Прокрутка с фиксацией на элементах (как в каруселях или страницах-лендингах) и анимации, привязанные к скроллу — два современных CSS-инструмента для создания насыщенного UX без JavaScript.
/* Контейнер: включаем snap */
.carousel {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory; /* Ось и строгость */
scroll-padding: 16px; /* Отступ от края до точки привязки */
gap: 12px;
}
/* Элементы: задаём точку привязки */
.slide {
flex-shrink: 0;
width: 300px;
scroll-snap-align: start; /* start | center | end */
scroll-snap-stop: always; /* Запрет перепрыгивать несколько элементов */
}scroll-snap-type: x mandatory; /* Горизонталь, обязательная привязка */
scroll-snap-type: y mandatory; /* Вертикаль, обязательная */
scroll-snap-type: y proximity; /* Привязка только когда близко */
scroll-snap-type: both mandatory; /* Обе оси */mandatory — всегда останавливается на snap-точке. Применяется для чётких переходов между секциями.
proximity — snap только если близко к точке. Мягче, подходит для длинных страниц.
html {
scroll-snap-type: y mandatory;
}
section {
height: 100vh;
scroll-snap-align: start;
}CSS scroll-driven animations (2023) позволяют привязать анимацию к прокрутке:
/* Прогресс-бар чтения */
@keyframes progress {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
.reading-progress {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: #7b2ff7;
transform-origin: left;
animation: progress linear;
animation-timeline: scroll(); /* Привязываем к скроллу */
animation-range: 0% 100%; /* Начало и конец */
}const progress = document.querySelector('.progress')
const animation = progress.animate(
[
{ transform: 'scaleX(0)' },
{ transform: 'scaleX(1)' },
],
{
timeline: new ScrollTimeline({
source: document.documentElement,
axis: 'block',
}),
fill: 'both',
}
)@keyframes fade-in {
from { opacity: 0; transform: translateY(24px); }
to { opacity: 1; transform: translateY(0); }
}
.reveal-on-scroll {
animation: fade-in linear both;
animation-timeline: view();
animation-range: entry 0% entry 40%;
/* Анимация проигрывается когда элемент входит в viewport */
}// Плавная прокрутка к элементу
element.scrollIntoView({
behavior: 'smooth',
block: 'start', // 'start' | 'center' | 'end' | 'nearest'
inline: 'nearest',
})
// Программный скролл контейнера
container.scrollTo({
left: targetX,
behavior: 'smooth',
})Симуляция scroll snap: вычисление ближайшей точки привязки и плавная анимация к ней
// Scroll Snap — горизонтальная карусель с CSS и JS-управлением
const style = document.createElement('style')
style.textContent = `
* { box-sizing: border-box; }
body { font-family: sans-serif; padding: 16px; }
.carousel-wrap { margin-bottom: 16px; }
.carousel {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
gap: 12px;
padding: 8px;
border: 2px solid #e2e8f0;
border-radius: 8px;
scrollbar-width: none;
}
.carousel::-webkit-scrollbar { display: none; }
.slide {
flex-shrink: 0;
width: 200px;
height: 120px;
border-radius: 8px;
scroll-snap-align: start;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
font-weight: 700;
color: white;
}
.controls { display: flex; gap: 8px; margin-top: 8px; }
.dot {
width: 10px; height: 10px;
border-radius: 50%;
background: #e2e8f0;
cursor: pointer;
transition: background 0.2s;
}
.dot.active { background: #7b2ff7; }
`
document.head.appendChild(style)
const wrap = document.createElement('div')
wrap.className = 'carousel-wrap'
document.body.appendChild(wrap)
const carousel = document.createElement('div')
carousel.className = 'carousel'
wrap.appendChild(carousel)
const colors = ['#7b2ff7', '#06b6d4', '#f59e0b', '#ef4444', '#10b981']
const slides = colors.map((bg, i) => {
const slide = document.createElement('div')
slide.className = 'slide'
slide.style.background = bg
slide.textContent = `Слайд ${i + 1}`
carousel.appendChild(slide)
return slide
})
// Dot-навигация
const dotsContainer = document.createElement('div')
dotsContainer.className = 'controls'
wrap.appendChild(dotsContainer)
const dots = slides.map((_, i) => {
const dot = document.createElement('div')
dot.className = 'dot' + (i === 0 ? ' active' : '')
dot.addEventListener('click', () => {
carousel.scrollTo({ left: slides[i].offsetLeft - carousel.offsetLeft - 8, behavior: 'smooth' })
})
dotsContainer.appendChild(dot)
return dot
})
// Определяем активный слайд по scroll
carousel.addEventListener('scroll', () => {
const scrollLeft = carousel.scrollLeft
const slideWidth = slides[0].offsetWidth + 12 // ширина + gap
const activeIndex = Math.round(scrollLeft / slideWidth)
dots.forEach((dot, i) => dot.classList.toggle('active', i === activeIndex))
console.log('Активный слайд:', activeIndex + 1)
})
// Программный переход
const nextBtn = document.createElement('button')
nextBtn.textContent = '→ Следующий'
nextBtn.style.cssText = 'padding: 6px 12px; background: #7b2ff7; color: white; border: none; border-radius: 4px; cursor: pointer; margin-right: 8px;'
let currentSlide = 0
nextBtn.addEventListener('click', () => {
currentSlide = (currentSlide + 1) % slides.length
slides[currentSlide].scrollIntoView({ behavior: 'smooth', inline: 'start', block: 'nearest' })
})
document.body.appendChild(nextBtn)
console.log('Карусель с CSS scroll-snap создана!')
console.log('Количество слайдов:', slides.length)Прокрутка с фиксацией на элементах (как в каруселях или страницах-лендингах) и анимации, привязанные к скроллу — два современных CSS-инструмента для создания насыщенного UX без JavaScript.
/* Контейнер: включаем snap */
.carousel {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory; /* Ось и строгость */
scroll-padding: 16px; /* Отступ от края до точки привязки */
gap: 12px;
}
/* Элементы: задаём точку привязки */
.slide {
flex-shrink: 0;
width: 300px;
scroll-snap-align: start; /* start | center | end */
scroll-snap-stop: always; /* Запрет перепрыгивать несколько элементов */
}scroll-snap-type: x mandatory; /* Горизонталь, обязательная привязка */
scroll-snap-type: y mandatory; /* Вертикаль, обязательная */
scroll-snap-type: y proximity; /* Привязка только когда близко */
scroll-snap-type: both mandatory; /* Обе оси */mandatory — всегда останавливается на snap-точке. Применяется для чётких переходов между секциями.
proximity — snap только если близко к точке. Мягче, подходит для длинных страниц.
html {
scroll-snap-type: y mandatory;
}
section {
height: 100vh;
scroll-snap-align: start;
}CSS scroll-driven animations (2023) позволяют привязать анимацию к прокрутке:
/* Прогресс-бар чтения */
@keyframes progress {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
.reading-progress {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: #7b2ff7;
transform-origin: left;
animation: progress linear;
animation-timeline: scroll(); /* Привязываем к скроллу */
animation-range: 0% 100%; /* Начало и конец */
}const progress = document.querySelector('.progress')
const animation = progress.animate(
[
{ transform: 'scaleX(0)' },
{ transform: 'scaleX(1)' },
],
{
timeline: new ScrollTimeline({
source: document.documentElement,
axis: 'block',
}),
fill: 'both',
}
)@keyframes fade-in {
from { opacity: 0; transform: translateY(24px); }
to { opacity: 1; transform: translateY(0); }
}
.reveal-on-scroll {
animation: fade-in linear both;
animation-timeline: view();
animation-range: entry 0% entry 40%;
/* Анимация проигрывается когда элемент входит в viewport */
}// Плавная прокрутка к элементу
element.scrollIntoView({
behavior: 'smooth',
block: 'start', // 'start' | 'center' | 'end' | 'nearest'
inline: 'nearest',
})
// Программный скролл контейнера
container.scrollTo({
left: targetX,
behavior: 'smooth',
})Симуляция scroll snap: вычисление ближайшей точки привязки и плавная анимация к ней
// Scroll Snap — горизонтальная карусель с CSS и JS-управлением
const style = document.createElement('style')
style.textContent = `
* { box-sizing: border-box; }
body { font-family: sans-serif; padding: 16px; }
.carousel-wrap { margin-bottom: 16px; }
.carousel {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
gap: 12px;
padding: 8px;
border: 2px solid #e2e8f0;
border-radius: 8px;
scrollbar-width: none;
}
.carousel::-webkit-scrollbar { display: none; }
.slide {
flex-shrink: 0;
width: 200px;
height: 120px;
border-radius: 8px;
scroll-snap-align: start;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
font-weight: 700;
color: white;
}
.controls { display: flex; gap: 8px; margin-top: 8px; }
.dot {
width: 10px; height: 10px;
border-radius: 50%;
background: #e2e8f0;
cursor: pointer;
transition: background 0.2s;
}
.dot.active { background: #7b2ff7; }
`
document.head.appendChild(style)
const wrap = document.createElement('div')
wrap.className = 'carousel-wrap'
document.body.appendChild(wrap)
const carousel = document.createElement('div')
carousel.className = 'carousel'
wrap.appendChild(carousel)
const colors = ['#7b2ff7', '#06b6d4', '#f59e0b', '#ef4444', '#10b981']
const slides = colors.map((bg, i) => {
const slide = document.createElement('div')
slide.className = 'slide'
slide.style.background = bg
slide.textContent = `Слайд ${i + 1}`
carousel.appendChild(slide)
return slide
})
// Dot-навигация
const dotsContainer = document.createElement('div')
dotsContainer.className = 'controls'
wrap.appendChild(dotsContainer)
const dots = slides.map((_, i) => {
const dot = document.createElement('div')
dot.className = 'dot' + (i === 0 ? ' active' : '')
dot.addEventListener('click', () => {
carousel.scrollTo({ left: slides[i].offsetLeft - carousel.offsetLeft - 8, behavior: 'smooth' })
})
dotsContainer.appendChild(dot)
return dot
})
// Определяем активный слайд по scroll
carousel.addEventListener('scroll', () => {
const scrollLeft = carousel.scrollLeft
const slideWidth = slides[0].offsetWidth + 12 // ширина + gap
const activeIndex = Math.round(scrollLeft / slideWidth)
dots.forEach((dot, i) => dot.classList.toggle('active', i === activeIndex))
console.log('Активный слайд:', activeIndex + 1)
})
// Программный переход
const nextBtn = document.createElement('button')
nextBtn.textContent = '→ Следующий'
nextBtn.style.cssText = 'padding: 6px 12px; background: #7b2ff7; color: white; border: none; border-radius: 4px; cursor: pointer; margin-right: 8px;'
let currentSlide = 0
nextBtn.addEventListener('click', () => {
currentSlide = (currentSlide + 1) % slides.length
slides[currentSlide].scrollIntoView({ behavior: 'smooth', inline: 'start', block: 'nearest' })
})
document.body.appendChild(nextBtn)
console.log('Карусель с CSS scroll-snap создана!')
console.log('Количество слайдов:', slides.length)Создай горизонтальную карусель с CSS Scroll Snap. Карусель должна содержать 5 слайдов, каждый шириной 280px. Добавь `scroll-snap-type: x mandatory` на контейнер и `scroll-snap-align: start` на каждый слайд. Скрой полосу прокрутки через `scrollbar-width: none`.
`scroll-snap-type: x mandatory` — горизонтальный snap с обязательной привязкой. `scroll-snap-align: start` — привязка к левому краю слайда. `scrollbar-width: none` — скрывает полосу прокрутки в Firefox. `flex-shrink: 0` — слайды не сжимаются.