Skip to content

Instantly share code, notes, and snippets.

@yamoo9
Last active February 23, 2020 06:37
Show Gist options
  • Save yamoo9/d32638a68a15ecfa6a98ebdb98064a25 to your computer and use it in GitHub Desktop.
Save yamoo9/d32638a68a15ecfa6a98ebdb98064a25 to your computer and use it in GitHub Desktop.
스타벅스 사이트 애니메이션 개선 스크립트 (이듬 블렌디드 러닝 실습 자료)
/**
* 이듬(E.UID) 라이브러리
* 블렌디드 러닝: yamoo9.github.io/EUID
* 작성자: yamoo9.naver.com
*/
var EUID = {
_version: '0.0.1',
// querySelector() = IE 9+
// 참고: https://caniuse.com/#feat=queryselector
el: function(selector, context) {
return (context || document).querySelector(selector);
},
elList: function(selector, context) {
return (context || document).querySelectorAll(selector);
},
// getBoundingClientRect() = IE 9+
// 참고: https://caniuse.com/#feat=getboundingclientrect
getRect: function(selector, prop) {
var rectInfo = EUID.el(selector).getBoundingClientRect();
return prop ? rectInfo[prop] : rectInfo;
},
// DOM 요소의 영역 안(Top)으로 스크롤 되었는지 확인하는 유틸리티
isTargetInside: function(target) {
var viewportHeight = window.innerHeight;
var offsetTop = EUID.getRect(target, 'top');
return offsetTop <= (viewportHeight - offsetTop / 6);
},
// DOM 요소의 영역 밖(Bottom)으로 스크롤 되었는지 확인하는 유틸리티
isTargetOutside: function(target) {
return EUID.getRect(target, 'bottom') <= 0;
},
// DOM 요소의 스크롤 영역 안, 밖에서 실행 될 콜백 함수 설정 유틸리티
insideOutside: function(target, options) {
var isTargetInside = EUID.isTargetInside;
var isTargetOutside = EUID.isTargetOutside;
// 1회 실행 옵션 기본값 설정
if (options.once === undefined) { options.once = true }
// outsideCallback 옵션 기본값 설정
if (options.outside === undefined) { options.outside = function(){} }
// 타겟이 안으로 들어왔을 때
if (isTargetInside(target) && !isTargetOutside(target)) {
if (!EUID[target]) {
EUID[target] = true;
typeof options.inside === 'function' && options.inside();
}
}
// 타겟이 밖으로 나갔을 때
if (!isTargetInside(target) || isTargetOutside(target)) {
if (!options.once) { delete EUID[target] }
typeof options.outside === 'function' && options.outside();
}
},
// classList = IE 10+ (replace() IE 미지원)
// 참고: https://caniuse.com/#feat=classlist
addClass: function(selector, className) {
EUID.el(selector).classList.add(className);
},
removeClass: function(selector, className) {
EUID.el(selector).classList.remove(className);
},
hasClass: function(selector, className) {
return EUID.el(selector).classList.contains(className);
},
toggleClass: function(selector, className) {
EUID.el(selector).classList.toggle(className);
},
repalceClass: function(selector, oldClass, newClass) {
return EUID.el(selector).classList.replace(oldClass, newClass);
},
// 비동기 스크립트 로드 & 콜백 유틸리티
loadAsyncCallback: function(scriptPath, callback) {
var newScript = document.createElement('script');
newScript.setAttribute('src', scriptPath);
newScript.addEventListener('load', callback.bind(this));
document.head.appendChild(newScript);
},
// 디바운스 유틸리티
debounce: function(callback, timeout, immediate) {
timeout = timeout || 100;
immediate = immediate || true;
var clearTimeoutID = null;
var afterCallback = function() {
clearTimeoutID = null;
if (!immediate) {
callback.apply(context, args);
}
};
var debounceAction = function() {
var context = this;
var args = arguments;
var callNow = immediate && !clearTimeoutID;
clearTimeout(clearTimeoutID);
clearTimeoutID = setTimeout(afterCallback, timeout);
callNow && callback.apply(context, args);
};
return debounceAction;
},
// 고유 아이디 유틸리티
uid: function() {
return 'euid-' + Math.floor(Math.random() * 10000);
},
// 레디 유틸리티
ready: function (callback) {
window.addEventListener('DOMContentLoaded', callback.bind(null, this));
},
// 디바운스 스크롤 유틸리티
debounceScroll: function(callback, wait) {
wait = wait || 10;
window.addEventListener('scroll', EUID.debounce(callback, wait));
}
};
// EUID 라이브러리 객체의 ready() 메서드
try {
// DOMContentLoaded 되면 콜백함수 실행
EUID.ready(practiceScrollAnimation);
} catch (error) {
// 오류 발생 시, Console에 오류 출력
console.error('EUID 라이브러리를 필요로 하는 스크립트입니다.\
EUID 라이브러리 스크립트를 먼저 호출해야 정상적으로 작동합니다.');
}
// 스크롤 애니메이션 실습
// 매개변수 $ = EUID 라이브러리 객체
function practiceScrollAnimation($) {
// 초기화
function init() {
bannerAnimation();
scrollParallaxSettings();
flipYMedal();
}
// STRAWBERRY 배너 애니메이션
function bannerAnimation() {
// anime 타임라인 객체 생성
var timeline = anime.timeline({
opacity: 0,
easing: 'spring(0.3, 50, 4, 1)',
duration: 800,
});
// 타임라인 옵셋
var offset = '-=1000';
// 타임라인 애니메이션
timeline
.add({
targets: '.strawberry_mv_wrap',
filter: [ 'hue-rotate(156deg)', 'hue-rotate(0deg)' ],
duration: 800,
easing: 'linear',
delay: 400,
})
.add(
{
targets: '.strawberry_set.set_01',
opacity: [ 0, 1 ],
translateX: [ -800, 0 ],
},
'-=300' // 상대 시간
)
.add(
{
targets: '.strawberry_set.set_02',
opacity: [ 0, 1 ],
translateY: [ -800, 0 ],
},
offset
)
.add(
{
targets: '.strawberry_set.set_03',
opacity: [ 0, 1 ],
translateX: [ 800, 0 ],
},
offset
)
.add(
{
targets: '.strawberry_slogan',
opacity: [ 0, 1 ],
translateY: [ 100, 0 ],
duration: 1000,
},
offset
)
.add(
{
targets: '.btn_strawberry_slogan',
opacity: [ 0, 1 ],
translateY: [ 100, 0 ],
duration: 1000,
},
'-=800'
)
.add(
{
targets: '.layer_floating',
opacity: [ 0, 1 ],
translateY: [ -1000, 0 ],
duration: 1400,
easing: 'spring(0.3, 50, 4, 1)',
},
1700 // 절대 시간
);
}
// 스크롤 애니메이션 초기 설정
function scrollParallaxSettings() {
// 애니메이션 준비 과정으로 위치, 투명도 초기화
initRewards();
initGuatemalaAtitlan();
initAprival();
initFavorite();
initStore();
// 디바운스(완급 조절: 성능 저하 방지) 스크롤 이벤트 연결
$.debounceScroll(handleScrollParallaxAnimation);
}
// 스크롤 패럴럭스 애니메이션 이벤트 핸들러
function handleScrollParallaxAnimation(e) {
// STARBUCKS REWARDS
rewardsAnimation();
// 과테말라 아티틀란
guatemalaAtitlanAnimation();
// NEW APRIVAL
aprivalAnimation();
// PICK YOUR FAVORITE
favoriteAnimation();
// 스타벅스를 가까이서 경험해보세요.
storeAnimation();
}
/**
* 애니메이션 초기화 → 스크롤 애니메이션
*/
function initRewards() {
anime.set('.reward_star_bg', { translateX: -100, });
anime.set(
[ '.reward_texbg img', '.reward_btn', '.reward_egift img', '.reward_egift_btn' ],
{
opacity: 0,
translateY: 30,
}
);
}
function rewardsAnimation() {
$.insideOutside('.bean_wrap_inner', {
inside: function() {
anime({
targets: '.reward_star_bg',
translateX: 0,
duration: 600,
easing: 'easeInSine',
});
anime({
targets: [ '.reward_texbg img', '.reward_btn', '.reward_egift img', '.reward_egift_btn' ],
opacity: 1,
translateY: 0,
duration: 700,
easing: 'easeOutSine',
delay: anime.stagger(100),
});
},
// 1회만 애니메이션 처리하지 않을 경우 false 설정
// once: false,
// once 설정 값을 false로 설정 시, DOM 객체 위치 초기화
// outside: function() {
// initRewards();
// }
});
}
function initGuatemalaAtitlan() {
anime.set([
'.newyear20_bean', '.newyear20_bean_ttl', '.newyear20_bean_txt', '.btn_newyear20_bean',
'.res_bean_17spring2', '.black_reserve_txt_area', '.black_btn_sum19.btnSpPc'
], { opacity: 0, });
anime.set('.newyear20_bean', { translateX: -300, });
anime.set('.newyear20_bean_ttl', { translateX: 300, });
anime.set('.newyear20_bean_txt', { translateX: 300, });
anime.set('.btn_newyear20_bean', { translateY: 50, });
}
function guatemalaAtitlanAnimation() {
$.insideOutside('.newyear_bean_wrap', {
inside: function() {
var tl = anime.timeline({
duration: 800,
easing: 'easeInOutExpo',
});
tl
.add(
{
targets: '.newyear20_bean',
opacity: 1,
translateX: 0,
},
100
)
.add(
{
targets: '.newyear20_bean_ttl',
opacity: 1,
translateX: 0,
},
300
)
.add(
{
targets: '.newyear20_bean_txt',
opacity: 1,
translateX: 0,
},
350
)
.add(
{
targets: '.btn_newyear20_bean',
opacity: 1,
translateY: 0,
duration: 1000,
easing: 'easeInOutExpo',
},
400
);
},
});
}
function initAprival() {
anime.set([
'.res_bean_17spring2', '.black_reserve_txt_area', '.black_btn_sum19.btnSpPc'
], { opacity: 0, });
}
function aprivalAnimation() {
$.insideOutside('.spring_20_reserve', {
inside: function() {
var tl = anime.timeline({
duration: 1000,
easing: 'linear',
});
tl
.add({
targets: '.res_bean_17spring2',
opacity: 1,
})
.add(
{
targets: '.black_reserve_txt_area',
opacity: 1,
},
500
)
.add(
{
targets: '.black_btn_sum19.btnSpPc',
opacity: 1,
},
1000
);
},
});
}
function initFavorite() {
anime.set([ '.fav_prod_txt01', '.fav_prod_txt02', '.btn_fav_prod' ], {
opacity: 0,
translateY: -200,
});
anime.set([ '.summer_17_fav_img.strawberry20Fav' ], {
opacity: 0,
translateX: 50,
});
}
function favoriteAnimation() {
$.insideOutside('.winter_fav_bg', {
// 스크롤 영역 안에 스크롤링 되면 반복 애니메이션 처리
once: false,
inside: function() {
var tl = anime.timeline({
duration: 1000,
delay: 300,
});
tl
.add({
targets: '.fav_prod_txt01',
opacity: 1,
translateY: 0,
})
.add(
{
targets: '.fav_prod_txt02',
opacity: 1,
translateY: 0,
},
200
)
.add(
{
targets: '.btn_fav_prod',
opacity: 1,
translateY: 0,
},
300
)
.add(
{
targets: '.summer_17_fav_img.strawberry20Fav',
opacity: 1,
translateX: 0,
duration: 600,
easing: 'easeOutQuint',
},
0
);
},
outside: function() {
initFavorite();
}
});
}
function initStore() {
anime.set('#storeWrap', {
overflow: 'hidden',
});
anime.set('.store_exp_img03', {
opacity: 0,
translateY: -100,
});
anime.set('.store_exp_img04', {
opacity: 0,
translateY: 100,
});
anime.set('.store_exp_img02', {
opacity: 0,
translateX: -50,
translateY: -50,
});
anime.set('.store_exp_img01', {
opacity: 0,
translateX: 50,
translateY: 50,
});
anime.set('.store_txt01', {
opacity: 0,
perspective: 600,
rotateY: -720,
});
anime.set('.store_txt02', {
opacity: 0,
perspective: 600,
rotateY: 360,
});
anime.set('.store_btn', {
scale: 0,
});
}
function storeAnimation() {
$.insideOutside('#storeWrap', {
inside: function() {
var tl = anime.timeline({
duration: 800,
easing: 'easeOutQuint',
});
tl
.add({
targets: '.store_exp_img03',
opacity: 1,
translateY: 0,
})
.add(
{
targets: '.store_exp_img04',
opacity: 1,
translateY: 0,
},
100
)
.add(
{
targets: '.store_exp_img02',
opacity: 1,
translateX: 0,
translateY: 0,
},
300
)
.add(
{
targets: '.store_exp_img01',
opacity: 1,
translateX: 0,
translateY: 0,
easing: 'spring(0.4, 90, 4, 5)',
},
500
)
.add(
{
targets: '.store_txt01',
opacity: 1,
perspective: 600,
rotateY: 0,
duration: 1400,
easing: 'easeOutExpo',
},
400
)
.add(
{
targets: '.store_txt02',
opacity: 1,
perspective: 600,
rotateY: 0,
duration: 1400,
easing: 'easeOutExpo',
},
600
)
.add(
{
targets: '.store_btn',
scale: 0,
easing: 'spring(0.4, 90, 7, 15)',
},
900
);
},
});
}
// 메달 플립(Y축)
function flipYMedal() {
var medal = $.el('#reserve2_medal');
var front = $.el('.front', medal);
var back = $.el('.back', medal);
var medalLink = $.el('a', medal);
anime.set(medal, {
position: 'relative',
perspective: 670,
});
anime.set([ front, back ], {
height: 334,
width: 334,
transformStyle: 'preserve-3d',
position: 'absolute',
backfaceVisibility: 'hidden',
rotateY: function(target, index) {
return index > 0 ? 180 : 0;
}
});
function handleMedalOver() {
anime({
targets: [ front, back ],
rotateY: function(target, index) {
return index > 0 ? 0 : 180;
},
duration: 1600,
});
}
function handleMedalOut() {
anime({
targets: [ front, back ],
rotateY: function(target, index) {
return index > 0 ? 180 : 0;
},
duration: 1600,
});
}
medal.addEventListener('mouseenter', handleMedalOver);
medal.addEventListener('mouseleave', handleMedalOut);
medalLink.addEventListener('focus', handleMedalOver);
medalLink.addEventListener('blur', handleMedalOut);
}
init();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment