Библиотека Locomotive Scroll переопределяет стандартное поведение
прокрутки браузера, создавая виртуальный скролл-контейнер. Это означает,
что нативный scrollTop больше не является источником истины
— позиция прокрутки хранится внутри экземпляра скролла.
Индикатор прокрутки (scroll indicator) должен синхронизироваться
именно с этим виртуальным состоянием, а не с
window.scrollY.
Ключевые данные, необходимые для построения индикатора:
scroll.instance.scroll.yscroll.instance.limit.yLocomotive Scroll предоставляет событие scroll, которое
вызывается при каждом обновлении позиции.
const scroll = new LocomotiveScroll({
el: document.querySelector('[data-scroll-container]'),
smooth: true
});
scroll.on('scroll', (instance) => {
const current = instance.scroll.y;
const limit = instance.limit.y;
});
Здесь:
instance.scroll.y — текущая позицияinstance.limit.y — максимальная длина прокруткиПроцент прокрутки вычисляется простой формулой:
const progress = current / limit;
Для перевода в проценты:
const percent = progress * 100;
Это значение используется для визуального отображения состояния прокрутки.
Минимальная разметка:
<div class="scroll-indicator">
<div class="scroll-indicator__progress"></div>
</div>
Пример вертикального индикатора:
.scroll-indicator {
position: fixed;
top: 0;
right: 20px;
width: 4px;
height: 100vh;
background: rgba(0, 0, 0, 0.1);
}
.scroll-indicator__progress {
width: 100%;
height: 0%;
background: #000;
transition: height 0.1s linear;
}
Обновление высоты индикатора:
const progressBar = document.querySelector('.scroll-indicator__progress');
scroll.on('scroll', (instance) => {
const progress = instance.scroll.y / instance.limit.y;
progressBar.style.height = `${progress * 100}%`;
});
Аналогичная реализация, но по ширине:
.scroll-indicator {
position: fixed;
bottom: 0;
left: 0;
height: 4px;
width: 100%;
}
.scroll-indicator__progress {
height: 100%;
width: 0%;
}
progressBar.style.width = `${progress * 100}%`;
Для повышения производительности предпочтительно использовать
transform, так как он не вызывает перерасчет layout.
.scroll-indicator__progress {
transform-origin: top;
transform: scaleY(0);
}
progressBar.style.transform = `scaleY(${progress})`;
Для горизонтального варианта:
transform-origin: left;
Locomotive Scroll уже использует сглаживание, но индикатор можно дополнительно анимировать:
.scroll-indicator__progress {
transition: transform 0.08s ease-out;
}
Создание эффекта отставания индикатора:
let displayedProgress = 0;
scroll.on('scroll', (instance) => {
const target = instance.scroll.y / instance.limit.y;
displayedProgress += (target - displayedProgress) * 0.1;
progressBar.style.transform = `scaleY(${displayedProgress})`;
});
Этот подход создает более плавную, “инерционную” анимацию.
HTML:
<div class="scroll-indicator">
<div class="scroll-indicator__progress"></div>
<span class="scroll-indicator__text">0%</span>
</div>
Jav * aScript:
const text = document.querySelector('.scroll-indicator__text');
scroll.on('scroll', (instance) => {
const progress = instance.scroll.y / instance.limit.y;
const percent = Math.round(progress * 100);
text.textContent = `${percent}%`;
});
Иногда требуется показывать прогресс внутри отдельного блока, а не всей страницы.
Для этого используется отслеживание элементов через
data-scroll и data-scroll-id.
HTML:
<section data-scroll data-scroll-id="section1">
</section>
Jav * aScript:
scroll.on('scroll', (instance) => {
const section = instance.currentElements['section1'];
if (section) {
const progress = section.progress;
progressBar.style.transform = `scaleY(${progress})`;
}
});
Здесь progress — значение от 0 до 1 внутри элемента.
HTML:
<svg class="progress-ring" width="60" height="60">
<circle
class="progress-ring__circle"
stroke="black"
stroke-width="4"
fill="transparent"
r="26"
cx="30"
cy="30"
/>
</svg>
Jav * aScript:
const circle = document.querySelector('.progress-ring__circle');
const radius = circle.r.baseVal.value;
const circumference = 2 * Math.PI * radius;
circle.style.strokeDasharray = circumference;
circle.style.strokeDashoffset = circumference;
scroll.on('scroll', (instance) => {
const progress = instance.scroll.y / instance.limit.y;
const offset = circumference - progress * circumference;
circle.style.strokeDashoffset = offset;
});
Для обратного направления:
const inverted = 1 - progress;
progressBar.style.transform = `scaleY(${inverted})`;
При изменении размера окна лимит прокрутки меняется. Требуется обновление:
window.addEventListener('resize', () => {
scroll.update();
});
if (progress <= 0.01 || progress >= 0.99) {
progressBar.style.opacity = 0;
} else {
progressBar.style.opacity = 1;
}
Индикатор можно использовать как источник данных:
Пример:
document.body.style.backgroundColor = `rgba(0,0,0,${progress})`;
Ключевые рекомендации:
transform, а не
width/heightscroll.onconsole.log в обработчикеВыделение логики индикатора в отдельный модуль:
class ScrollIndicator {
constructor(scroll, element) {
this.scroll = scroll;
this.el = element;
this.progress = 0;
this.init();
}
init() {
this.scroll.on('scroll', this.update.bind(this));
}
update(instance) {
const target = instance.scroll.y / instance.limit.y;
this.progress += (target - this.progress) * 0.1;
this.el.style.transform = `scaleY(${this.progress})`;
}
}
Использование:
new ScrollIndicator(scroll, document.querySelector('.scroll-indicator__progress'));
Такие решения позволяют не только отображать позицию, но и усиливать восприятие структуры страницы.