Vue中SVG Foreign Object拖拽时的Chromium异常滚动问题
Chromium内核浏览器中SVG拖拽时画布异常滚动的修复方案
问题背景
基于Vue开发情绪板应用,采用SVG实现可拖拽画布,通过SVG Foreign Object承载高响应性组件。功能整体正常,但在Chromium内核浏览器(如Chrome、Edge)中存在异常:页面滚动后拖拽红色方块组件时,SVG画布会随之滚动,而非仅组件本身移动;页面处于顶部或底部时无此问题,Firefox浏览器中完全正常。此前尝试阻止window的wheel事件未生效。
问题根源
Chromium对SVG元素的事件处理机制与Firefox存在差异,拖拽时的鼠标事件会冒泡到外层滚动容器;同时使用clientX/clientY计算坐标时未考虑页面滚动偏移,导致拖拽行为误触发了容器滚动。
修复步骤
- 给外层滚动容器添加引用,方便精准控制其滚动行为
- 用
pageX/pageY替代clientX/clientY计算坐标,pageX是相对于文档的坐标,不受页面滚动影响 - 拖拽开始时,阻止滚动容器的wheel事件并设置其overflow为hidden;拖拽结束后恢复原有状态
- 移除无效的全局window wheel监听,避免影响页面其他区域滚动
修改后的代码
App.vue
<script setup lang="ts"> import CanvasComponent from "@/components/CanvasComponent.vue"; import { ref } from "vue"; const width = 3000; const height = 3000; const scrollContainer = ref<HTMLDivElement | null>(null); // 对外暴露容器引用,供子组件调用 const getScrollContainer = () => scrollContainer.value; </script> <template> <div ref="scrollContainer" :style="`width:${(width+80)}px; height:${(height+80)}px`" class="overflow-auto p-10" > <svg viewBox="0 0 3000 3000" style="background-color:blue" :width="width + 'px'" :height="height + 'px'" xmlns="http://www.w3.org/2000/svg" version="1.1" > <CanvasComponent :initial-x="300" :initial-y="400" initial-width="200px" initial-height="300px" :get-scroll-container="getScrollContainer" ></CanvasComponent> </svg> </div> </template>
CanvasComponent.vue
<script setup lang='ts'> import { nextTick, ref } from "vue"; const props = defineProps<{ initialX: number, initialY: number, initialWidth: string, initialHeight: string, getScrollContainer: () => HTMLDivElement | null }>(); const x = ref(props.initialX) const y = ref(props.initialY); const width = ref(props.initialWidth) const height = ref(props.initialHeight) const isDragging = ref(false); const offsetX = ref(0); const offsetY = ref(0); let scrollContainer: HTMLDivElement | null = null; let wheelHandler: (e: WheelEvent) => void; const dragStart = (e: MouseEvent) => { e.stopPropagation(); e.preventDefault() isDragging.value = true; scrollContainer = props.getScrollContainer(); // 使用pageX/pageY计算偏移,不受页面滚动影响 offsetX.value = e.pageX - x.value; offsetY.value = e.pageY - y.value; if (scrollContainer) { // 阻止容器滚动 scrollContainer.style.overflow = 'hidden'; // 绑定wheel事件阻止滚动 wheelHandler = (evt) => evt.preventDefault(); scrollContainer.addEventListener('wheel', wheelHandler); } nextTick(() => { document.addEventListener('mousemove', drag); document.addEventListener('mouseup', dragEnd); }); } const drag = (e: MouseEvent) => { e.stopPropagation(); e.preventDefault(); if(!isDragging.value) return; // 用pageX计算当前位置 x.value = e.pageX - offsetX.value; y.value = e.pageY - offsetY.value; } const dragEnd = (e: MouseEvent) => { e.preventDefault(); isDragging.value = false; if (scrollContainer && wheelHandler) { // 恢复容器滚动 scrollContainer.style.overflow = 'auto'; scrollContainer.removeEventListener('wheel', wheelHandler); } document.removeEventListener('mousemove', drag); document.removeEventListener('mouseup', dragEnd); } </script> <template> <svg> <foreignObject class="drag-handle-class" :x="x" :y="y" :width="width" :height="height" @mousedown="dragStart" > <div class="w-full h-full bg-red-500 drag-handle-class"></div> </foreignObject> </svg> </template>
额外说明
- 移除了红色方块的
overflow-scroll类,避免内部滚动事件干扰拖拽行为 - 仅针对滚动容器进行滚动控制,不会影响页面其他区域的正常滚动
- 事件绑定与清理成对执行,避免内存泄漏
内容的提问来源于stack exchange,提问作者Robin Mons
相关产品推荐
相关产品推荐

