Довольно часто в интернете можно встретится с интересными эффектами на сайте и многие из них нацелены на то, чтобы сфокусировать на себе интерес пользователя и задержать его на сайте как можно дольше (Youtube: «10 часов с гипно-жабой»).
Увеличение среднего времени на сайте делает сайт более привлекательным для рекламодателей и хорошо влияет на ранжирование в поисковых системах.

Идея
С разработкой одного из таких эффектов довелось столкнуться нашей команде технической поддержки. Клиент — довольно прогрессивный дизайнер, работающий на зарубежный рынок, и ему было важно, чтобы работы, представленные на его сайте могли заинтересовать не только визуальным содержимым, но и функциональным. Таким образом было составлено тех. задание, на эффект «зависания изображений», словно привет из 2000-х когда в Windows после победы в косынке мы наблюдали следующее:
Идея эффекта в том, чтобы прокручивая страницу изображения оставляли за собой плавный «хвост» из своих копий, буд-то сдвинута колода карт.
ГОТОВЫЙ ПРИМЕР
Как это работает
Чтобы реализовать данный эффект необходимо было сперва создать несколько копий изображений и поместить их под главное изображение. Для этого используем метод .clone(), и забегая вперед скажу, что нам понадобится знать количество изображений поэтому мы объявим один раз переменную для количества изображений и будем ее использовать. Создаем простейший цикл создания копий изображений:
var img_count = 4; $('.img-block').each(function(){ for (i = 1; i <= img_count; i++) { $(this).find('img:last-child') .clone() .addClass("newImg"+i) .prependTo(this); }});
Добавляем класс новым изображениям и нумеруем их, чтобы в дальнейшем мы могли задать им свою скорость анимации. Для самого базового эффекта мы зададим всем картинкам одинаковую скорость анимации и абсолютным позиционированием поместим их друг под другом:
img[class^="newImg"]{ position: absolute; transition: 0.2s; }
Теперь необходимо добавить смещение при скроллинге. Для этого создадим 3 функции: для скроллинга вверх, вниз и для возвращения картинок назад. Для смещения будем использовать css стиль translate3d, чтобы мы могли позиционировать картинки как нам необходимо не изменяя базовые стили положения изображений:
function moveBtm(img_count, current){ for (var i = 2; i <= img_count; i++) { j = i+i; $('.newImg'+i).css('transform','translate3d(0, '+current*j+'px, 0)'); } }
В функцию добавляем цикл обхода каждого изображения. В смещении будем использовать разницу положения страницы от начала скроллинга до конца, за это отвечает переменная current. Таким образом мы получим разное расстояние смещения при разной силе скролла. А чтобы еще больше усилить эффект смещения мы умножим его на порядковый номер изображения, таким образом картинки не будут складываться в единый блок и будут смещаться друг за другом. С помощью такой манипуляции j = i+i (где i — порядковый номер изображения) мы можем регулировать отступы картинок друг за другом и они будут иметь визуально правильную разницу отступа. Цикл стартуем со второго изображения, поскольку нам необходимо, чтобы первая картинка была неподвижной.
Тоже самое делаем и для скролла вниз:
function moveTop(img_count, current){ for (var i = 2; i <= img_count; i++) { j = i+i; $('.newImg'+i).css('transform','translate3d(0, -'+current*j+'px, 0)'); } }
С той лишь разницей, что по оси Y мы делаем смещение в минус.
Далее нам необходимо сбросить стиль translate3d иначе картинки не будут возвращаться назад до конца:
function clear(img_count){ for (var i = 2; i <= img_count; i++) { $('.newImg'+i).css('transform','translate3d(0, 0, 0)'); } }
Проблемы
Итак картинки созданы, смещение задано, теперь настало время для задания действий. В целом уже сейчас можно слушать обычный scroll() и с помощью scrollTop() вычислить наш current, но проблема в том, что стандартный скроллинг слишком грубый и не позволяет нам добится плавности при прокрутки.
К тому же при таком скроллинге картинки будут грубо скакать и добиться плавности будет очень сложно.
Решение с помощью js плагинов
Чтобы это исправить подключим плагин Smooth Scrollbar и реализуем плавную прокрутку на странице. Также сразу добавим плагин Overscroll, входящий в состав данного расширения, и когда пользователь прокрутит страницу до конца она не будет резко упираться в край экрана.
var Scrollbar = window.Scrollbar; Scrollbar.use(window.OverscrollPlugin) const scrollbar = Scrollbar.init(document.querySelector('body'), { damping: 0.08, plugins: { overscroll: { effect: 'bounce', } }, });
После подключения плагина Smooth Scrollbar стандартный метод scroll() будет выдавать нам пустое значение, это особенность работы плагина, но разработчики плагина продумали это и добавили собственный метод для прослушивания скролла. Добавляем переменную для записи в нее значения текущей позиции страницы и слушаем дальнейшие действия пользователя с помощью метода scrollbar.addListener():
CurrentScroll = 0; scrollbar.addListener(() => { var offset = scrollbar.offset.y; var NextScroll = offset; if ((NextScroll > CurrentScroll) && (offset != 0)){ current = NextScroll - CurrentScroll; moveBtm(img_count, current); } else if (offset != 0) { current = CurrentScroll - NextScroll; moveTop(img_count, current); } CurrentScroll = NextScroll; setTimeout(function(){ clear(img_count); }, 400); });
Поскольку scroll() не работает, то в плагине также присутствует метод для отслеживания позиции страницы scrollbar.offset. С его помощью вычисляем наш current по простой формуле:
current = NextScroll - CurrentScroll - если крутим вниз current = CurrentScroll - NextScroll - если крутим вверх
Чтобы понять в какую сторону скроллится страница просто сравниваем прошлое положение и новое:
если NextScroll > CurrentScroll - то крутится вниз, иначе вверх
В конце метода запускам функцию очистки значения в отложенном режиме, чтобы картинки успели доехать до верхней точки и плавно вернуться назад. На этом все. Полный код выглядит так
$(document).ready(function(){ var img_count = 4; $('.img-block').each(function(){ for (i = 1; i <= img_count; i++) { $(this).find('img:last-child').clone().addClass("newImg"+i).prependTo(this); } }); function moveBtm(img_count, current){ for (var i = 2; i <= img_count; i++) { j = i+i; $('.newImg'+i).css('transform','translate3d(0, '+current*j+'px, 0)'); } } function moveTop(img_count, current){ for (var i = 2; i <= img_count; i++) { j = i+i; $('.newImg'+i).css('transform','translate3d(0, -'+current*j+'px, 0)'); } } function clear(img_count){ for (var i = 2; i <= img_count; i++) { $('.newImg'+i).css('transform','translate3d(0, 0, 0)'); } } var Scrollbar = window.Scrollbar; Scrollbar.use(window.OverscrollPlugin) const scrollbar = Scrollbar.init(document.querySelector('body'), { damping: 0.08, plugins: { overscroll: { effect: 'bounce', } }, }); CurrentScroll = 0; scrollbar.addListener(() => { var offset = scrollbar.offset.y; var NextScroll = offset; if ((NextScroll > CurrentScroll) && (offset != 0)){ current = NextScroll - CurrentScroll; moveBtm(img_count, current); } else if (offset != 0) { current = CurrentScroll - NextScroll; moveTop(img_count, current); } CurrentScroll = NextScroll; setTimeout(function(){ clear(img_count); }, 400); }); });