Коротко — задача ложится идеально на ScrollTrigger от GSAP: для каждого ряда (“panel”) делаем «пин» секции на время, равное разнице высот внутреннего контента и видимой секции, и параллельно анимируем смещение внутреннего контента вверх (scrub: true). При обратной прокрутке всё проигрывается в обратном порядке автоматически.
Ниже минимальный рабочий пример (GSAP 3 + ScrollTrigger). Подставьте свои селекторы/HTML — идея та же.
HTML (структура):
- .panel — одна строка (высота = 100vh)
- в ней .left и .right
- в правой колонке .content-inner — блок с большей высотой
CSS (минимум):
- .panel { display:flex; height:100vh; overflow:hidden; }
- .right { flex:1; overflow:hidden; position:relative; }
- .content-inner { position:relative; }
JS (GSAP + ScrollTrigger):
1) подключите gsap и ScrollTrigger
2) выполните:
gsap.registerPlugin(ScrollTrigger);
document.querySelectorAll('.panel').forEach(panel => {
const content = panel.querySelector('.content-inner');
if (!content) return;
// delta — насколько контент больше видимой панели
const delta = content.scrollHeight - panel.clientHeight;
// если контент не превышает высоту панели — ничего не делаем
if (delta <= 0) return;
gsap.to(content, {
y: -delta, // сдвигаем контент вверх на разницу
ease: 'none',
scrollTrigger: {
trigger: panel,
start: 'top top', // когда верх панели вверху вьюпорта — pin
end: () => `+=${delta}`, // протяжённость прокрутки равна delta
scrub: true, // синхронизировано со скроллом (включая обратный)
pin: true, // "фиксируем" секцию на время анимации
anticipatePin: 1,
// markers: true // включите на время отладки
}
});
});
Объяснение почему это работает
- pin: true удерживает текущую секцию вверху экрана, поэтому страница «не пролистывает дальше», пока не проиграется анимация — именно этого вы хотели (а не «продолжения обычного скролла страницы»).
- scrub: true делает анимацию синхронной со скроллом и автоматически воспроизводит её в обратном порядке при прокрутке назад.
- end вычисляется как delta — то есть сколько пикселей прокрутки нужно, чтобы полностью показать скрытую часть контента.
Полезные советы / подводные камни
- Убедитесь, что .panel имеет фиксированную видимую высоту (обычно 100vh). Если панель сама растягивается, расчёт delta будет неверный.
- Если вы используете относительные единицы (vh) или динамичный контент — вызывайте ScrollTrigger.refresh() на resize / после загрузки изображений.
- Для отладки включите markers: true.
- Если у вас несколько подобных секций последовательно — они автоматически будут проигрываться одна за другой.
- Для адаптивности используйте ScrollTrigger.matchMedia, чтобы отключать пин/анимацию на мобильных экранах при необходимости.
Если хотите, могу конкретно подправить ваш jsfiddle (вставить код прямо туда) — пришлите ссылку ещё раз или дайте доступ, и я внесу правки.