下面我将为您提供几种在 Vue 中实现的解决方案,从最推荐到备选方案,并附上完整的代码示例。
解决方案 1: 使用事件修饰符 .prevent (最推荐)
Vue 的事件修饰符提供了一种非常优雅和声明式的方式来处理这个问题。.prevent 修饰符会自动调用 event.preventDefault()。
关键点: 将 .prevent 添加到 @touchmove 事件上。现代浏览器为了优化滚动性能,会将 touchmove 事件默认为“被动 (passive)”,这意味着在事件监听器内部调用 event.preventDefault() 可能会被忽略。而 Vue 的 .prevent 修饰符会自动处理这个问题,确保 preventDefault 生效。
完整 Vue 3 (Composition API) 示例这是一个完整的、可直接运行的组件示例。
<template>
<div class="page-container">
<div class="content">
<p>向上滚动页面查看效果</p>
<p>...</p>
<p>...</p>
<p>尝试拖动下面的方块</p>
<!-- Draggable Element -->
<div
class="draggable-box"
:style="boxStyle"
@touchstart.stop="onTouchStart"
@touchmove.prevent="onTouchMove"
@touchend="onTouchEnd"
>
拖动我
</div>
<p>...</p>
<p>...</p>
<p>页面底部</p>
</div>
</div>
</template>
<script setup>
import { ref, reactive, computed } from 'vue';
// 标记当前是否正在拖拽
const isDragging = ref(false);
// 元素的位置
const position = reactive({ x: 0, y: 0 });
// 拖拽起始时,手指相对于元素左上角的偏移
const startOffset = reactive({ x: 0, y: 0 });
// 使用计算属性动态生成样式
const boxStyle = computed(() => ({
transform: `translate(${position.x}px, ${position.y}px)`
}));
// 触摸开始事件
const onTouchStart = (event) => {
isDragging.value = true;
// 记录初始触摸点与元素当前位置的差值
const touch = event.touches[0];
startOffset.x = touch.clientX - position.x;
startOffset.y = touch.clientY - position.y;
console.log("Drag Start");
};
// 触摸移动事件
const onTouchMove = (event) => {
// 如果没有在拖拽状态,则直接返回
if (!isDragging.value) return;
// event.preventDefault() 已由 .prevent 修饰符自动处理
// 这就是阻止页面滚动的关键!
const touch = event.touches[0];
// 根据手指的移动实时更新元素位置
position.x = touch.clientX - startOffset.x;
position.y = touch.clientY - startOffset.y;
};
// 触摸结束事件
const onTouchEnd = () => {
if (isDragging.value) {
isDragging.value = false;
console.log("Drag End");
}
};
</script>
<style>
/* 用于创建可滚动的页面环境 */
.page-container {
font-family: sans-serif;
text-align: center;
}
.content {
height: 200vh; /* 使 body 产生滚动条 */
padding-top: 50vh;
background: linear-gradient(to bottom, #f0f4f8, #d9e2ec);
}
/* 可拖拽元素的样式 */
.draggable-box {
width: 100px;
height: 100px;
background-color: #007bff;
color: white;
border-radius: 10px;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
cursor: grab;
user-select: none; /* 防止拖动时选中文本 */
touch-action: none; /* CSS属性,提示浏览器该元素有自定义触摸行为 */
position: relative; /* 确保 transform 生效 */
z-index: 1000;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.draggable-box:active {
cursor: grabbing;
background-color: #0056b3;
}
</style> 代码解释:
@touchmove.prevent: 这是解决问题的核心。它告诉 Vue 在 onTouchMove 事件触发时,自动阻止其默认行为(即页面滚动)。
@touchstart.stop: .stop 修饰符会阻止事件冒泡。虽然在这个特定场景下不是必须的,但在复杂布局中可以防止父元素上意外的 touchstart 监听器被触发。
touch-action: none;: 这是一个重要的 CSS 属性。它向浏览器提供了一个性能提示,告知该元素上的触摸操作(如平移、缩放)将由 JavaScript 处理,浏览器不应尝试任何自己的行为(如滚动或缩放)。这可以作为 preventDefault 的补充,有时甚至可以单独解决问题,但结合使用最为稳妥。
状态管理: 使用 isDragging ref 来跟踪拖拽状态,确保只在拖拽时才更新元素位置。
坐标计算: 在 onTouchStart 中计算并保存了手指位置和元素当前位置的偏移量 (startOffset)。这可以确保拖拽时元素不会“跳”到你的手指尖,而是保持相对位置平滑移动。
解决方案 2: 切换 Body 的 Overflow 属性 (备选方案)
这是一种更“强制”的方法。当拖拽开始时,通过 JavaScript 给 <body> 标签添加一个 CSS 类,该类设置 overflow: hidden; 来禁用整个页面的滚动。拖拽结束后再移除该类。
优点:
非常可靠,绝对能阻止滚动。
缺点:
在某些桌面浏览器上,隐藏滚动条可能会导致页面宽度变化,引起内容“跳动”。
感觉不如方案 1 优雅。
修改 <script setup> 部分// ... (保留 isDragging, position, startOffset, boxStyle 的定义)
// 触摸开始事件
const onTouchStart = (event) => {
isDragging.value = true;
document.body.classList.add('no-scroll'); // 添加类以禁用滚动
const touch = event.touches[0];
startOffset.x = touch.clientX - position.x;
startOffset.y = touch.clientY - position.y;
};
// 触摸移动事件 (不再需要 .prevent)
const onTouchMove = (event) => {
if (!isDragging.value) return;
const touch = event.touches[0];
position.x = touch.clientX - startOffset.x;
position.y = touch.clientY - startOffset.y;
};
// 触摸结束事件
const onTouchEnd = () => {
if (isDragging.value) {
isDragging.value = false;
document.body.classList.remove('no-scroll'); // 移除类以恢复滚动
}
}; 添加对应的 CSS在你的 <style> 标签中加入:
body.no-scroll {
overflow: hidden;
} 总结与最佳实践
| 方案 1: .prevent 修饰符 | Vue 原生、代码优雅、意图清晰、无副作用 | 无明显缺点 | ★★★★★ (首选) |
| 方案 2: 切换 Body Overflow | 绝对可靠 | 可能导致页面跳动,代码稍显侵入式 | ★★★☆☆ (备选) |
强烈建议您使用第一种方案。它利用了 Vue 框架的特性,代码更简洁、更具可读性,并且完美地解决了问题而没有引入其他副作用。
同时,不要忘记在你的 CSS 中为可拖拽元素添加 touch-action: none;,这是一个很好的性能和行为优化。
网友回复


