Още ScrollMagic
Вече имаме един урок за ScrollMagic, но нека разгледаме и още един пример за неговите възможности, за да затвърдим как точно можем да работим с тази библиотека.
Примерът, който ще дадем тук няма да е много по-сложен, а по-скоро ще даде още една гледна точка и това как да работим с различните параметри и настройки.
Начални (примерни) файлове
За начало ще използваме готови HTML/CSS файлове, които можете да вземете от първия commit в GitHub към проект, който съм създал за този урок.
Крайният ефект, към който ще се стремим е заглавията на различните секции да се въртят най-горе на сайта подобно на marquee. Освен това ще търсим да добавим и някакъв fade effect на основните контейнери.
ScrollMagic JavaScript
Да започнем с помощна константа като предния път:
const vh = window.screen.availHeight / 100;
А след това да създадем и нашия ScrollMagic контролер:
const controller = new ScrollMagic.Controller();
Да припомним, че правим контролер за всеки scrollbar, на чиято база искаме после да правим анимации. В повечето случаи на една страница ще имате един контролер. Изключения бихме направили, ако имате елемент с вътрешен scroll и на негова база искате да имате други анимации.
Intro Заглавие
Първо нека анимираме основното заглавие на страницата.
Него ще обработим различно от заглавията на отделните секции по 2 причини:
- Все пак това е основното заглавие (различно е). Освен, че в markup-а е H1, а другите са H2 и има по-голям шрифт (по дизайн е различно)
- Искаме това заглавие да бъде видимо още като се зареди страницата, така че то ще почне от средата и само ще излезе надясно. Останалите първо ще „влязат“ на екрана и след това ще минат през целия него.
Pin-ване вертикално на екрана със ScrollMagic
Първо ще го pin-нем на екрана, така че при scroll-ване надолу да си остане вертикално намясто, а не да излезе нагоре:
const introTitlePin = new ScrollMagic.Scene({ triggerElement: '.intro__title', triggerHook: 0 })
.setPin('.intro__title')
.addTo(controller);
triggerElement
е самото заглавие, а triggerHook
е 0, което означава „когато е най-горе на екрана“. То така и така си е най-горе на екрана, но задаваме triggerHook
-а по този начин, за да нямаме неяснотии около очакваното държание.
След това просто викаме setPin()
метода, като pin-ваме пак самото заглавие. И цялата сцена добавяме към контролера си.
Да припомним, че сцена правим за всяка отделна анимация, която искаме. Или поне при фиксиране на елемент на екрана, както е в горния пример.
Анимиране по хоризонтала със ScrollMagic
После нека добавим и Tween анимация (вече отиваме към GSAP интеграцията със ScrollMagic), която да се движи за изместването на заглавието надясно към края на екрана:
const titleSwipe = new ScrollMagic.Scene({ triggerElement: '.intro__title', triggerHook: 0, duration: 50 * vh })
.setTween(new TweenLite.fromTo('.intro__title', 1, { x: 0 }, { x: '100%' }))
.addTo(controller);
Trigger-а отново е самото заглавие. Тук добавяме и продължителност на анимацията: половин екран (50 * vh
). Така заглавието започва от средата (хоризонтално) на екрана и след половин екран scroll ще изпълни анимацията.
Тази анимация сме задали в частта setTween
, която създава нов fromTo
tween, който започва от x: 0
, което е подобно на left: 0
в CSS. Разликата е, че освен по-кратко с x
, GSAP всъщност използва transform: translateX()
зад кулисите, за да промени тази стойност. По този начин работата се прехвърля от процесора към видео картата, което в повечето случаи води до по-добро бързодействие.
Крайните стойности при fromTo
анимация се подават в още един обект, наличен във функцията. Ако имахме само from
или само to
щяхме да имаме един обект със стилове. При fromTo
обаче трябва да зададем и двете двойки, всяка със собствен обект.
До момента имаме този първи commit с анимация на intro заглавието: https://github.com/magadanskiuchen/scroll-magic-demo/commit/2b31ef2743e6b4b019b5a6e5f5f340927807d5eb
Подзаглавия
Идва ред на подзаглавията.
Тъй като те са елементи вътрешни за всеки .screen
, освен .screen--intro
от началните файлове, неща направим JavaScript константа с тези елементи:
const screens = document.querySelectorAll('.screen:not(.screen--intro)');
Циклене с ES6 синтаксис
В предни уроци е възможно да съм използвал този синтаксис, без преди това да го обясня.
Нека сега се отклоня от основния урок и кажа няколко думи по въпроса.
Масиви или NodeList (списък с елементи, получени от querySelectorAll
) можем да изциклим с .forEach
. Това само по себе си не е ES6 синтаксис, но на този forEach
трябва да се подаде callback
функция, която да се изпълни за всеки елемент.
По принцип бихме написали такава callback функция като:
someArray.forEach(function (element) {
// действия, които извършваме спрямо този елемент
});
При ES6 синтаксис за улеснение (и запазване на scope-а на ключова дума this
, в случай на обектно-ориентиран JavaScript) можем да пропуснем ключовата дума function
. Освен това, ако имаме само един аргумент можем да пропуснем и кръглите скоби около него, а преди големите / фигурни скоби пишем =>
.
Така получаваме:
someArray.forEach(element => {
// действия, които извършваме спрямо този елемент
});
По принцип forEach
приема 3 аргумента. В JavaScript не сме длъжни да дефинираме опционални аргументи в callback функции, така че горното ще е валидно, но ако искаме да се възползваме и от останалите ще трябва да върнем обратно кръглите скоби:
someArray.forEach((element, index, wholeArray) => {
// действия, които извършваме спрямо този елемент
});
index
е поредния номер на елемента (започвайки от 0 за първия елемент) в списъка. wholeArray
пък е същото като someArray
в горния пример (ако ни се наложи манипулациите да ги базираме на някой друг елемент от масива).
Обаче можем пък и да опростим още повече работата. Ако същинската работа, която вършим в callback функцията е само един ред – можем да прескочим големите / фигурни скоби:
someArray.forEach(element => element.incrementSomething());
В горния пример, ако имаме масив от елементи можем да извикаме incrementSomething()
метода на всеки от елементите в този масив.
Циклене на screen
-овете ни
Сега ще направим:
screens.forEach(screen => {
const heading = screen.querySelector('.screen__title');
});
с което обикаляме всички screen-ове и взимаме заглавията в константа.
Pin-ване и хоризонтално анимиране на подзаглавия със ScrollMagic
Това като цяло става подобно на работата, която вече свършихме за основното заглавие, но нека направим и:
new ScrollMagic.Scene({ triggerElement: screen, triggerHook: 0 })
.setPin(heading)
.addTo(controller);
new ScrollMagic.Scene({ triggerElement: screen, triggerHook: 0.55, duration: 90 * vh })
.setTween(new TweenLite.fromTo(heading, 1, { x: '-100%', y: 0 }, { x: '100%', y: 0 }))
.addTo(controller);
Разликите са, че:
- Базираме сцените на screen-а (това е референцията, която подаваме на
triggetElement
) triggerHook
при хоризонталната анимация е0.55
, защото искаме показването на заглавието да започне, когато screen-а вече почти е стигнал до средата на екранаx
го променяме от-100%
до100%
– вече първо влиза на екрана от ляво и заминава надясно.
Понеже горното води до позициониране на заглавията, на височина където биха били, ако нямаха position: fixed
(ScrollMagic нарочно прави това, за да изглежда всичко по-натурално), трябва да добавим малко CSS, който да направи тези заглавия фиксирани отпреди това и най-горе на екрана:
.screen__title:not(.intro__title) {
position: fixed;
top: 0;
width: 100%;
}
Обобщено: направили сме следните промени: https://github.com/magadanskiuchen/scroll-magic-demo/commit/cf334810e7c543dd3a9388f9a7544173da9d5b74
Fade-in на самите screen-ове със ScrollMagic
За целта правим поредната сцена с tween, в които ще променим opacity-то на screen-а от 0 до 1 при scroll:
new ScrollMagic.Scene({ triggerElement: screen, triggerHook: 0.9, duration: 50 * vh })
.setTween(new TweenLite.fromTo(screen, 1, { opacity: 0 }, { opacity: 1, ease: Power3.easeInOut }))
.addTo(controller);
triggerHook
-а съм го задал на 0.9, което ще да рече най-долните 10% от екрана да е прозрачен и чак след това да започне анимацията.
duration
-а е половин екран.
Самият tween отново е от тим fromTo
, като задаваме отначало opacity: 0
, а накрая: opacity: 1
. За по-приятна анимация съм добавил easing, който при fromTo
се подава заедно с to
обекта със стилове.
Леко отместване на картинката
И понеже само един fade-in е прекалено елементарен ефект, нека допълним и едно отместване на картинката нагоре:
new ScrollMagic.Scene({ triggerElement: screen, triggerHook: 0.75, duration: 50 * vh })
.setTween(new TweenLite.to(screen.querySelector('.content__image'), 1, { y: 0, ease: Power1.easeOut }))
.addTo(controller);
Правим поредната сцена, където trigger-а отново е самият screen. Този път искаме отместването да започне след като вече screen-а е отминал 25% от долния ръб на екрана (съответно не е и прозрачен).
В самия tween задаваме да променим y
на картинката на 0, с по-полегат easing.
Тъй като тук в JavaScript-а нямаме from
на tween-а, а само to
трябва да добавим и следните стилове към всички картинки по подразбиране:
.content__image {
transform: translateY(30%);
}
Или като цяло по анимациите имаме https://github.com/magadanskiuchen/scroll-magic-demo/commit/056471581c217c51acbf988d464e264f583051fb
Резултат
Крайният код е наличен на https://github.com/magadanskiuchen/scroll-magic-demo а live preview има на https://magadanski.com/demo/parallax-scrollmagic-2/ .