Создание скроллбара с эффектом загрузки на Vue.js

Перевод статьи Creating a Scroll-Based Progress Bar in Vue.

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

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

Если вам стало интересно, вы можете ознакомиться с демкой на CodePen, а так же ниже в статье вы найдете информацию о том как это можно реализовать, .

Шаг 1: Создаем приложение Vue

Для реализации нашей задумки я предлагаю использовать мгновенное прототипирование Vue. Создаем файл Vue.app и добавляем в него следующее:

<template>
<div id="app">
<div class="card">
<progress-bar :value="progress"/>
<div class="text-section">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cupiditate architecto voluptatum, laboriosam quisquam quod minus molestias. Rem ut quidem, corrupti nihil molestiae deserunt, iusto velit unde atque mollitia, eum ad maiores exercitationem. Soluta harum sit cupiditate eos, commodi itaque nihil, beatae dolorem ducimus, repudiandae, vero quo corporis sed laborum at maxime dicta dolores perferendis! Possimus repellat velit iste quod recusandae suscipit vitae ex soluta nostrum animi saepe eius itaque, voluptas, sapiente minima quo culpa explicabo necessitatibus distinctio. Veritatis amet tempora, consectetur molestias optio eveniet laudantium, tenetur aspernatur nobis ratione sit hic in impedit quod deserunt recusandae atque, ipsam molestiae sequi!
</div>
</div>
</div>
</template>

Здесь мы выводим небольшой блок с некоторым количеством текста, а так же компонент прогресс бара который мы ещё не создали.

В секцию вставляем инициализацию нашего приложения:

<script>
import ProgressBar from './ProgressBar'

export default {
components: { ProgressBar },
data () {
return {
progress: 0
}
}
}
</script>

Это стандартная инициализация Vue экземпляра, здесь мы добавляем компонент прогресс бара, а так же указываем параметр в котором мы будем хранить прогресс чтения пользователя от 0 до 1.

Теперь переходим к секции .

<style>
* {
box-sizing: border-box;
}

body {
margin: 0;
padding: 1px 0 0;
width: 100vw;
height: 100vh;
background: #6B5CA5;
}

html {
padding: 0;
margin: 0;
}

.card {
border-radius: 3px;
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
line-height: 1.5;
font-size: 18px;
color: #444;
width: 300px;
margin: 10px auto;
height: 150px;
margin-top: 10px;
display: flex;
flex-direction: column;
background: #FFFFFF;
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
}

.text-section {
height: 100%;
max-height: 100%;
padding: 0 10px 10px;
overflow: scroll;
-webkit-overflow-scrolling: touch;
}
</style>

Немного стилей что бы оформить все это безобразие.

Шаг 2: Создание компонента ProgressBar.vue

Создаем файл ProgressBar.vue и добавляем в него следующий код:

<template>
<div class="progress-bar">
<div class="filled-bar"></div>
<span class="percentage-text">
Progress: {{ percentageText }}
</span>
</div>
</template>

Этот компонент состоит из 3 частей:

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

и будут спозиционированы абсолютно. И конечно же у .percentage-text будет больший чем у

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

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

Теперь напишем немного кода в секцию :

<script>
export default {
props: {
value: {
type: Number,
default: 0
}
},

computed: {
percentageText () {
return `${Math.round(this.value * 100)}%`
}
}
}
</script>

Обратите внимание как мы получаем текущее значение прогресса через переменную .

Используя вычисляемое свойство мы превращаем дробное значение в целые проценты, то есть если у нас прогресс равен 0.05, то мы получим 5%.

Теперь давайте добавим немного стилей:

<style scoped>
.progress-bar {
position: relative;
height: 30px;
width: 100%;
border-bottom: 1px solid #f0f0f0;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
border-radius: 3px 3px 0 0;
}

.percentage-text {
position: absolute;
top: 50%;
left: 50%;
z-index: 2;
font-size: 14px;
transform: translate3d(-50%, -50%, 0);
}

.filled-bar {
position: absolute;
top: 0;
left: 0;
z-index: 2;
height: 100%;
width: 100%;
background: #eef0ff;
}
</style>

Если у вас открыт браузер при выполнении этих операций, то вы скорее всего увидите что то подобное:

Теперь нам необходимо сделать 2 вещи что бы заставить всё это работать:

  • Поменять положение заполненного контейнера исходя из значения параметра;
  • Посчитать прогресс скролла пользователя в процентах, и передать его в параметр .

Шаг 3: изменение положения закрашенного блока

Мы будем использовать для изменения положения закрашенного блока прогресс бара.

Здесь необходимо обратить внимание на пару деталей. Во-первых нам необходимо менять только x координату, а остальные оставить 0. Во-вторых мы используем вместо по соображениям производительности — чтобы позволить GPU обрабатывать его.

Напрямую в указываем необходимые стили:

<div
class="filled-bar"
:style="{ transform: `translate3d(-${(1 - value) * 100}%, 0, 0)` }"
></div>

Теперь вы можете проверить что прогресс бар работает корректно, меняя дефолтное значение . Поставьте например 0.5 и вы увидите это:

Шаг 4: подсчет текущего значения скролла пользователя

Чтобы получить это значение, мы должны сначала прослушать событие @scroll в , а затем разделить текущую позицию прокрутки (в пикселях) на полную высоту текстового блока.

В App.vue добавляем и к секции вот так:

<div
class="text-section"
ref="text"
@scroll="onScroll"
>

Нам необходимо добавить к элементу, что бы получить доступ к позиции скролла и его высоте.

Теперь напишем метод :

methods: {
onScroll () {
const progress = this.$refs.text.scrollTop / (this.$refs.text.scrollHeight - this.$refs.text.clientHeight)
if (progress > 1) {
this.progress = 1
} else if (progress < 0) {
this.progress = 0
} else {
this.progress = progress
}
}
}

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

Владимир Бандуристов

Written by

Занимаюсь созданием бизнес-сайтов, интернет-магазинов, корпоративных сайтов, версткой, доработкой сайтов, их наполнением и продвижением.