Плавная смена фона при скролле в JavaScript без резких переходов
Как сделать плавную анимацию смены статичного фонового изображения при скролле: используйте слои с opacity, CSS transitions, Vanilla JS с requestAnimationFrame или GSAP ScrollTrigger. Примеры кода для fade-эффекта без лагов.
Как сделать плавную смену статичного фонового изображения при скролле в JavaScript без резких переходов?
Текущий код меняет фон резко при достижении определенных позиций скролла:
// Смена фона при скролле
const back = document.getElementById('back');
const pathImg_1 = "/img/back/back.avif";
const pathImg_2 = "/img/back/back_2.jpg";
const pathImg_3 = "/img/back/back.avif";
window.addEventListener('scroll', ()=>{
const windowHight = window.innerHeight;
const hight_1box = document.getElementById('main').offsetHeight;
const header = document.getElementById('header').offsetHeight;
const mainBox = document.getElementById('main');
const hight_2box = document.getElementById('scills_box').offsetHeight;
const hight_3box = document.getElementById('about').offsetHeight;
let scrollPosition = window.scrollY;
const firstShift = hight_1box - header - (header*0.2);
const secondShift = hight_1box+hight_2box+hight_3box + header;
if(firstShift<=scrollPosition){
back.style.backgroundImage = `url(${pathImg_2})`;
mainBox.style.backgroundColor = 'black';
}
if(secondShift<=scrollPosition){
back.style.backgroundImage = `url(${pathImg_3})`;
}
if(secondShift>=scrollPosition){
back.style.backgroundImage = `url(${pathImg_2})`;
}
if(firstShift>=scrollPosition){
back.style.backgroundImage = `url(${pathImg_1})`;
mainBox.style.backgroundColor = 'rgba(0, 0, 0, 0.26)';
}
})
Логика: блоки пролистываются поверх статичного фона. После черного блока фон должен плавно смениться на новый, видимый через прозрачный блок. Рекомендуйте библиотеки (например, GSAP, AOS) или варианты кода с CSS transitions/opacity для smooth background change on scroll.
Для плавной смены статичного фонового изображения при скролле замените резкую установку backgroundImage на два наложенных слоя с плавным изменением opacity через CSS transitions. В вашем коде просто добавьте второй и третий div с position: fixed, transition: opacity 0.6s ease и меняйте их прозрачность по прогрессу скролла — это создаст fade-effect без скачков. Лучший вариант — библиотека GSAP с ScrollTrigger, где анимация привязана к позиции прокрутки: gsap.to('#back2', {opacity: 1, scrollTrigger: {trigger: '#main', scrub: true}}).
Содержание
- Проблемы вашего кода и почему фон меняется резко
- Базовый подход: два слоя с CSS opacity
- Vanilla JS решение с requestAnimationFrame
- GSAP ScrollTrigger для идеальной анимации при скролле
- IntersectionObserver и альтернативы вроде AOS
- Оптимизация и производительность
- Источники
- Заключение
Проблемы вашего кода и почему фон меняется резко
Ваш скрипт работает по принципу if-условий: как только scrollPosition пересекает порог вроде firstShift, фон мгновенно переключается на pathImg_2. Это создает резкий скачок, потому что backgroundImage не анимируется по умолчанию — браузер просто подставляет новое изображение. Плюс, логика с if(secondShift>=scrollPosition) конфликтует: она может перезаписывать предыдущие изменения хаотично.
А что если трафик на сайт вырастет, и на мобильных скролл будет дерганым? Такие простые слушатели window.scroll вызываются 60+ раз в секунду, жрут CPU и не дают плавности. Блоки вроде #main с прозрачным backgroundColor: rgba(0,0,0,0.26) пролистываются поверх фиксированного #back, но без перехода это выглядит как обрезка фото ножницами.
Решение простое: не меняйте изображение напрямую. Используйте наложение слоев — нижний фон статичный, верхний fade-in’ится. Это стандарт для анимации при скролле, где opacity анимируется от 0 до 1 за 0.5-0.6s.
Базовый подход: два слоя с CSS opacity
Начните с HTML: добавьте несколько фиксированных фонов.
<div id="back-wrapper" style="position: fixed; top:0; left:0; width:100%; height:100%; z-index: -1; overflow: hidden;">
<div id="back1" style="position: absolute; top:0; left:0; width:100%; height:100%; background-image: url('/img/back/back.avif'); background-size: cover; opacity: 1; transition: opacity 0.6s ease;"></div>
<div id="back2" style="position: absolute; top:0; left:0; width:100%; height:100%; background-image: url('/img/back/back_2.jpg'); background-size: cover; opacity: 0; transition: opacity 0.6s ease;"></div>
<div id="back3" style="position: absolute; top:0; left:0; width:100%; height:100%; background-image: url('/img/back/back.avif'); background-size: cover; opacity: 0; transition: opacity 0.6s ease;"></div>
</div>
CSS transition на opacity сделает fade плавным — браузер сам интерполирует. В JS просто:
const back1 = document.getElementById('back1');
const back2 = document.getElementById('back2');
const back3 = document.getElementById('back3');
// ... ваши расчеты firstShift, secondShift
window.addEventListener('scroll', () => {
let scrollPosition = window.scrollY;
if (scrollPosition >= firstShift) {
back1.style.opacity = 0;
back2.style.opacity = 1;
}
if (scrollPosition >= secondShift) {
back2.style.opacity = 0;
back3.style.opacity = 1;
}
});
Почему это работает лучше? Opacity — анимируемое свойство, в отличие от backgroundImage. Но на длинных страницах scroll-событие все равно тормозит. Переходим к оптимизации.
Vanilla JS решение с requestAnimationFrame
Чтобы избежать лагов, добавьте throttle через requestAnimationFrame — он синхронизирует с 60fps браузера. Плюс, вычисляйте прогресс плавно: не бинарный if, а progress = Math.min(scrollY / диапазон, 1) для градиентного fade.
Вот полный рефакторинг вашего кода:
let ticking = false;
const backs = {
1: document.getElementById('back1'),
2: document.getElementById('back2'),
3: document.getElementById('back3')
};
function updateBackground() {
const scrollY = window.scrollY;
const windowHeight = window.innerHeight;
// ... ваши hight_1box и т.д.
const firstShift = hight_1box - header - (header * 0.2);
const secondShift = hight_1box + hight_2box + hight_3box + header;
// Плавный прогресс для первого перехода
const progress1 = Math.max(0, Math.min(1, (scrollY - firstShift + windowHeight * 0.2) / (windowHeight * 0.5)));
backs[1].style.opacity = 1 - progress1;
backs[2].style.opacity = progress1;
// Второй переход
const progress2 = Math.max(0, Math.min(1, (scrollY - secondShift + windowHeight * 0.2) / (windowHeight * 0.5)));
backs[2].style.opacity = progress2 * (1 - progress2); // Кросс-фейд
backs[3].style.opacity = progress2;
ticking = false;
}
window.addEventListener('scroll', () => {
if (!ticking) {
requestAnimationFrame(updateBackground);
ticking = true;
}
});
Этот код из ru.stackoverflow дает настоящий плавный скролл без библиотек. Прозрачные блоки вроде #main теперь идеально показывают новый фон сквозь fade.
GSAP ScrollTrigger для идеальной анимации при скролле
Хотите профессиональный уровень? GSAP — король анимации смены фона. Подключите <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script> и ScrollTrigger.
Установка: gsap.registerPlugin(ScrollTrigger);
Код для вашей страницы:
gsap.registerPlugin(ScrollTrigger);
gsap.timeline({
scrollTrigger: {
trigger: "#main",
start: "top bottom",
end: "#scills_box bottom",
scrub: 1, // Привязка к скорости скролла
ease: "power2.inOut"
}
})
.to("#back1", {opacity: 0, duration: 1})
.to("#back2", {opacity: 1, duration: 1}, 0);
gsap.timeline({
scrollTrigger: {
trigger: "#about",
start: "top bottom",
end: "bottom bottom",
scrub: 1
}
})
.to("#back2", {opacity: 0, duration: 1})
.to("#back3", {opacity: 1, duration: 1}, 0);
Scrub делает переход пропорциональным скроллу — скроллишь медленно, анимация растягивается. Для трех изображений используйте одну timeline с позициями: .to("#back2", {}, 0.5).to("#back3", {}, 1). Примеры в GSAP форуме показывают, как это бьет vanilla на headless.
IntersectionObserver и альтернативы вроде AOS
Если GSAP overkill, попробуйте IntersectionObserver — нативный API для появления элементов при скролле. Добавьте data-bg к блокам:
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const nextBg = entry.target.dataset.bg;
const current = document.querySelector('.back-current');
const next = document.querySelector('.back-next');
next.style.backgroundImage = `url(${nextBg})`;
next.style.opacity = 1;
current.style.opacity = 0;
current.classList.remove('back-current');
next.classList.add('back-current');
}
});
}, { threshold: 0.5 });
observer.observe(document.getElementById('main'));
observer.observe(document.getElementById('scills_box'));
AOS (Animate On Scroll) проще: подключите CDN, добавьте data-aos="fade-up" к блокам, но для фона комбинируйте с opacity. jQuery-вариант из gravitsapa подойдет для legacy: $(window).scroll(function(){ if(scroll >= 500) $('.back-next').animate({opacity:1},400); });.
Оптимизация и производительность
Не забывайте: preload изображений (<link rel="preload" as="image" href="/img/back/back_2.jpg">), чтобы fade не ждал загрузки. Тестируйте на мобильных — fixed backgrounds иногда глючат в iOS. Для parallax добавьте transform: translateY в GSAP.
Если страницы длинные, комбинируйте с will-change: opacity в CSS. И да, throttle спасет от 1000+ вызовов scroll в секунду на тач-устройствах.
Источники
- qna.habr.com/q/309680
- ru.stackoverflow.com/questions/717611
- gravitsapa.info/…jquery
- gsap.com/…scroll
- webgolovolomki.com/…skrolle
- ru.stackoverflow.com/…prokrutke
- dejurka.ru/…pure-css
- gsap.com/…on-scroll
Заключение
Плавная смена фона при скролле достигается opacity-слоями с transitions или GSAP ScrollTrigger — забудьте про резкие if. Начните с vanilla JS для простоты, перейдите на GSAP для wow-эффекта. Тестируйте на реальных устройствах, и ваша страница полетит. Готовый код сэкономит часы — копируйте и адаптируйте под свои пороги.