Создание прокрутки прогресса в стиле Tech Crunch с помощью Vue, SVG и TypeScript
В этой статье я рассмотрю, как можно воспроизвести круговой бегунок Tech Crunch, который используется для геймификации своих статей, побуждая пользователей прокручивать их до конца (и просматривать рекламу в конце статьи).
Он будет построен с использованием Vue, TypeScript и SVG. Тем не менее, мы полностью отделим бизнес-логику (вычисления, связанные с прокруткой и анимацией) от Vue, чтобы вы могли легко адаптировать ее к выбранной вами структуре.
Скроллер Tech Crunch: Вы можете увидеть скроллер, который мы копируем, в действии в любой статье Tech Crunch, например, в этой. По мере прокрутки вниз зеленый ползунок в правом верхнем углу с X медленно заполняется.
Исходный код: исходный код доступен здесь.
Демо: рабочая демонстрация доступна здесь.
Эта статья не является типичным руководством «вот код, скопируйте и вставьте его» - я сначала напишу базовое доказательство концепции, а затем проведу его рефакторинг, обсудив некоторые передовые практики, которые я изучил при создании приложений Vue и независимых от фреймворков библиотек. с годами.
В частности, я рассмотрю разделение независимой от платформы логики (например, бизнес-логики, которая не использует какие-либо API-интерфейсы Vue) от кода, специфичного для платформы (например, частей приложения, которые ссылаются на this
, ссылаясь на экземпляр Vue или использовать систему реактивности Vue, такую как вычисляемые свойства).
Правильное разделение проблем имеет большую ценность; он позволяет легко интегрировать универсальные библиотеки в выбранную вами структуру, упрощает модульное тестирование и сохраняет простые компоненты - Vue - это платформа для создания пользовательского интерфейса, поэтому большая часть кода в ваших компонентах должна быть связана с представлением данных, а не с бизнесом. логика.
Я начну с создания нового приложения Vue с использованием vue-cli
со следующими параметрами: TyepeScript, Babel, синтаксис без компонентов класса.
Сначала я создам компонент под названием Progress.vue
в src/components
. Внутри добавьте следующий минимальный код - объяснение следует.
Это будет работать следующим образом: пользователь укажет два маркера. Они начинаются как null
. Они указывают, между какими двумя точками мы хотим отслеживать прогресс прокрутки. На данный момент это будут просто элементы HTML, выбранные document.querySelector
, но вы также можете разрешить пользователю передавать их как свойства. Мы будем хранить процент прокрутки пользователя в объекте data
.
Теперь добавьте следующий код в App.vue
, который мы будем использовать для тестирования компонента <Progress />
.
Много шаблонов, но пока ничего особенного. Рендерим кучу <div />
. Затем <div id=”progress-marker-start”></div>
- это момент, когда мы начнем отслеживать прогресс прокрутки. Далее у нас есть еще несколько <div />
и конечная точка <div id=”progress-marker-end”></div>
.
Это дает следующее:
В качестве доказательства концепции мы просто будем отображать% в правом верхнем углу, когда пользователь прокручивает страницу.
Расчет смещения маркеров
Есть несколько способов подсчитать, как далеко прокрутил пользователь. Тот, который, как я нашел, покрывает все крайние случаи и работает правильно, требует трех частей информации для расчета:
- Текущая позиция, к которой прокручены пользователи
- Положение
progress-marker-start
по оси y относительно верха области просмотра - Положение
progress-marker-end
по оси y относительно верха области просмотра
Получить текущую позицию прокрутки тривиально - мы можем использовать window.scrollY
и считать это днем. Две другие позиции y немного сложнее. Часть любого алгоритма для вычисления позиции элемента HTML почти всегда будет включать getBoundingClientRect()
. Моя стратегия ничем не отличается. Сначала моя стратегия заключалась в следующем:
Это казалось нормальным, пока вы не добавили отступ к элементу <div id="app" />
- тогда document.body.getBoundingClientRect().top
перестанет точно отражать верхнюю часть документа! Следующее изображение иллюстрирует это:
Вы можете видеть, что 25 пикселей не учтены, что обозначено красной стрелкой. Я решил это с помощью document.documentElement.getBoundingClientRect().top
. document.documentElement
относится к элементу <html />
, а getBoundingClientRect().top
возвращает 0:
Пока что у меня это отлично работает, но, возможно, есть предостережения и в отношении этого метода (например, если кто-то решил добавить margin
в HTML, что я не очень часто видел, если вообще когда-либо).
Обладая этими знаниями, мы можем добавить симпатичный маленький метод, который будет получать смещение по оси Y для элемента. Добавьте methods
ключ со следующей функцией в Progress.vue
:
Расчет хода прокрутки
Я хочу начать подсчет процента прокрутки не тогда, когда <div id=”progress-marker-start”></div>
появляется на экране, а когда он исчезает над верхней частью области просмотра. Итак, все, что нам нужно сделать, это взять различие в позиции y <div id=”progress-marker-start”></div>
и <div id=”progress-marker-end”></div>
и вычесть window.innerHeight
. Это даст нам общее количество пикселей, по которым мы хотим отслеживать прогресс прокрутки.
Используя App.vue
, который я определил, получается около 2011 пикселей.
Наконец, мы можем вычислить процентный прогресс, разделив window.scrollY - offsetFromTop
на общее количество. window.scrollY - offsetFromTop
отражает точку, когда начальный маркер находится над верхней частью текущего окна просмотра.
Собирая все это вместе, мы получаем следующую функцию. Добавьте его в Progress.vue
под methods
:
Последнее, что нужно сделать, - это вызвать этот метод, когда пользователь прокручивает страницу. Это не последний код - мы сделаем много улучшений, но самый простой способ проверить это - добавить прослушиватель событий в ловушку mounted
в Progress.vue
:
Оно работает! Мы внесем множество улучшений, но это отличное подтверждение концепции.
Пока что весь тег <script>
для Progress.vue
выглядит следующим образом:
Извлеките логику из компонента
Прежде чем мы добавим красивый пользовательский интерфейс, такой как у Tech Crunch, мы можем отделить логику прокрутки от компонента. В настоящее время, если кто-то хочет использовать наш компонент прогресса прокрутки, им нужно задействовать весь Vue - не идеально, если ваш сайт еще не использует Vue. Ни одна из написанных нами логических схем даже не использует API Vue.
Давайте выделим логику и сделаем <Progress />
тонкую оболочку Vue, соединяющую Vue и реальную бизнес-логику. Это дает нам возможность выпустить агонистическую библиотеку прогресса прокрутки, которую авторы затем могут интегрировать в свое приложение Vue / React / Angular / любое другое.
Я создам progress.ts
скрипт в каталоге components
и добавлю функции, описанные выше, внеся небольшие изменения в их сигнатуры:
Обновите Progress.vue
. Раздел <script>
в <Progress />
теперь представляет собой лишь тонкую оболочку логики в progress.ts
:
Все еще работает! Давайте добавим круговой интерфейс, который заполняется по мере прокрутки пользователей, как в Tech Crunch.
Немного SVG
Оказывается, вы не можете создать круг с постепенным заполнением, подобный тому, который мы хотим, только с помощью CSS. Или, по крайней мере, это не лучший инструмент для работы. Эта честь принадлежит SVG. Прежде чем интегрировать круговую анимацию SVG в приложение, нам нужно немного разобраться в SVG.
Базовый круг SVG нарисован следующим образом:
Это выглядит так:
Следующее интересующее нас свойство - stroke-dasharray
. Вы можете узнать больше об этом MDN, однако основная предпосылка заключается в том, что вы можете рисовать фигуру с помощью тире вместо сплошной границы. Вот некоторые примеры:
Для нашей цели мы хотим, чтобы stroke-dasharray
равнялось длине окружности нашего круга. Мы можем вычислить это программно: 2 * Math.PI * radius (который мы указали равным 50 пикселей). Для нашего круга получается 314 пикселей. Следующий фрагмент демонстрирует этот расчет:
Пока ничего не изменилось - по-прежнему красный кружок. Однако теперь мы можем воспользоваться другим свойством stroke-dashoffset
. Устанавливает смещение (от того места, где рисуется тире). В настоящее время наш круг состоит из одной большой черточки, установленной с использованием stroke-dasharray
, то есть полной окружности круга. Давайте попробуем разные числа для stroke-dasharray
и посмотрим, как все изменится:
Чем больше stroke-dashoffset
, тем меньше красный круг! По мере увеличения размера красная граница почти полностью исчезает. Итак, когда пользователь прокрутил 0% измеряемой области, мы хотим, чтобы stroke-dashoffset
был (окружность * прогресс), где прогресс находится между 0 и 1. Например:
Близко, но не совсем. Это смещает круг на 25%, рисуя оставшиеся 75%. На самом деле мы хотим наоборот - нарисовать только 25%. Таким образом, наш расчет выглядит следующим образом (окружность - (окружность * прогресс)):
Выглядит неплохо!
Интеграция SVG Circle и Logic
Теперь у нас есть все части, которые нужно рисовать кругом при прокрутке: процентное изменение и SVG, который мы можем обновить с помощью JavaScript. Давай сделаем это. Обновите раздел <template>
в Progress.vue
Затем обновите тег <style>
. Теперь это намного проще - теперь мы делаем большую часть стиля в SVG.
Далее у нас есть обновление до progress.ts
. Я добавил функцию updateCircle
, которая реализует рассмотренные выше вычисления и охватывает несколько крайних случаев. Я также внес небольшие изменения в getScrollPercentage
. Теперь готовый файл progress.ts
выглядит так:
Наконец, давайте воспользуемся функцией updateCircle
. Обновите раздел <script>
в Progress.vue
:
Я добавил вызов updateCircle
в обратном вызове прослушивателя событий scroll
. Я также удаляю прослушиватель событий на destroyed
, чтобы избежать потенциальной утечки памяти. Теперь это работает! При прокрутке вверх и вниз вы можете видеть, как обводка круга увеличивается и уменьшается, отражая ваш прогресс между двумя маркерами. Попробуйте демо здесь.
Последнее улучшение - requestAnimationFrame
Чтобы узнать, почему это полезно, изучите requestAnimationFrame
и пассивные прослушиватели событий - информации много. Есть и другие улучшения и оптимизации, о которых я мог бы написать в будущей статье.
В этой статье говорилось:
- некоторые предостережения в отношении
getClientBoundingRect
- использование Vue в качестве тонкой оболочки вокруг вашей бизнес-логики
- базовый SVG
Потенциальным улучшением было бы создание более привлекательного пользовательского интерфейса, чем просто красный кружок! Если кто-то что-то построит, поделитесь, пожалуйста, со мной. This CodePen - хорошее место для начала.
Исходный код доступен здесь, а рабочая демонстрация - здесь.
Изначально опубликовано в моем личном блоге.