что такое скролл самп
Контролируем scroll полностью или реализация события React: onRenderChildrenComplete
Всем привет! Я фуллстек, пишу код для сервиса TolstoyComments. В процессе работы был накоплен ценный опыт, как решать разного рода задачи на React. Этим опытом я хочу поделиться.
Бывало так, что после загрузки страницы, вам нужно сделать прокрутку экрана к заданному месту? Все ли всегда получалось с первого раза? И что делать если скролл все равно дергается в процессе загрузки страницы?
Ссылка на источник картинки https://donttakefake.com/otkuda-vzyalsya-mem-nelzya-prosto-tak-vzyat-i-rasskazyvaet-rezhisser-vlastelina-kolets/ Генератор мемов: iloveimg.com
Это пример самой комментируемой статьи на хабре когда скролл не с первого раза показывает нужный комментарий
Пример самой комментируемой статьи на хабре когда скролл не с первого раза показывает нужный комментарий
В примере я хотел показать, что даже на хабре это работает не с первого раза. Видно, что после инициализации страницы, нужный комментарий оказался в нужном месте, потом он куда-то улетел, а потом снова оказался в нужном месте.
Почему же так происходит?
Все дело в высоте содержимого над комментарием! Если в процессе отрисовки страницы изменится высота содержимого над комментарием, то позиционирование скролла будет потеряно.
Разберем более детально пример в начале статьи. Сначала загружается HTML и делается доскролл до нужного комментария. Потом скролл теряется, так как высота содержимого над комментарием изменилась и скролл уже показывает не туда куда надо. После этого происходит коррекция скролла и он снова начинает показывать куда надо. Здесь я делаю предположение, что возможно разработчики хабра это предусмотрели и на всякий случай сделали костыль, который срабатывает как раз в том случае если скролл все таки теряется.
Почему высота содержимого меняется?
Самый простой случай, когда содержимое верстки может менять свою высоту связано с выводом картинок. Картинки в верстке имеют ширину и высоту и чаще всего их вывод делается адаптивным за счет css стилей. Чаще всего пользуются вот таким сочетанием стилей:
Это магическая конструкция заставляет браузеры пропорционально масштабировать изображение, под разную ширину экрана. Но чтобы она корректно работала нужно выполнить одно условие: нужно чтобы в теге img были прописаны исходные размеры изображения: ширина и высота (параметры width и height).
Взглянем на верстку картинок в комментариях на Хабре:
Как можно видеть, атрибутов width и height тут нет, а значить браузер не сможет корректно определить высоту содержимого страницы до того, как начнется загрузка самой картинки.
Получается, чтобы обойти проблему с выводом картинок нужно:
Либо заранее прописывать корректные значения width и height
Либо вешать на картинку события onload onerror и обрабатывать эти события чтобы узнать о том когда картинка загрузится или не загрузится.
А по другому никак?
Да конечно, есть еще вариант как сделано на хабре, я кстати тоже так сделал при разработке комментариев на портале myslo.ru (пример ссылки на комментарий и отработки доскролла)
С помощью этих функций происходит повторная докрутка скролла если с первого раза что-то пошло не так. Конечно это приводит к некрасивым сайд эффектам на странице.
Как избавиться от сайд эффектов?
Все просто, нужно избавиться от динамического контента! Шучу конечно, на практике такое невозможно и это нужно как-то решать.
Самый простой пример реализации скролла до нужного элемента на react:
Есть некоторый компонент, после инициализации которого происходит вызов useLayoutEffect и перемещение скролла. В этом примере у нас нет элементов, которые могут менять свою высоту в процессе рендеринга страницы. Поэтому доскролл будет работать корректно.
Пример доскролла до элемента на codesandbox.
Теперь добавим немного динамического контента
Пример с подгрузкой картинок на codesandbox
Если внимательно посмотреть что будет происходить в этом примере то можно заметить как скролл сразу отображается на нужном месте но полоса скролла как-то странно уменьшается. Что же тут происходит?
Сначала происходит полная загрузка HTML. Потом браузер делает фокус скролла на нужном нам элементе. А потом происходит подгрузка картинок выше места скролла. Но поскольку все современные браузеры умные, то они автоматически двигает скролл так чтобы содержимое не смещалось относительно левого верхнего угла.
Браузер пытается любой ценой сохранить нужное нам положение скролла. Иногда у него это получается, иногда нет.
В случае использования scrollintoview браузер понимает на каком элементе нужно сохранить положение скролла до полной загрузки страницы, и поэтому положение скролла не теряется. Но что делать если у нас SPA и мы хотим реализовать восстановление положения скролла при переходе на шаг назад?
В этом случае, проблема в том что мы не можем использовать метод scrollintoview. Мы можем использовать window.scrollTo но этот метод уже не имеет такого умного поведения сохранения положения скролла.
Можно ли корректно восстановить скролл?
Для удобства я сделал вывод числового значения положения скролла и записал процесс обновления страницы в замедленном виде.
Страница загружается, потом происходит перемещение скролла на 1000 (как и нужно). Но потом скролл становится 1294 и даже 1589. Откуда такие значения скролла?
Пример с выводом текущего положения скролла, решенный через костыль codesandbox
Разберем написанный код:
Эта строка должна восстанавливать положение скролла, но она не работает, так как на странице есть динамические компоненты с изменяемой высотой.
Меня такое решение не устраивает!
Скролл дергается в процессе отрисовки страницы и это мягко говоря не красиво, а грубо говоря раздражает.
На этом месте уже можно сформулировать задачу:
Дано, страница с динамическими элементами, которые могут менять свою высоту в процессе отрисовки.
Нужно реализовать такой метод onRenderChildrenComplete чтобы он вызывался сразу после завершения отрисовки всех дочерних компонентов.
А если динамический компонент добавлен через условие? Здесь могут возникнуть сложности.
Формулируя выше описанные требования я решил все реализовать через контекст:
Здесь описана реализация заглушки для контекста.
После подключения хука в нем будет вызван инкримент счетчика countInit. А после завершения рендеринга в компоненте будет вызвано событие onRender и тем самым значения счетчика countRender совпадет со значением счетчика countInit. При совпадении этих значений произойдет вызова события onRenderChildrenComplete.
После загрузке всех картинок на странице происходит вызов метода, в котором запускается позиционирование скролла.
Конечно в реальных проектах могут быть совершенно разные динамические компоненты, намного сложнее приведенного примера с картинками. Здесь так же может идти речь о компонентах, которые нуждается в дополнительном fetch. Но тем не менее, описанный выше способ позволяет решить и эти проблемы.
Резюме
Контролировать положение скролла непросто. Велика вероятность получить сайд эффект или потерю положения скролла если делать позиционирование не в нужный момент. В мире React эту проблему можно решить и получить возможность корректного восстановления скролла для SPA сайтов. Пример в статье с картинками явно показывает что даже в случае нескольких динамических компонентов, событие завершения рендеринга всех дочерних компонентов отрабатывает корректно.
Приведенный мною код не решает всех проблем динамического рендеринга. Здесь по-прежнему остается просто поле скрытых нюансов, каждый из которых характерен для конкретной задачи.
Практика использования спецификации CSS Scroll Snap
Часто ли у вас возникало желание воспользоваться какой-нибудь возможностью CSS, позволяющей, без лишних усилий, создать элемент-контейнер, поддерживающий прокрутку? CSS, что очень хорошо, даёт нам такую возможность. Я, когда только начинал заниматься фронтенд-разработкой, пользовался для создания прокручиваемых элементов JavaScript-плагинами. Но иногда нужно что-то такое, что позволяет создавать подобные элементы просто и быстро, без привлечения JavaScript. Сделать это можно, воспользовавшись спецификацией CSS Scroll Snap.
Здесь я хочу раскрыть основы практического использования этой спецификации. Я сам только недавно разобрался с CSS Scroll Snap, поэтому рассказывать всё это буду, так сказать, «по горячим следам».
Зачем использовать CSS Scroll Snap?
С ростом популярности мобильных телефонов и планшетов растёт и необходимость разработки компонентов веб-страниц, прокручивать которые удобно, пользуясь сенсорными экранами, и, в частности, жестами, известными как «свайпы». Возьмём, например, компонент, реализующий галерею изображений. Пользователь может легко, пользуясь «свайпами», направленными влево или вправо, листать галерею, изображения в которой расположены горизонтально. С таким компонентом удобнее работать, чем с компонентом, содержимое которого нужно прокручивать по вертикали.
Вертикальная организация содержимого не всегда удобна при работе со страницами на устройствах, оснащённых сенсорными экранами. С содержимым, организованным горизонтально, на таких устройствах работать удобнее
Одной из основных причин появления спецификации CSS Scroll Snap является обеспечение разработчиков удобными средствами для организации хорошо контролируемой прокрутки содержимого страниц. Это позволяет расширить пользовательский опыт и упростить реализацию механизмов страниц, предусматривающих использование прокрутки.
Основы работы с контейнерами, поддерживающими прокрутку
Для того чтобы создать элемент-контейнер, поддерживающий прокрутку, понадобится следующее:
Контейнер, поддерживающий прокрутку элементов
Это — база, на основе которой создаются контейнеры, поддерживающие прокрутку. Но для достижения нашей цели одного этого недостаточно. Для того чтобы с подобным контейнером было бы удобно работать, над ним ещё надо потрудиться.
Проблема контейнеров, поддерживающих прокрутку содержимого
Проблема нашего контейнера заключается в том, что он не даёт пользователю тех же удобств, что и использование «свайпов». Главная сильная сторона подобных жестов, выполняемых на сенсорном экране, заключается в том, что они позволяют удобно, одним пальцем, листать содержимое страниц, выводимое горизонтально или вертикально.
Вот как выглядит работа с содержимым страницы, при создании которой используется контейнер, который мы только что описали.
Работа с обычным контейнером, поддерживающим прокрутку
Как видите, каждый элемент приходится буквально «вести» на его место, не отрывая палец от экрана. Это — не «свайп» и это очень неудобно с точки зрения пользователя. Но, используя возможности CSS Scroll Snap, мы можем решить эту проблему, просто описав точки привязки (snap point), которые упростят горизонтальную или вертикальную прокрутку содержимого страницы.
Знакомство с CSS Scroll Snap
Для того чтобы воспользоваться возможностями CSS Scroll Snap дочерние элементы должны выводиться внутри контейнера в inline-режиме. Сделать это можно с использованием одного из вышеописанных методов. Я использую для этих целей Flexbox-макет.
Теперь нам, чтобы спецификация CSS Scroll Snap заработала бы, нужно воспользоваться ещё парой свойств. Главный вопрос тут заключается в том, куда именно их нужно добавить.
Благодаря использованию этих свойств мы настроили элементы, выводимые в контейнере, так, что они будут привязаны к началу контейнера.
Элементы привязаны к началу контейнера
То, что у нас получилось сейчас, мне очень нравится. Такой подход к прокрутке содержимого контейнера делает работу с ним более естественной. Остановимся подробнее на свойствах, которые позволили этого достичь.
▍Свойство scroll-snap-type
▍Оси контейнера
Оси контейнера представляют собой направления прокрутки содержимого этого контейнера. Прокрутка может осуществляться по горизонтали или по вертикали. Значение x указывает на горизонтальную прокрутку, значение y — на вертикальную.
Горизонтальная прокрутка и вертикальная прокрутка
▍Настройка жёсткости привязки
На следующем рисунке показано, как браузер привязывает элементы к началу контейнера каждый раз, когда пользователь прокручивает содержимое контейнера по горизонтали в одном направлении.
Элементы жёстко привязаны к началу контейнера
Здесь можно найти видео к этому примеру.
Прокрутка с привязкой
А вот — интерактивный вариант этого примера.
Работа с интерактивным вариантом примера
Самое приятное тут то, что при прокрутке элементов, делается ли это на компьютере, с использованием мыши, или на сенсорном экране, можно буквально почувствовать то, как элементы «притягиваются» к началу контейнера.
Элементы привязаны к началу контейнера менее жёстко
Последствия использования значения proximity
▍Свойство scroll-snap-align
Для того чтобы лучше разобраться в этих значениях — взгляните на следующий рисунок. Представим, что в контейнере имеется магнит, который позволяет нам контролировать точки привязки содержимого контейнера. Рассмотрим действие этого «магнита» на содержимое контейнера, ориентированное по горизонтали.
Значения свойства scroll-snap-align и их влияние на содержимое, расположенное в контейнере по горизонтали
Значения свойства scroll-snap-align и их влияние на содержимое, расположенное в контейнере по вертикали
Ниже приведено несколько видеопримеров.
▍Привязка содержимого к началу контейнера
При установке свойства scroll-snap-align в значение start содержимое контейнера привязывается к его началу.
Привязка содержимого к началу контейнера
▍Привязка содержимого к центру контейнера
При установке свойства scroll-snap-align в значение center содержимое контейнера привязывается к его центру.
Привязка содержимого к центру контейнера
▍Привязка содержимого к концу контейнера
При установке свойства scroll-snap-align в значение end содержимое контейнера привязывается к его концу.
Привязка содержимого к концу контейнера
▍Использование свойства scroll-snap-stop
Вот видеодемонстрация прокрутки списка, созданного с использованием вышеописанных стилей. Если список прокручивать слишком быстро, можно легко «проскочить» через три-четыре элемента.
При слишком быстрой прокрутке некоторые элементы можно пропустить
Вот соответствующие стили:
Браузер не позволяет пропускать элементы
В результате пользователь, выполнив одно движение, может прокрутить список лишь на один элемент. Благодаря этому можно предотвратить пропуск важных элементов. При таком подходе каждая точка привязки будет напоминать знак «STOP».
Точки привязки — это, при использовании значения always, то же самое, что и знаки «STOP»
Эксперименты со свойством scroll-snap-stop
▍Внутренние отступы и свойство scroll-padding
Вот стили, соответствующие сценарию горизонтальной прокрутки:
Использование свойства scroll-padding при реализации горизонтальной прокрутки
То же самое применимо и к контейнерам с вертикальной прокруткой:
Использование свойства scroll-padding при реализации вертикальной прокрутки
▍Внешние отступы и свойство scroll-margin
Использование свойства scroll-margin
Способы использования CSS Scroll Snap
▍Список изображений
CSS Scroll Snap отлично подходит для создания списка изображений. Благодаря этому работать с таким списком будет гораздо удобнее, чем с использованием обычной прокрутки.
Прокручиваемый список изображений
Вот интерактивный вариант этого примера.
Эксперименты со списком изображений
▍Список друзей
Список друзей — это ещё один отличный способ использования CSS Scroll Snap. Нижеприведённый пример взят с Facebook (то есть — перед нами — реальный пример).
Вот CSS-код к этому примеру:
Без использования свойства padding-bottom тень выводится не полностью
▍Список аватаров
Этот подход хорош для тех случаев, когда нужно, чтобы аватар располагался бы по центру контейнера.
Здесь с этим примером можно поэкспериментировать.
Эксперименты со списком аватаров
▍Список разделов, занимающих всю доступную высоту области просмотра страницы
Использование спецификации CSS Scroll Snap может пригодиться и при реализации сценариев вертикальной прокрутки элементов. Например — при организации работы с элементами, занимающими всю высоту области просмотра страницы.
Элементы, занимающие всю высоту области просмотра страницы
Вот разметка к этому примеру:
Здесь можно найти рабочий вариант этого примера.
Элементы, занимающие всю доступную высоту области просмотра страницы
▍Значения inline и block свойства scroll-snap-type
В данном примере значение inline представляет горизонтальное измерение в горизонтальных режимах вывода данных, например — в английском языке. А для языков вроде японского значение inline будет представлять вертикальное измерение.
Подробнее о логических CSS-свойствах можно почитать здесь.
▍Доступность
При применении спецификации CSS Scroll Snap стоит помнить о доступности контента. Вот пример плохого использования этой спецификации. Здесь настройки прокрутки мешают пользователю свободно читать выводимые материалы.
Прокрутить текст так, как показано справа, при таком подходе нельзя. Дело в том, что при прокрутке осуществляется привязка заголовков к началу контейнера
Неудачное использование CSS Scroll Snap
Пожалуйста, постарайтесь так не делать!
Пользуетесь ли вы возможностями CSS Scroll Snap в своих проектах?
Обзор технологий скроллинга
Анимации, имеющие отношение к скроллингу веб-страниц, существуют уже многие годы. В последнее время подобные анимации стали распространённее. Возможно, дело тут отчасти в том, что устройства, используемые для работы в интернете, стали мощнее. Эти устройства способны нормально обрабатывать и выводить больше визуальных эффектов, чем раньше.
Существует множество технологий, связанных со скроллингом. Цель этой статьи заключается в том, чтобы дать обзор таких технологий и инструментов, которые помогут подобрать и создать то, что нужно в каждой конкретной ситуации. Я разделил бы технологии скроллинга на две широкие категории. В первую входят технологии для реализации специфических механизмов скроллинга, во вторую — технологии скроллинга общего назначения.
Технологии для реализации специфических механизмов скроллинга
В CSS существует несколько простых стандартных эффектов скроллинга, поддерживаемых современными браузерами. В некоторых случаях их применения может быть достаточно для того чтобы обеспечить особенные нужды некоего проекта.
▍CSS-свойство position: sticky
Синий элемент «упирается» в верхнюю часть контейнера и не прокручивается вместе с остальными элементами
Вот демонстрация такого скроллинга.
▍Эффект параллакса
Эффект параллакса — это, скорее, не особая технология, а специальный технический приём. Но, как бы там ни было, этот эффект может оказаться весьма кстати в тех случаях, когда нужно, чтобы при скроллинге разные части страницы двигались бы с разной скоростью. Данный приём хорошо описан в этом материале. Существует и немало примеров его реализации. Например — этот. Для меня главный минус этого приёма заключается в том, что сложно понять то, какие значения, дающие правильный эффект параллакса, нужно использовать для настройки перспективы и трансформаций.
Эффект параллакса: элементы движутся с разной скоростью.
Вот демонстрация эффекта параллакса.
▍Прокрутка с привязкой к определённым точкам
Использование скроллинга с точками привязки позволяет браузеру автоматически настраивать положение элементов, перемещая их в определённую позицию после того, как пользователь завершил обычную операцию скроллинга. Это может оказаться полезным в случаях, когда нужно, чтобы после завершения прокрутки некий элемент находился бы целиком в поле зрения пользователя. Однако соответствующий API пока ещё нестабилен, поэтому постарайтесь пользоваться самыми свежими его реализациями и с осторожностью относитесь к применению этого подхода к скроллингу в продакшне. Вот хорошая статья на эту тему.
Если пользователь, прокручивая содержимое, уводит элемент за верхнюю границу контейнера, браузер автоматически изменит положение элемента так, чтобы он был бы виден целиком
Вот демонстрация работы скроллинга с точками привязки.
▍Плавная прокрутка
Плавный скроллинг поддерживается средствами браузера при прокрутке страницы до определённого раздела с использованием метода window.scrollTo() в JavaScript, или даже с применением CSS-свойства scroll-behavior. В настоящее время для реализации плавного скроллинга со сглаживанием движений колеса мыши требуются специальные JavaScript-библиотеки. Но при применении таких библиотек нужно обеспечить их нормальное взаимодействие с другими технологиями скроллинга. Кроме того, использование плавного скроллинга — это далеко не всегда хорошая идея.
Технологии скроллинга общего назначения
▍Использование API Intersection Observer
API IntersectionObserver позволяет с успехом решать различные задачи, связанные со скроллингом, в том случае, если всё, что нужно для запуска анимации, — это знание о том, видим ли элемент в области просмотра, а так же о том, какая именно часть элемента видима. Это делает API IntersectionObserver отличным инструментом для запуска анимации, сопровождающей появление элемента на экране. Но, даже так, некоторые эффекты очень сложно (хотя и можно) реализовать с помощью этого API. Например — это запуск разных анимаций в зависимости от направления движения элемента. Этот API, кроме того, не особенно полезен в ситуации, если при скроллинге нужно запускать анимацию тогда, когда элемент находится где-то в середине области просмотра, то есть, не появляется в области просмотра и не исчезает из неё.
▍Использование события scroll
Инструменты для создания механизмов скроллинга общего назначения
Существует несколько мощных библиотек для реализации скроллинга, которые нацелены на то, чтобы дать разработчику полный контроль над анимациями, выполняемыми при прокрутке страниц, а так же на то, чтобы как можно сильнее облегчить разработчику жизнь, не заставляя его самостоятельно заниматься сложными вычислениями.
▍ScrollMagic
Библиотека ScrollMagic даёт нам сравнительно простой API, позволяющий создавать различные эффекты при скроллинге. Эта библиотека может работать совместно с различными библиотеками для анимации, наподобие GSAP и Velocity.js. Правда, в последние несколько лет эта библиотека недостаточно хорошо поддерживается. Это привело к тому, что была создана библиотека ScrollScene.
▍ScrollScene
ScrollScene — это, в сущности, обёртка, которая направлена на то, чтобы повысить удобство работы с библиотекой ScrollMagic и (или) с API IntersectionObserver. Здесь используется собственная версия ScrollMagic, которая отличается лучшей поддержкой, чем обычный вариант библиотеки. Тут имеются и дополнительные возможности, наподобие проигрывания видео и поддержки контрольных точек, влияющих на анимацию. Кроме того, эта библиотека использует GSAP.
▍ScrollTrigger
Библиотека ScrollTrigger — это официальный GreenSock-плагин для GSAP. Эта библиотека отличается большим набором возможностей, её API кажется мне самым простым из API существующих библиотек для скроллинга. Используя эту библиотеку, вы полностью контролируете то, где именно начинается и заканчивается анимация скроллинга, вы можете анимировать при прокрутке всё что угодно (WebGL, canvas, SVG, DOM), можете закреплять элементы на время выполнения анимации. Этим возможности данной библиотеки не ограничиваются. Кроме того, эту библиотеку поддерживает GreenSock, получить помощь по её использованию можно на форумах GreenSock.
▍Библиотека, достойная упоминания: Locomotive Scroll
Сравнение технологий и инструментов
Вот сравнение технологий скроллинга.
API Intersection Observer | Плавная прокрутка | Точки привязки в CSS | CSS-эффект параллакса | CSS-свойство position: sticky | |
Закрепление элементов | — | — | — | — | + |
Эффект параллакса | — | — | — | + | — |
Управление динамикой анимации | ± | — | — | ± | — |
Использование контрольных точек | ± | — | + | — | — |
Динамическая пакетная обработка элементов | + | — | — | — | — |
Поддержка эффектов горизонтального скроллинга | + | + | + | + | + |
Подходит для продакшна (хорошая браузерная поддержка) | ± | ± | ± | + | ± |
Полная свобода в анимировании | — | — | — | — | — |
Поддержка разработчиком | n/a | n/a | n/a | n/a | n/a |
Работа с DOM, Canvas, WebGl, SVG | + | — | — | — | — |
Поддержка изменения размеров элементов | + | + | + | + | + |
Ограничивает анимацию только релевантным разделом | + | + | + | — | + |
Различает направления скроллинга | ± | — | — | — | — |
Технология, встроенная в браузер | + | + | + | + | + |
Вот сравнение рассмотренных библиотек.
ScrollTrigger | Locomotive Scroll | ScrollScene | ScrollMagic | |
Закрепление элементов | + | ± | + | + |
Эффект параллакса | + | + | + | + |
Управление динамикой анимации | + | ± | ± | ± |
Использование контрольных точек | + | ± | ± | ± |
Динамическая пакетная обработка элементов | + | — | + | — |
Поддержка эффектов горизонтального скроллинга | + | + | + | + |
Подходит для продакшна (хорошая браузерная поддержка) | + | ± | + | + |
Полная свобода в анимировании | + | ± | + | + |
Поддержка разработчиком | + | + | + | — |
Работает с DOM, Canvas, WebGl, SVG | + | ± | + | + |
Поддержка изменения размеров элементов | + | + | + | ± |
Ограничивает анимацию только релевантным разделом | + | — | ± | ± |
Различает направления скроллинга | + | ± | + | + |
Технология, встроенная в браузер | — | — | — | — |
Итоги
Для реализации некоторых особых механизмов скроллинга, вроде закрепления элементов и эффекта параллакса, может быть достаточно стандартных возможностей CSS. По меньшей мере — это так при условии использования полифиллов для браузеров, которые соответствующие возможности CSS не поддерживают.
Я обычно, для настройки скроллинга, рекомендую использовать библиотеку ScrollTrigger. Она позволяет достичь всего, на что способен чистый CSS, а так же — многого другого. Эта библиотека берёт на себя заботу о браузерной поддержке тех или иных технологий, облегчает выполнение вычислений, что позволяет тому, кто её использует, просто заниматься своими делами.
Какие технологии вы используете при настройке скроллинга в своих проектах?