Last active
February 23, 2020 06:37
-
-
Save yamoo9/d32638a68a15ecfa6a98ebdb98064a25 to your computer and use it in GitHub Desktop.
스타벅스 사이트 애니메이션 개선 스크립트 (이듬 블렌디드 러닝 실습 자료)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 이듬(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)); | |
} | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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