Главная » Статьи » Эффект анимации прокрутки Apple

Эффект анимации прокрутки Apple

Эффект анимации прокрутки Apple

От автора: Apple хорошо известна своей стильной анимацией на страницах товаров. Например, когда вы прокручиваете страницу вниз, товары могут разворачиваться, MacBook открываются и переходит в iPhone, демонстрируя аппаратное обеспечение и рассказывая интерактивные истории о том, как использовать товары.

Многие эффекты, которые вы видите, созданы не только в HTML и CSS. В чем же спросите вы? Ну, это может быть немного трудно понять. Даже использование инструментов разработчика браузера не всегда дает ответ, так как они часто не могут видеть элемент canvas.

Давайте подробно рассмотрим один из этих эффектов, чтобы увидеть, как он создан, и затем воссоздать некоторые из этих магических эффектов в наших собственных проектах. В частности, давайте повторим страницу товара AirPods Pro и эффект смещения света на баннере.

Основная концепция

Идея состоит в том, чтобы создать анимацию, как последовательность изображений с быстрым чередованием. Ну, как мультфильм в блокноте! Никаких сложных сцен WebGL или расширенных библиотек JavaScript не требуется.

Синхронизируя каждый кадр с позицией прокрутки пользователя, мы можем воспроизводить анимацию, когда пользователь прокручивает страницу вниз (или назад).

Начинаем с разметки и стилей

HTML и CSS для этого эффекта очень просты, поскольку магия происходит внутри элемента canvas, который мы контролируем с помощью JavaScript, присваивая ему идентификатор.

В CSS мы зададим документу высоту 100vh и сделаем body в 5 раз выше него, чтобы получить необходимую длину прокрутки для этой работы. Мы также сопоставим цвет фона документа с цветом фона изображений.

Последнее, что мы сделаем, это позиционируем canvas по центру и ограничим его max-width и height, чтобы он не превышал размеры окна просмотра.

html { height: 100vh;
}


body { background: #000; height: 500vh;
}


canvas { position: fixed; left: 50%; top: 50%; max-height: 100vh; max-width: 100vw; transform: translate(-50%, -50%);
}

Прямо сейчас мы можем прокрутить страницу вниз (даже если содержимое не превышает высоту области просмотра), и canvas остается в верхней части области просмотра. Это все, что нам нужно от HTML и CSS. Давайте перейдем к загрузке изображений.

Выбор правильных изображений

Поскольку мы будем работать с последовательностью изображений (опять же, похоже на мультфильм в блокноте), мы будем предполагать, что имена файлов нумеруются в том же каталоге последовательно в порядке возрастания (т. е. 0001.jpg, 0002.jpg, 0003.jpg и т. д.).

Мы напишем функцию, которая возвращает путь к файлу с номером файла изображения, который нам нужен, основываясь на позиции прокрутки пользователя.

const currentFrame = index => ( `https://www.apple.com/105/media/us/airpods-pro/2019/1299e2f5_9206_4470_b28e_08307a42f19b/anim/sequence/large/01-hero-lightpass/${index.toString().padStart(4, '0')}.jpg`
)

Так как номер изображения является целым числом, нам нужно преобразовать его в строку и использовать padStart(4, ’0′) для добавления нулей перед индексом, пока мы не получим четыре цифры, соответствующие именам наших файлов. Так, например, передав 1 в эту функцию, мы получим 0001.

Это дает нам возможность обрабатывать пути к изображениям. Вот первое изображение в последовательности, выведенное в элементе canvas:

Как видите, первое изображение уже на странице. На данный момент это просто статический файл. Мы хотим обновлять его на основе позиции прокрутки пользователя. И мы не просто хотим загрузить один файл изображения, а затем заменить его, загрузив другой файл. Мы хотим отображать изображения в canvas и обновлять рисунок следующим изображением в последовательности (но мы вернемся к этому чуть позже).

Мы уже создали функцию для генерации пути к файлу изображения на основе числа, которое передаем в него, поэтому теперь нам нужно отследить позицию прокрутки пользователя и определить соответствующий кадр изображения для этой позиции прокрутки.

Подключение изображений к позиции прокрутки пользователя

Чтобы узнать, какое число нам нужно передать (и, следовательно, какое изображение загрузить), нам нужно рассчитать прогресс прокрутки пользователя. Мы создадим прослушиватель событий, чтобы отслеживать это и обрабатывать некоторые математические вычисления для определения того, какое изображение загрузить.

Нам нужно знать:

Где прокрутка начинается и заканчивается

Прогресс пользователя при прокрутке (т. е. процент от того, как далеко пользователь переместился вниз по странице)

Изображение, которое соответствует прогрессу прокрутки пользователя

Мы будем использовать scrollTop, чтобы получить позицию вертикальной прокрутки элемента, которая в нашем случае будет верхней частью документа. Это будет служить нам отправной точкой. Мы получим конечное (или максимальное) значение, вычтя высоту окна из высоты прокрутки документа. Далее мы разделим значение scrollTop на максимальное значение, которое пользователь может прокрутить вниз, что дает нам прогресс прокрутки пользователя.

