Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Animation

Створення бездоганної каруселі. Частина 2

by
Read Time:10 minsLanguages:
This post is part of a series called Create the Perfect Carousel.
Create the Perfect Carousel, Part 1
Create the Perfect Carousel, Part 3

Ukrainian (українська мова) translation by AlexBioJS (you can also view the original English article)

Вітаю вас у серії посібників «Створення бездоганної каруселі». Ми створюємо зручну та чарівну карусель за допомогою можливостей Popmotion з симуляції фізичних явищ (* на зразок швидкості, прискорення, тертя, зусилля пружини. Тут і надалі примітка перекладача), JavaScript, твінінгу (* побудування проміжних відображень; плавний перехід від одного ключового кадру до іншого з [автоматичним] створенням проміжних кадрів (в анімації)) та відстеження дій користувача.

У 1-й частині цієї серії ми розглянули, як Amazon та Netflix реалізували їх каруселі, і оцінили переваги та недоліки їх підходів. Використовуючи отримані знання, ми вибрали стратегію створення нашої каруселі та реалізували можливість прокручування контенту за допомогою дотиків завдяки можливостям Popmotion з симуляції фізичних явищ.

У другій частині ми реалізуємо можливість прокручування контенту по горизонталі. Також ми розглянемо деякі звичайні прийоми реалізації пагінації (* перехід між сторінками/елементами) та скористаємося одним. Нарешті, ми підключимо індикатор ходу процесу, за допомогою якого буде вказуватися, скільки контенту каруселі переглянув користувач.

Ви можете освіжити у пам'яті, де ми зупинилися, відвідавши цей Pen (* фрагмент коду на Codepen).

Додаємо можливість прокручування контенту по горизонталі

Нечасто буває, щоб у каруселі, реалізованій за допомогою JavaScript, була можливість прокручування контенту по горизонталі. Це печально: для користувачів лептопів та мишок з можливістю прокручування контенту по горизонталі, заснованій на інерції (* інерціальне прокручування), цей спосіб є найшвидшим способом перегляду елементів каруселі. Це настільки ж печально, як те, коли користувачів, які мають можливість керування контентом за допомогою дотиків, змушують переглядати елементи каруселі за допомогою кнопок, а не рухів пальцями по екрану.

На щастя, цей спосіб можна реалізувати за допомогою декількох рядків коду. У кінці функції carousel додайте новий слухач подій.

