如何实现带scroll snap的横向滚动容器点击按钮500ms滚动指定距离
实现自定义时长的平滑横向滚动
问题说明
现有配置了scroll-snap属性的横向可滚动容器,要求点击按钮时,容器以500ms动画时长精确滚动340px(对应320px子元素+20px间距),实现无延迟的平滑滚动效果。
解决方案
1. 修正CSS基础样式
先调整子元素宽度匹配需求,同时移除原生scroll-behavior: smooth(它无法自定义动画时长,会干扰自定义逻辑):
.Container { display: flex; scroll-snap-type: x mandatory; position: relative; overflow-y: hidden; overflow-x: scroll; gap: 20px; /* 移除原生平滑滚动,避免冲突 */ } .Container>div { min-width: 320px; max-width: 320px; height: 100px; background-color: blue; scroll-snap-align: start; scroll-snap-stop: always; }
2. 编写JS自定义滚动动画
使用requestAnimationFrame实现精确的时长控制和流畅动画,同时加入缓动函数让滚动更自然:
const container = document.querySelector('.Container'); const slideBtn = document.getElementById('slider_button'); const scrollDistance = 340; // 单次滚动距离 const duration = 500; // 动画时长(ms) slideBtn.addEventListener('click', () => { const startScrollLeft = container.scrollLeft; const targetScrollLeft = startScrollLeft + scrollDistance; let startTime = null; const animateScroll = (timestamp) => { if (!startTime) startTime = timestamp; // 计算动画进度(0到1) const progress = Math.min((timestamp - startTime) / duration, 1); // 加入ease-in-out缓动,让动画更顺滑 const easeProgress = progress < 0.5 ? 2 * progress * progress : 1 - Math.pow(-2 * progress + 2, 2) / 2; // 更新滚动位置 container.scrollLeft = startScrollLeft + (targetScrollLeft - startScrollLeft) * easeProgress; // 未完成则继续动画 if (progress < 1) { requestAnimationFrame(animateScroll); } }; requestAnimationFrame(animateScroll); });
3. 关键细节说明
- 用
requestAnimationFrame绑定浏览器刷新周期,避免卡顿和延迟 - 自定义缓动函数让滚动动画更符合用户视觉预期
- 精确控制滚动距离,同时兼容
scroll-snap的吸附特性(滚动结束后会自动对齐到子元素) - 移除原生
scroll-behavior,防止默认平滑逻辑干扰自定义动画
完整可运行代码
<!DOCTYPE html> <html lang="zh-CN"> <head> <style> .Container { display: flex; scroll-snap-type: x mandatory; position: relative; overflow-y: hidden; overflow-x: scroll; gap: 20px; padding: 10px 0; } .Container>div { min-width: 320px; max-width: 320px; height: 100px; background-color: blue; scroll-snap-align: start; scroll-snap-stop: always; border-radius: 8px; } #slider_button { margin-top: 20px; padding: 8px 16px; cursor: pointer; } </style> </head> <body> <div class="Container"> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> </div> <button id="slider_button">Slide</button> <script> const container = document.querySelector('.Container'); const slideBtn = document.getElementById('slider_button'); const scrollDistance = 340; const duration = 500; slideBtn.addEventListener('click', () => { const startScrollLeft = container.scrollLeft; const targetScrollLeft = startScrollLeft + scrollDistance; let startTime = null; const animateScroll = (timestamp) => { if (!startTime) startTime = timestamp; const progress = Math.min((timestamp - startTime) / duration, 1); const easeProgress = progress < 0.5 ? 2 * progress * progress : 1 - Math.pow(-2 * progress + 2, 2) / 2; container.scrollLeft = startScrollLeft + (targetScrollLeft - startScrollLeft) * easeProgress; if (progress < 1) { requestAnimationFrame(animateScroll); } }; requestAnimationFrame(animateScroll); }); </script> </body> </html>
内容的提问来源于stack exchange,提问作者cryndry
相关产品推荐
相关产品推荐

