You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现桌面端网页平滑水平滚动?支持Vue 3/原生JS

横向平滑滚动实现方案

原生JavaScript实现

通过requestAnimationFrame实现逐帧缓动动画,替代瞬间修改scrollLeft的方式,彻底解决块状滚动问题:

// 初始化目标滚动位置和动画状态
let targetScrollLeft = document.documentElement.scrollLeft;
let isAnimating = false;

function scrollHorizontally(e) {
    e.preventDefault();
    // 标准化滚轮方向值(-1向下滚,1向上滚)
    const delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
    // 单次滚轮的总移动步长,可根据需求调整
    const scrollStep = delta * 60;
    // 更新目标滚动位置
    targetScrollLeft -= scrollStep;
    // 限制滚动范围,避免超出内容边界
    targetScrollLeft = Math.max(0, Math.min(targetScrollLeft, document.documentElement.scrollWidth - window.innerWidth));
    
    // 未在动画中则启动动画循环
    if (!isAnimating) {
        isAnimating = true;
        requestAnimationFrame(animateScroll);
    }
}

function animateScroll() {
    const currentScroll = document.documentElement.scrollLeft;
    // 计算当前位置与目标位置的差值
    const diff = targetScrollLeft - currentScroll;
    
    // 差值小于1时,直接定位到目标位置并结束动画
    if (Math.abs(diff) < 1) {
        document.documentElement.scrollLeft = targetScrollLeft;
        document.body.scrollLeft = targetScrollLeft;
        isAnimating = false;
        return;
    }
    
    // 缓动移动(系数0.1可调整,数值越小动画越平缓)
    document.documentElement.scrollLeft += diff * 0.1;
    document.body.scrollLeft += diff * 0.1;
    requestAnimationFrame(animateScroll);
}

// 绑定滚轮事件(兼容Firefox)
document.addEventListener('mousewheel', scrollHorizontally);
document.addEventListener('DOMMouseScroll', scrollHorizontally);

核心逻辑说明

  • 利用requestAnimationFrame实现浏览器原生帧动画,保证动画流畅度
  • 通过缓动公式(每次移动差值的10%)实现平滑过渡,避免块状跳跃
  • 限制滚动范围,防止超出内容边界
  • 统一处理不同浏览器的滚轮事件参数

Vue3实现方案

封装为自定义指令,方便在组件中复用,同时避免内存泄漏:

1. 注册全局自定义指令

// main.js
import { createApp } from 'vue';
import App from './App.vue';

const app = createApp(App);

app.directive('horizontal-scroll', {
    mounted(el) {
        let targetScrollLeft = el.scrollLeft;
        let isAnimating = false;

        const handleWheel = (e) => {
            e.preventDefault();
            const delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
            const scrollStep = delta * 60;
            targetScrollLeft -= scrollStep;
            // 限制容器内的滚动范围
            targetScrollLeft = Math.max(0, Math.min(targetScrollLeft, el.scrollWidth - el.clientWidth));

            if (!isAnimating) {
                isAnimating = true;
                requestAnimationFrame(animate);
            }
        };

        const animate = () => {
            const current = el.scrollLeft;
            const diff = targetScrollLeft - current;
            if (Math.abs(diff) < 1) {
                el.scrollLeft = targetScrollLeft;
                isAnimating = false;
                return;
            }
            el.scrollLeft += diff * 0.1;
            requestAnimationFrame(animate);
        };

        // 绑定事件
        el.addEventListener('mousewheel', handleWheel);
        el.addEventListener('DOMMouseScroll', handleWheel);

        // 存储清理函数,用于卸载时移除事件
        el._unbindScroll = () => {
            el.removeEventListener('mousewheel', handleWheel);
            el.removeEventListener('DOMMouseScroll', handleWheel);
        };
    },
    unmounted(el) {
        // 组件卸载时清理事件
        if (el._unbindScroll) el._unbindScroll();
    }
});

app.mount('#app');

2. 在组件中使用

<template>
  <div class="horizontal-wrap" v-horizontal-scroll>
    <!-- 你的横向溢出内容 -->
  </div>
</template>

<style scoped>
.horizontal-wrap {
  width: 100vw;
  overflow-x: auto;
  white-space: nowrap;
}
</style>

为什么原生scrollBy({behavior: 'smooth'})体验差?

浏览器内置的平滑滚动是通用实现,无法精准匹配自定义的大跨度滚动步长,且不同浏览器的动画曲线、延迟表现不一致,导致体验割裂。自行实现帧动画可以完全控制滚动速度、缓动效果,体验更统一。

内容的提问来源于stack exchange,提问作者sterix24

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 10:35:03