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

Vue3+Hammer.js滑动动画卡顿问题求助

移动端垂直拖动盒子卡顿问题解决

问题现象

实现移动端垂直拖动盒子滑动效果,复制示例代码后出现跳帧、滑动不流畅的问题,预期是平滑跟随手指拖动并在结束后平滑归位/展开。

卡顿原因分析

  1. 响应式更新频繁阻塞主线程:pan事件触发频率极高(每秒数十次),每次更新响应式变量offset都会触发Vue的依赖收集和重渲染,导致主线程被占用,无法及时处理触摸事件和渲染。
  2. 拖动时transition干扰:CSS中设置的transition: 0.3s ease-in-out会在拖动过程中对transform的变化应用过渡,导致盒子的位置更新滞后于手指移动,产生卡顿感。
  3. HammerJS配置缺失:未禁用浏览器默认触摸行为,可能导致触摸事件被浏览器的滚动等行为抢占;同时pointers:0的配置不符合常规拖动需求,应该设置为1(单指拖动)。

具体修复方案

1. 优化响应式更新,减少重渲染

用非响应式变量存储实时拖动偏移,仅在拖动结束时更新响应式变量;拖动过程中直接操作DOM的transform,避免Vue响应式开销。

修改后的script部分:

import Hammer from 'hammerjs';
import Vue from 'vue';

export default {
    data() {
        return {
            offset: 0,
            threshold: 0,
            isDragging: false,
        };
    },
    mounted() {
        this.setupHammer();
    },
    methods: {
        setupHammer() {
            const postEl = this.$refs.post as HTMLElement;
            const hammertime = new Hammer.Manager(postEl, {
                touchAction: 'none' // 禁用浏览器默认触摸行为
            });

            hammertime.add(
                new Hammer.Pan({
                    threshold: this.threshold,
                    pointers: 1, // 单指拖动,符合常规交互
                    direction: Hammer.DIRECTION_VERTICAL,
                })
            );

            // 用非响应式变量存实时偏移
            let tempOffset = 0;
            const height = postEl.clientHeight;

            hammertime.on('pan', (event) => {
                tempOffset = event.deltaY / height;
                // 直接操作DOM,跳过Vue响应式更新
                postEl.style.transform = `translate3d(0, ${tempOffset * 100}%, 0)`;
            });

            hammertime.on('panstart', (event) => {
                this.isDragging =
                    event.offsetDirection === Hammer.DIRECTION_DOWN ||
                    event.offsetDirection === Hammer.DIRECTION_UP;
                // 拖动时移除transition,避免跟随延迟
                postEl.style.transition = 'none';
            });

            hammertime.on('panend', (event) => {
                this.isDragging = false;
                // 恢复transition,用于结束后的平滑动画
                postEl.style.transition = '0.3s ease-in-out';

                if (event.deltaY > 200 || event.velocityY > 0.4) {
                    this.offset = 1;
                } else {
                    this.offset = 0;
                }
                // 同步DOM到响应式变量的状态
                postEl.style.transform = `translate3d(0, ${this.offset * 100}%, 0)`;
            });
        },
    },
};

2. 调整CSS,确保GPU加速和无多余重排

给.post添加will-change: transform,告诉浏览器提前优化该元素的transform动画;同时确保元素脱离文档流相关的属性不会引发重排:

.post {
  position: relative;
  flex-direction: column;
  font-family: Inter, "Inter Semi Bold", sans-serif;
  font-weight: 400;
  padding: .5em 1.2em;
  height: 100%;
  transition: 0.3s ease-in-out;
  will-change: transform; /* 提示浏览器优化transform动画 */
  overflow: hidden; /* 避免内部元素溢出引发额外重绘 */
}

3. 模板部分简化(可选)

由于拖动过程中直接操作DOM,模板中的:style可以只在非拖动状态生效,或者保留作为初始和结束状态的 fallback:

<template>
    <div
            class="uppBox scrollableUppbox uppBox--pink post"
            ref="post"
            :style="{ transform: `translate3d(0, ${offset * 100}%, 0)` }"
    >
        <div class="post__iconBox">
            <i class="material-icons">pause</i>
        </div>
        <div class="post__authorData">
            <div>photo</div>
            <h1>ExampleUser</h1>
        </div>
        <div class="post__content">
            Content
        </div>
    </div>
</template>

关键优化点说明

  • touchAction: 'none':禁用浏览器的默认触摸行为(如滚动、缩放),确保HammerJS能完整捕获触摸事件。
  • 拖动时移除transition:避免过渡动画干扰实时拖动的跟随效果,结束后再恢复过渡用于平滑归位。
  • 直接操作DOM代替响应式更新:减少Vue响应式系统在高频事件中的开销,让拖动更流畅。
  • will-change: transform:触发GPU加速,让transform动画在独立图层渲染,减少主线程压力。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 06:35:12