Иногда дизайнеры создают сложную логику и фиксируют части интерфейса. А еще они красят разделы страницы в контрастные цвета. Как с этим справиться?
Вам поможет иммёрсер — джаваскрипт библиотека для замены фиксированных элементов при прокрутке страницы.
Иммёрсер вычисляет состояния один раз в момент инициализации. Затем он следит за позицией скролла и планирует перерисовку документа в следующем такте цикла событий через метод requestAnimationFrame. Скрипт изменяет свойство transform, это задействует графический ускоритель.
Иммёрсер написан на тайпскрипте. Всего 6.63Кб в сжатии gzip.
Корневой элемент иммёрсера — это родительский контейнер для ваших фиксированных блоков. Фактически они позиционированы абсолютно внутри фиксированного корневого элемента. Слои — это разделы страницы, окрашенные в разные цвета. Еще вы наверняка захотите добавить навигацию по разделам, выделяющую активный раздел.
Через npm:
Через yarn:
Или если вы хотите использовать иммёрсер в браузере как глобальную переменную:
npm install immerser
yarn add immerser
<script src="https://unpkg.com/immerser@5.1.0/dist/immerser.min.js"></script>Сначала настройте свой фиксированный контейнер как корневой элемент иммёрсера, добавив атрибут data-immerser
Затем расположите в нем абсолютно позиционированные дочерние элементы и добавьте каждому атрибут data-immerser-solid="solid-id" с идентификатором блока.
Добавьте каждому слою атрибут data-immerser-layer. Передайте конфигурацию в виде JSON в каждый слой с помощью атрибута data-immerser-layer-config='{"solid-id": "classname-modifier"}'. Также вы можете передать конфигурацию всех слоев массивом в параметре solidClassnameArray настроек. Конфигурация должна содержать описание классов для блоков, когда они находятся поверх слоя.
Так же вы можете добавить элемент с атрибутом data-immerser-pager для создания навигации.
<div class="fixed" data-immerser>
<div class="fixed__pager pager" data-immerser-pager data-immerser-solid="pager"></div>
<a href="#reasoning" class="fixed__logo logo" data-immerser-solid="logo">иммёрсер</a>
<div class="fixed__menu menu" data-immerser-solid="menu">
<a href="#reasoning" class="menu__link">Зачем нужен иммёрсер</a>
<a href="#how-to-use" class="menu__link">Как пользоваться</a>
<a href="#how-it-works" class="menu__link">Принцип работы</a>
<a href="#options" class="menu__link">Настройки</a>
<a href="#recipes" class="menu__link">Рецепты</a>
</div>
<div class="fixed__language language" data-immerser-solid="language">
<a href="/" class="language__link">english</a>
<a href="/ru.html" class="language__link">по-русски</a>
</div>
<div class="fixed__about about" data-immerser-solid="about">
© 2025 — Владимир Лысов, Челябинск, Россия
<a href="https://github.com/dubaua/immerser">гитхаб</a>
<a href="mailto:dubaua@gmail.com">dubaua@gmail.com</a>
</div>
</div>
<div data-immerser-layer data-immerser-layer-config='{"logo": "logo--contrast", "pager": "pager--contrast", "social": "social--contrast"}' id="reasoning"></div>
<div data-immerser-layer data-immerser-layer-config='{"menu": "menu--contrast", "about": "about--contrast"}' id="how-to-use"></div>
<div data-immerser-layer data-immerser-layer-config='{"logo": "logo--contrast", "pager": "pager--contrast", "social": "social--contrast"}' id="how-it-works"></div>
<div data-immerser-layer data-immerser-layer-config='{"menu": "menu--contrast", "about": "about--contrast"}' id="options"></div>
<div data-immerser-layer data-immerser-layer-config='{"logo": "logo--contrast", "pager": "pager--contrast", "social": "social--contrast"}' id="recipes"></div>Добавьте стили цвета текста и фона на ваши блоки и слои с помощью классов, переданных в дата-атрибут или настройки. В примере я использую методологию БЭМ.
.fixed {
position: fixed;
top: 2em;
bottom: 3em;
left: 3em;
right: 3em;
z-index: 1;
}
.fixed__pager {
position: absolute;
top: 50%;
left: 0;
transform: translate(0, -50%);
}
.fixed__logo {
position: absolute;
top: 0;
left: 0;
}
.fixed__menu {
position: absolute;
top: 0;
right: 0;
}
.fixed__language {
position: absolute;
bottom: 0;
left: 0;
}
.fixed__about {
position: absolute;
bottom: 0;
right: 0;
}
.pager,
.logo,
.menu,
.language,
.about {
color: black;
}
.pager--contrast,
.logo--contrast,
.menu--contrast,
.language--contrast,
.about--contrast {
color: white;
}Добавьте иммёрсер в код и создайте экземпляр с настройками.
// Вам не нужно импортировать иммёрсер,
// если вы используете его в браузере как глобальную переменную
import Immerser from 'immerser';
const immerserInstance = new Immerser({
// будет переопределена настройками,
// переданными в атрибут data-immerser-layer-config каждого слоя
solidClassnameArray: [
{
logo: 'logo--contrast-lg',
pager: 'pager--contrast-lg',
language: 'language--contrast-lg',
},
{
pager: 'pager--contrast-only-md',
menu: 'menu--contrast',
about: 'about--contrast',
},
{
logo: 'logo--contrast-lg',
pager: 'pager--contrast-lg',
language: 'language--contrast-lg',
},
{
logo: 'logo--contrast-only-md',
pager: 'pager--contrast-only-md',
language: 'language--contrast-only-md',
menu: 'menu--contrast',
about: 'about--contrast',
},
{
logo: 'logo--contrast-lg',
pager: 'pager--contrast-lg',
language: 'language--contrast-lg',
},
],
hasToUpdateHash: true,
fromViewportWidth: 1024,
pagerLinkActiveClassname: 'pager__link--active',
scrollAdjustThreshold: 50,
scrollAdjustDelay: 600,
on: {
init(immerser) {
// колбек события инициализации
},
bind(immerser) {
// колбек события привязки к документу
},
unbind(immerser) {
// колбек события отвязки от документа
},
destroy(immerser) {
// колбек события уничтожения
},
activeLayerChange(activeIndex, immerser) {
// колбек события смены активного слоя
},
layersUpdate(layersProgress, immerser) {
// колбек события обновления прогресса слоёв
},
},
});
Сначала иммёрсер собирает информацию о слоях, блоках, окне и документе. Затем скрипт создает карту состояний для каждого слоя. Карта содержит размеры слоя, блоков и позиции их пересечений при скролле.
После сбора информации скрипт копирует все блоки в маскирующий контейнер и применяет к каждому классы, переданные в настройках. Если вы добавили навигацию, то иммёрсер создаст ссылки на каждый слой.
Затем иммёрсер подписывается на события скролла документа и изменения размеров окна.
При скролле иммёрсер двигает маскирующий контейнер так, чтобы показывать часть каждой группы блоков для каждого слоя под ними. При изменении размеров окна скрипт рассчитает карту состояний заново.
Вы можете передать настройки параметром функции конструктора или дата-атрибутом в документе. Дата-аттрибут обрабатывается последним, поэтому он переопределит настройки, переданные в конструктор.
| параметр | тип | значение по умолчанию | описание |
|---|---|---|---|
| solidClassnameArray | array | [] | Массив настроек слоев. Конфигурация, переданная в data-immerser-layer-config перезапишет эту настройку для соответствующего слоя. Пример конфигурации показан выше |
| fromViewportWidth | number | 0 | Минимальная ширина окна для инициализации иммёрсера |
| pagerThreshold | number | 0.5 | Насколько должен следующий слой быть видим в окне, чтобы он стал активен в навигации |
| hasToUpdateHash | boolean | false | Флаг, контролирующий обновление хеша страницы |
| scrollAdjustThreshold | number | 0 | Дистанция до верха или низа окна браузера в пикселях. Если текущая дистанция меньше переданного значения, то скрипт подстроит положение скролла |
| scrollAdjustDelay | number | 600 | Сколько ждать бездействия пользователя, чтобы начать подстройку скролла |
| pagerLinkActiveClassname | string | pager-link-active | Применяется, к каждой ссылке пейджера, ссылающуюся на активный слой |
| isScrollHandled | boolean | true | Подписывается на событие прокрутки, если включено. Выключите, если используете внешний контроллер скролла |
| debug | boolean | false | Включает логирование предупреждений и ошибок. По умолчанию true в режиме разработки, иначе false |
| on | object | {} | Начальные обработчики событий, сгруппированные по имени события |
На события можно подписаться через поле on в настройках конструктора или с помощью вызова метода on или once у экземпляра иммёрсера.
| событие | аргументы | описание |
|---|---|---|
| init | immerser: Immerser | Вызывается после инициализации |
| bind | immerser: Immerser | Вызывается после привязки к DOM |
| unbind | immerser: Immerser | Вызывается после отвязки от DOM |
| destroy | immerser: Immerser | Вызывается после уничтожения экземпляра |
| activeLayerChange | layerIndex: number, immerser: Immerser | Вызывается при смене активного слоя |
| layersUpdate | layersProgress: number[], immerser: Immerser | Вызывается при каждом обновлении скролла |
| название | тип | описание |
|---|---|---|
| debug | property | Управляет тем, логирует ли иммёрсер предупреждения и ошибки |
| bind | method | Клонирует разметку, навешивает подписчики и запускает логику |
| unbind | method | удаляет сгенерированную разметку и подписчики, сохраняя экземпляр пригодным к повторному использованию |
| destroy | method | Полностью уничтожает иммёрсер: отключает его, удаляет слушатели, возвращает оригинальную разметку и очищает внутреннее состояние |
| render | method | Пересчитывает размеры и перерисовывает маски |
| syncScroll | method | Обновляет иммёрсер при внешнем управлении скроллом (требуется флаг isScrollHandled = false) |
| on | method | Регистрирует постоянный обработчик события иммёрсера |
| once | method | Регистрирует одноразовый обработчик события, который удаляется после первого вызова |
| off | method | Удаляет конкретный обработчик для указанного события иммёрсера |
| activeIndex | getter | Текущий индекс активного слоя, рассчитанный по позиции скролла |
| isBound | getter | Показывает, активен ли сейчас иммёрсер (разметка склонирована, подписчики навешаны) |
| rootNode | getter | Корневой элемент, к которому привязан иммёрсер |
| layerProgressArray | getter | Прогресс каждого слоя (0–1), показывающий, насколько слой виден в окне браузера |
Вы уже знаете, что иммёрсер клонирует элементы. Подписчики событий и данные, привязанные к нодам, не клонируются вместе с элементом. К счастью, вы можете разметить иммёрсер самостоятельно. Для этого разместите внутри корневого элемента маскирующие контейнеры для блоков по числу слоев. В таком случае скрипт не будет клонировать элементы. Подписчики и реактивная логика останутся нетронутыми. В примере на этой странице я создаю подписчик на клик по смайлу справа до инициализации.
<div class="fixed" data-immerser>
<div data-immerser-mask>
<div data-immerser-mask-inner>
<!-- ваша разметка -->
</div>
</div>
<div data-immerser-mask>
<div data-immerser-mask-inner>
<!-- ваша разметка -->
</div>
</div>
</div>Если вы наведете мышь на элемент, находящийся на границе слоев, то псевдоселектор :hover сработает только на одну часть. Чтобы наведение сработало на все клоны элемента, задайте идентификатор наведения в атрибуте data-immerser-synchro-hover="hoverId". При наведении мыши на такой элемент, ко всем его клонам добавится класс _hover. Стилизуйте по этому селектору вместе с псевдоселектором :hover, чтобы добиться нужного эффекта.
a:hover,
a._hover {
color: magenta;
}Иммёрсер не отслеживает изменения документа, если вы динамически добавляете или удаляете ноды. Если вы меняете высоту документа, и хотите, чтобы иммёрсер пересчитал и перерисовал блоки, вызовите метод render у экземпляра иммёрсера.
// проведите любые манипуляции, меняющие поток документа
document.appendChild(someNode);
document.removeChild(anotherNode);
// затем укажите иммёрсеру перерисовать штуки
immerserInstance.render();
Если прокруткой управляет кастомный движок, например, Locomotive Scroll, выключите обработчик скролла иммёрсера флагом isScrollHandled=false и вызывайте метод syncScroll при каждом обновлении позиции движком. Иммёрсер только перерисует маски и не будет вешать свой обработчик. Иммёрсер не оптимизирует вызовы в этом режиме — оптимизация производительности остается на стороне клиента.
import Immerser from 'immerser';
const immerserInstance = new Immerser({
// выключаем обработку скролла иммёрсером под внешний движок
isScrollHandled: false,
});
customScrollEngine.on('scroll', () => {
// подпишитесь на событие скролла движка и запустите синхронизацию иммёрсера
immerserInstance.syncScroll();
});
Ядро библиотеки было написано в 2019 году и существенно доработано в 2022 году до появления программирования с ИИ. В более поздних итерациях ИИ использовался как вспомогательный инструмент для инфраструктурных задач, обновления документации и генерации обслуживающего кода.
Для меня ИИ — это инструмент наравне с линтерами, сборщиками и другими средствами ускорения и упрощения работы. Я ленивый, и моя лень толкает меня на изобретения.
Я использую ИИ открыто и считаю важным прямо об этом говорить, потому что для кого-то это может иметь решающее значение.