ScrollMagic Demo

Още 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 причини:

  1. Все пак това е основното заглавие (различно е). Освен, че в markup-а е H1, а другите са H2 и има по-голям шрифт (по дизайн е различно)
  2. Искаме това заглавие да бъде видимо още като се зареди страницата, така че то ще почне от средата и само ще излезе надясно. Останалите първо ще “влязат” на екрана и след това ще минат през целия него.

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);

Разликите са, че:

  1. Базираме сцените на screen-а (това е референцията, която подаваме на triggetElement)
  2. triggerHook при хоризонталната анимация е 0.55, защото искаме показването на заглавието да започне, когато screen-а вече почти е стигнал до средата на екрана
  3. 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/ .

Вашият коментар

Вашият имейл адрес няма да бъде публикуван. Задължителните полета са отбелязани с *

Този сайт използва Akismet за намаляване на спама. Научете как се обработват данните ви за коментари.