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

Framer Motion拖拽SVG位移异常:viewBox与窗口尺寸不匹配

解决Framer Motion在SVG拖拽时viewBox导致的位移缩放问题

当SVG的viewBox与实际视口尺寸不匹配时,Framer Motion默认的拖拽逻辑会直接使用屏幕像素坐标,导致元素位移和鼠标移动不成比例。要解决这个问题,你可以通过SVG的**CTM(当前变换矩阵)**将屏幕坐标转换为SVG内部的用户坐标系,再结合Framer的useMotionValue和useTransform钩子实现适配。

实现步骤

  • 获取SVG元素的DOM引用,用于计算CTM
  • 用useMotionValue跟踪拖拽的原始屏幕坐标
  • 通过useTransform将屏幕坐标转换为SVG内部坐标
  • 将转换后的坐标绑定到可拖拽元素的位置属性

完整代码示例

import { motion, useMotionValue, useTransform, useDrag } from "framer-motion";
import { useRef } from "react";

export default function SVGDrag() {
  const svgRef = useRef(null);
  // 跟踪拖拽的屏幕坐标,初始值对应元素初始位置
  const dragX = useMotionValue(5);
  const dragY = useMotionValue(5);

  // 转换屏幕坐标到SVG用户坐标系
  const svgX = useTransform(dragX, (value) => {
    if (!svgRef.current) return value;
    const ctm = svgRef.current.getScreenCTM();
    return (value - ctm.e) / ctm.a;
  });

  const svgY = useTransform(dragY, (value) => {
    if (!svgRef.current) return value;
    const ctm = svgRef.current.getScreenCTM();
    return (value - ctm.f) / ctm.d;
  });

  // 绑定拖拽事件,更新屏幕坐标值
  const bindDrag = useDrag({
    onDrag: (_event, info) => {
      dragX.set(info.point.x);
      dragY.set(info.point.y);
    },
    initial: { x: 5, y: 5 },
  });

  return (
    <svg ref={svgRef} viewBox="0 0 40 20" style={{ width: "100vw", height: "100vh" }}>
      <motion.circle
        {...bindDrag}
        cx={svgX}
        cy={svgY}
        r="0.5"
        strokeWidth="0.1"
        fill="blue"
      />
    </svg>
  );
}

关键逻辑说明

  • CTM转换:getScreenCTM()返回SVG元素到屏幕坐标系的变换矩阵,通过逆运算将屏幕坐标转换为SVG内部用户坐标:
    • 用户X坐标:(screenX - ctm.e) / ctm.a
    • 用户Y坐标:(screenY - ctm.f) / ctm.d
  • useTransform:实时将原始拖拽的屏幕坐标转换为适配SVG的坐标,确保鼠标移动和元素位移完全匹配
  • useDrag:手动绑定拖拽事件获取屏幕坐标,替代Framer默认的拖拽位置绑定,避免直接使用不匹配的像素单位

这种方法不需要修改viewBox,完全通过坐标转换适配现有SVG的缩放比例,完美解决位移不匹配的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 18:20:58