Затем нам нужно преобразовать этот прогресс прокрутки в порядковый номер, соответствующий последовательности нумерации изображений, чтобы мы могли вернуть соответствующее изображение для этой позиции. Мы можем сделать это, умножив число прогресса на количество кадров (изображений), которые у нас есть. Мы будем использовать Math.floor(), чтобы округлить это число, и обернем его в Math.min() с максимальным количеством кадров, чтобы оно никогда не превышало общее количество кадров.

window.addEventListener('scroll', () => { const scrollTop = html.scrollTop; const maxScrollTop = html.scrollHeight - window.innerHeight; const scrollFraction = scrollTop / maxScrollTop; const frameIndex = Math.min( frameCount - 1, Math.floor(scrollFraction * frameCount) );
});

Обновление canvas соответствующими изображениями

Теперь мы знаем, какое изображение нам нужно вывести, когда изменяется прогресс прокрутки пользователя. Вот где в игру вступает магия canvas. canvas имеет много интересных функций для создания различных вещей, от игр и анимации до генераторов дизайна макетов и много чего еще!

Одной из таких функций является метод requestAnimationFrame, который работает в браузере для обновления canvas способом, который был бы нам не доступен, если бы вместо этого мы работали напрямую с файлами изображений. Вот почему я выбрал подход canvas вместо, скажем, элемента img или элемента div с фоновым изображением.

requestAnimationFrame будет соответствовать частоте обновления браузера и включать аппаратное ускорение с помощью WebGL для его рендеринга с использованием видеокарты устройства или встроенной графики. Другими словами, мы получим очень плавные переходы между кадрами — изображение не будет мигать!

Давайте вызовем эту функцию в приемнике событий прокрутки, чтобы поменять изображения, когда пользователь прокручивает страницу вверх или вниз. requestAnimationFrame принимает аргумент обратного вызова, поэтому мы передадим функцию, которая обновит источник изображения и выведет в canvas новое изображение:

requestAnimationFrame(() => updateImage(frameIndex + 1))

Мы увеличиваем на frameIndex на 1, потому что, хотя последовательность изображений начинается с 0001.jpg, наш вычисленный прогресс прокрутки фактически начинается с 0. Это гарантирует, что эти два значения всегда соответствовать.

Функция обратного вызова, которую мы передаем для обновления изображения, выглядит следующим образом:

const updateImage = index => { img.src = currentFrame(index); context.drawImage(img, 0, 0);
}

Мы передаем frameIndex в функцию. Это устанавливает источник изображения со следующим изображением в последовательности, которое выводится в элементе canvas.

Улучшение с помощью предварительной загрузки изображения

Формально мы закончили. Но мы можем сделать все еще лучше! Например, быстрая прокрутка приводит к небольшому отставанию между кадрами изображения. Это связано с тем, что каждое новое изображение отправляет новый сетевой запрос, требующий новой загрузки.

Мы можем попробовать предварительно загрузить изображения новых сетевых запросов. Таким образом, каждый кадр будет уже загружен, что сделает переходы намного быстрее, а анимацию — более плавной! Все, что нам нужно сделать, это перебрать всю последовательность изображений и загрузить их:

const frameCount = 148;


const preloadImages = () => { for (let i = 1; i < frameCount; i++) { const img = new Image(); img.src = currentFrame(i); }
};


preloadImages();

Краткое примечание относительно производительности

Хотя этот эффект довольно красивый, он также предполагает использование огромного количества изображений. 148, если быть точным.

Независимо от того, насколько мы оптимизируем изображения или насколько быстро работает CDN, загрузка сотен изображений всегда приведет к раздутой странице. Допустим, у нас есть несколько экземпляров на одной странице. Мы можем получить статистику производительности следующим образом:

Эффект анимации прокрутки Apple

Это может быть хорошо для высокоскоростного интернет-соединения без жестких ограничений по объему передаваемых данных, но мы не можем сказать то же самое о мобильных пользователях. Это сложный баланс, но мы должны помнить об опыте каждого и о том, как наши решения влияют на них.

Вот несколько вещей, которые мы можем сделать, чтобы помочь достичь такого баланса:

Загрузка одного запасного изображения вместо всей последовательности изображений

Создание последовательностей, которые используют меньшие файлы изображений для определенных устройств

Разрешение пользователю включить последовательность, возможно, с помощью кнопки, которая запускает и останавливает последовательность

Apple использует первый вариант. Если вы загрузите страницу AirPods Pro на мобильном устройстве, подключенном к медленному 3G-соединению, то статистика производительности будет выглядеть намного лучше:

Эффект анимации прокрутки Apple

Да, это все еще тяжелая страница. Но это намного легче, чем то, что мы получили бы без оптимизации производительности. Вот как Apple может получить так много сложных последовательностей на одной странице.

Автор: Jurn van Wissen

Источник: https://css-tricks.com

Редакция: Команда webformyself.