Нижче вашого обробника подій startTouchScroll додайте заглушку (* невеликий фрагмент програмного коду, котрий або нічого не робить, або друкує повідомлення типу "FileOpenStub», або підставляє необхідні для налагодження дані і т.ін.; додається до розроблюваної програми замість ще не написаної функції (драйвера, модуля, підсистеми, ...)) під назвою onWheel:

Тепер, якщо ви скористаєтеся коліщатком прокручування, знаходячись у каруселі, та перевірите свою консоль, то побачите там дані про положення коліщатка по осі Х.

Як і у випадку з прокручуванням контенту за допомогою дотиків, якщо рух коліщатка здійснюється по вертикалі, як це звичайно відбувається, то сторінка повинна прокручуватися як звичайно. Якщо рух коліщатка здійснюється по горизонталі, то нам потрібно зібрати дані про рух коліщатка та застосувати їх до каруселі. Так що в onWheel замініть console.log наступним кодом:

За допомогою цього блоку коду прокручування сторінки зупиниться, якщо вона відбувається по горизонталі. Для оновлення відступу нашого повзунка по осі Х тепер всього-на-всього потрібно отримати значення властивості deltaX об'єкта події та додати його до поточного значення sliderX:

Ми повторно використовуємо нашу раніше створену функцію clampXOffset для обгортання цих обчислень та забезпечення того, щоб карусель не прокручувалася за межі її розрахованих границь.

Відступ з приводу обмеження частоти генерації подій, що виникають при прокручуванні

У будь-якому доброму посібнику, в якому працюють з подіями, що виникають при діях з боку користувача, буде роз'яснено важливість обмеження частоти виникнення тих подій. Це так, оскільки події, що виникають при прокручуванні, діях з мишею та діях, виконуваних користувачем дотиками пальцями по екрану, всі можуть генеруватися швидше частоти зміни кадрів (* швидкість сканування або виводу на екран відеокадрів (ЗО кадрів за секунду в стандарті NTSC і 25 кадрів за секунду в стандарті PAL/SECAM)) пристрою.

Вам не потрібно, щоб виконувалася зайва ресурсозатратна робота на зразок рендерінгу (* процес побудови й відображення графічної сцени або тривимірного об'єкта за його описом в растрову цифрову форму (КГА)) каруселі двічі за один фрейм, оскільки це марна розтрата ресурсів та прямий шлях до повільно реагуючого інтерфейсу.

У цьому посібнику не розглядалося це питання, оскільки рендерери, надавані Popmotion, використовують Framesync, крихітний планувальник завдань, синхронізований з фреймами. Це означає, що ви могли би викликати (v) => sliderRenderer.set('x', v) багато разів підряд, а ресурсозатратний рендерінг було би виконано тільки один раз, у наступному фреймі.

Пагінація

На цьому все з прокручуванням. Тепер нам потрібно трохи оживити навігаційні кнопки, з якими до цього часу не працювали.

Тепер ось що: цей посібник присвячений реалізації можливості взаємодії користувача з інтерфейсом, тому ви запросто можете змінити дізайн цих кнопок на той, який вам потрібен. Особисто мені здається, що вказівні стрілки інтуїтивно зрозуміліші (і повністю інтернаціоналізовані за налаштуванням).

Як повинна працювати пагінація?

Є дві явні стратегії, якими ми могли би скористатися для реалізації можливості пагінації по елементам каруселі: стратегія, згідно з якою перехід відбувається поступово по всім елементам, або стратегія, згідно з якою перехід відбувається по першим частково відображеним (* неясним, нечітким) елементам. Тільки одна з них вірна, проте оскільки я бачив, наскільки часто використовується інша, то я подумав, що варто було би пояснити, чому остання невірна.

1. Стратегія, згідно з якою перехід відбувається поступово по всім елементам

Item By Item Example

Просто виміріть відступ по осі Х наступного елемента списку та перемістіть контейнер з елементами на ту відстань. Це дуже простий алгоритм, який вибирають через його простоту, а не дружелюбність по відношенню до користувача.

Проблема полягає в тому, що на більшості екранів зможе відобразитися безліч елементів одночасно, і люди нашвидку переглянуть їх всі перед тим, як спробувати перейти до наступного частково прихованому елементу.

При цьому ваш інтерфейс здасться користувачам повільно реагуючим, якщо не зовсім їх засмутить. Єдина ситуація, коли цей варіант був би вдалим вибором, – та, коли вам відомо, що елементи вашої каруселі мають той самий розмір або трохи менше видимої області екрана.

Проте, якщо відображається безліч елементів одночасно, то краще використовувати другий метод.

2. Стратегія, згідно з якою перехід відбувається по першим частково відображеним елементам

The First Obscured ItemThe First Obscured ItemThe First Obscured Item

При цьому підході відбувається пошук першого частково відображеного елемента у напрямку, в якому ми хочемо переміщати карусель, отримання його відступу по осі Х та прокручування до нього.

Завдяки цьому ми отримуємо максимальну кількість нових елементів, керуючись припущенням, що користувач вже бачив всі представлені на цей момент елементи.

Оскільки ми отримуємо більше елементів, то для переміщення по каруселі потрібно натиснути навігаційні кнопки меншу кількість разів. У результаті швидкішої навігації збільшиться залученість користувачів та буде гарантовано, що вони побачать більшу кількість ваших продуктів.

Слухачі подій

Для початку давайте додамо слухачі подій, так щоб ми могли почати працювати над пагінацією.

Спершу нам потрібно вибрати наші кнопки для переходу до попереднього та наступного елементам. Угорі функції carousel додайте наступний код:

Потім внизу функції carousel додайте слухачі подій:

Нарешті, вище вашого блоку зі слухачами подій додайте власне функції:

goto – функція, в якій буде реалізовано всю логіку для пагінації. Функція просто приймає число, яке представляє потрібний нам напрямок переміщення по каруселі. gotoNext та gotoPrev просто викликають цю функція, передаючи 1 або -1 відповідно.

Вибір «сторінки»

Користувач може запросто прокрутити цю карусель, у ній є n елементів, і можна змінити розмір каруселі. Так що концепція традиційної сторінки буквально тут не підходить. Ми не будемо підраховувати кількість сторінок.

Замість цього при виклику функції goto ми дізнаємося напрямок, вказаний у delta, та отримаємо перший частково відображений елемент. Він стане першим елементом нашої наступної сторінки.

На першому етапі нам потрібно отримати поточний відступ по осі Х нашого повзунка та скористатися ним разом з повною видимою шириною повзунка, щоб знайти «ідеальний» відступ, на який ми би хотіли прокрутити карусель. Ідеальний відступ – той, на який ми би прокрутили карусель, якщо би не були зацікавлені вмістом повністю відображених елементів каруселі. Він надає нам підходяще місце для початку пошуку нашого першого елемента.

Ми можемо виконати тут приблизну оптимізацію. Завдяки передачі значення targetX до функції clampXOffset, реалізованої у попередньому посібнику, ми можемо перевірити, чи відрізняється значення, яке вона повертає, від того, що міститься у targetX. Якщо так, то це означає, що наше значення targetX знаходиться за межами значень границь нашої прокручуваної області, так що нам не потрібно визначати найближчий частково відображений елемент. Ми просто прокручуємо карусель до кінця.

Знаходження найближчого частково відображуваного елемента

Потрібно відзначити, що робота наступного коду заснована на припущенні, що всі елементи вашої каруселі мають однаковий розмір. Це дозволяє нам виконати оптимізацію, яка полягає у тому, що нам не потрібно змінювати розмір кожного елемента. Якщо ваші елементи мають різний розмір, то наведене нижче рішення як і раніше є доброю відправною точкою.

Над функцією goto додайте функцію findClosestItemOffset, згадану в останньому фрагменті коду:

Для початку нам потрібно дізнатися ширину наших елементів та інтервалів між ними. Метод Element.getBoundingClientRect() може надати нам цю інформацію. Для визначення ширини ми просто вимірюємо ширину першого елемента. Для того щоб обчислити ширину інтервалу між елементами, ми можемо виміряти right (* правий) відступ першого елемента та left (* лівий) відступ другого, а потім відняти з останнього перший:

Тепер, за наявності значень змінних targetX та delta, переданих до функції (* findClosestItem), ми маємо всі дані, потрібні для швидкого визначення відступу, до якого нам потрібно прокрутити карусель.

Обчислення полягає у поділі абсолютного значення targetX на результат суми ширина елемента + ширина інтервалу. У результаті ми отримаємо точну кількість елементів, яку можемо вмістити у межах тієї відстані.

Потім округляємо з підвищенням числа або з пониженням числа, в залежності від напрямку пагінації (вказаного у delta). У результаті вийде кількість повних елементів, які ми можемо вмістити.

Нарешті, помножуємо це число на суму ширина елемента + ширина інтервалу для отримання нового відступу, що починається з повністю відображеного елемента.

Анімація пагінації

Тепер, коли ми маємо значення targetX, ми можемо занімувати карусель до нього. Для цього ми скористаємося робочим коником веб-анімацій – твіном (від англ. tween).

Для тих, хто не знає, tween – скорочене від between (* від англ. між). За допомогою твіну змінюються значення властивостей упродовж вказаного періоду часу. Якщо ви користувалися CSS-переходами, то знате, що це те саме.

Є ряд переваг (та недоліків!) використання JavaScript замість CSS для реалізації твінів. Оскільки ми також анімуємо sliderX за допомогою можливостей Popmotion з симуляції фізичних явищ та в залежності від виконаних користувачем дій, то у цьому випадку нам буде легше дотримуватися використання цієї технології (* JavaScript) і для реалізації твінів.

Також завдяки цьому пізніше ми можемо підключити індикатор ходу процесу, і він буде вільно працювати з усіма нашими анімаціями без додаткових зусиль з нашого боку.

Для початку нам потрібно імпортувати tween з Popmotion:

У кінці нашої функції goto ми можемо додати наш твін, за допомогою якого значення змінюється від значення currentX, до значення targetX:

За налаштуванням у Popmotion в якості значення duration задано 300 мілісекунд та easing.easeOut в якості значення ease (* від англ. згладжувати; задавати варіант зміни анімації). Ці значення було вибрано спеціально для надання анімаціям, які виникають при взаємодії користувача з інтерфейсом, чутливого характеру (* на зразок сповільнення прокручування каруселі), проте ви запросто можете поекспериментувати з цими значеннями та подивитися, чи вийде у вас щось, що краще відповідає вашому бренду.

Індикатор ходу процесу

Користувачам корисно знати, яку частину каруселі вони вже переглянули. Для цього ми можемо підключити індикатор ходу процесу.

Для вашого індикатора могло би бути задано різне стильове оформлення. Для цього посібника я підготував кольоровий елемент div висотою 5px, що розташовується між кнопками для переходу до попереднього та наступного елементів. У цьому посібнику для нас важливий та знаходиться у центрі нашої уваги саме спосіб, за допомогою якого ми підключаємо до нашого коду та анімуємо індикатор.

Ви ще не бачили індикатор, оскільки ми початково задали для нього правило transform: scaleX(0). Ми використовуємо трансформацію scale для підгонки ширини індикатора, оскільки, як ми розібрали у частині 1, трансформації більш ефективні, ніж зміна значень властивостей на зразок left або, як у нашому випадку, width.

Це також дозволяє нам легко написати код, в якому розмір задається у процентах: поточне значення sliderX, що знаходиться між значеннями minXOffset та maxXOffset.

Давайте почнемо з отримання нашого індикатора div.progress-bar після селектора кнопки для переходу до попереднього елемента (prev):

Після визначення sliderRenderer ми можемо додати рендерер (* апаратний пристрій або програмний продукт, який виконує рендерінг зображення) для progressBar:

Тепер давайте додамо функцію для оновлення значення scaleX індикатора ходу процесу.

Ми скористаємося функцією об'єкта calc (* об'єкт з методами для здійснення різних обчислень) під назвою getProgressFromValue. Вона приймає діапазон, який у нашому випадку визначається значеннями minXOffset та maxXOffset, та третє число. Ця функція повертає інформацію про положення, число між 0 та 1, того третього числа у межах встановленого діапазону.

Ми вказали діапазон тут у вигляді maxXOffset, minXOffset, тоді як інтуїція підказує, що ці значення повинні були би бути вказані у зворотному порядку. Це так, оскільки значеннями x та maxXOffset є від'ємні числа, тоді як в якості значення minXOffset виступає 0. Формально 0 є більшим з двох вищезазначених чисел (* maxXOffset та minXOffset), проте менше значення власне представляє максимальний відступ. Від'ємні значення, ви зрозуміли?

Ми хочемо, щоб індикатор ходу процесу оновлювався паралельно зі значенням sliderX, так що давайте змінимо наступний рядок:

На цей:

Тепер кожного разу при оновленні значення sliderX буде оновлюватися і значення індикатора ходу процесу.

Завершення

На цьому ми завершили з цією частиною! Ви можете завантажити самий останній код у цьому Pen на Codepen. Ми вдало реалізували можливість прокручування каруселі по горизонталі за допомогою коліщатка прокручування, пагінації та індикатор ходу процесу.

На цей момент карусель виглядає доволі добре! В останній частині ми вдосконалимо її. Ми додамо можливість повного керування каруселлю за допомогою клавіатури, щоб гарантувати те, що будь-хто може нею користуватися.

Також ми додамо декілька чарівних ефектів за допомогою смикання, реалізованого завдяки ефекту зусилля пружини, що виникає, коли користувач намагається прокрутити карусель за її межі або за допомогою дотиків пальцями, або завдяки пагінації.

Тоді і побачимося!

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.