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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 01:02:37