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

SVG带transform元素鼠标调整尺寸失效,求坐标转换方案

问题描述

我有一批SVG元素,想给它们加上鼠标拖拽调整尺寸的功能。元素没有任何transform属性(比如matrix、rotate、scale这些)的时候,功能完全正常;但只要给rect加上transform="matrix(1.1,1.1,1,0,0,0)"这种属性,功能就失效了。我知道应该用matrix.inverse()把鼠标坐标转换成元素自身的空间坐标,但试了好几次都没搞成,现在根本没法正确算出拖拽起始点和当前点的x、y差值。相关代码如下:

var rect = document.getElementById('rect');
var grip = document.getElementById('grip');
function moveGrip() {
    var baseMatrix = rect.getCTM(),
      x = rect.width.baseVal.value + rect.x.baseVal.value,
        y = rect.height.baseVal.value + rect.y.baseVal.value;
    grip.cx.baseVal.value = x * baseMatrix.a + y * baseMatrix.c + baseMatrix.e;
    grip.cy.baseVal.value = x * baseMatrix.b + y * baseMatrix.d + baseMatrix.f;
}

grip.addEventListener('mousedown', onGripMouseDown);
document.addEventListener('mousemove', onDocMouseMove);

var startDragPoint, startDragSize;
function onGripMouseDown(evt) {
    startDragSize = {w: rect.width.baseVal.value, h: rect.height.baseVal.value};
    startDragPoint = {x: evt.clientX, y: evt.clientY};
}

function onDocMouseMove(evt) {
    if (evt.buttons & 1) {
    //    dragging
        rect.width.baseVal.value = startDragSize.w + evt.clientX - startDragPoint.x;
        rect.height.baseVal.value = startDragSize.h + evt.clientY - startDragPoint.y;
        moveGrip();
    }
}

moveGrip();
<svg width="500" height="400">
    <rect id="rect" x="20" y="20" width="200" height="100"
          transform="matrix(1.1,1.1,1,0,0,0)"
          style="fill:none;stroke: #3a2dd0; stroke-width: 2px;"></rect>
    <g>
        <circle id="grip" r="3" stroke-width="1" stroke="green"></circle>
    </g>
</svg>
解决办法

问题出在鼠标事件的坐标是屏幕坐标系的,而元素的尺寸是基于自身局部坐标系的——当元素有transform时,这俩坐标系不匹配,必须通过逆矩阵把屏幕坐标转成元素本地的坐标才行。

具体修改步骤

  1. 获取逆矩阵转换鼠标坐标:在按下拖拽点和拖拽移动的函数里,用getScreenCTM()拿到元素到屏幕的变换矩阵,再取它的逆矩阵,把鼠标的clientX/clientY转换成元素局部空间的坐标。
  2. 调整拖拽逻辑:
    • 记录拖拽开始时的局部坐标,不是屏幕坐标
    • 用局部坐标的差值来调整元素尺寸,而不是直接用屏幕坐标的差值

修改后的完整代码

JavaScript

var rect = document.getElementById('rect');
var grip = document.getElementById('grip');
var svg = document.querySelector('svg');

function moveGrip() {
    var baseMatrix = rect.getCTM(),
        x = rect.width.baseVal.value + rect.x.baseVal.value,
        y = rect.height.baseVal.value + rect.y.baseVal.value;
    grip.cx.baseVal.value = x * baseMatrix.a + y * baseMatrix.c + baseMatrix.e;
    grip.cy.baseVal.value = x * baseMatrix.b + y * baseMatrix.d + baseMatrix.f;
}

grip.addEventListener('mousedown', onGripMouseDown);
document.addEventListener('mousemove', onDocMouseMove);
document.addEventListener('mouseup', () => startDragPoint = null); // 加个mouseup重置拖拽状态

var startDragPoint, startDragSize;

function onGripMouseDown(evt) {
    startDragSize = {w: rect.width.baseVal.value, h: rect.height.baseVal.value};
    // 拿到逆矩阵,把起始鼠标坐标转成元素局部坐标
    var inverseMatrix = rect.getScreenCTM().inverse();
    startDragPoint = svg.createSVGPoint();
    startDragPoint.x = evt.clientX;
    startDragPoint.y = evt.clientY;
    startDragPoint = startDragPoint.matrixTransform(inverseMatrix);
}

function onDocMouseMove(evt) {
    if (!startDragPoint) return;
    if (evt.buttons & 1) {
        // 转换当前鼠标坐标到元素局部空间
        var inverseMatrix = rect.getScreenCTM().inverse();
        var currentPoint = svg.createSVGPoint();
        currentPoint.x = evt.clientX;
        currentPoint.y = evt.clientY;
        currentPoint = currentPoint.matrixTransform(inverseMatrix);
        
        // 用局部坐标的差值调整尺寸
        rect.width.baseVal.value = startDragSize.w + (currentPoint.x - startDragPoint.x);
        rect.height.baseVal.value = startDragSize.h + (currentPoint.y - startDragPoint.y);
        moveGrip();
    }
}

moveGrip();

HTML

<svg width="500" height="400">
    <rect id="rect" x="20" y="20" width="200" height="100"
          transform="matrix(1.1,1.1,1,0,0,0)"
          style="fill:none;stroke: #3a2dd0; stroke-width: 2px;"></rect>
    <g>
        <circle id="grip" r="3" stroke-width="1" stroke="green"></circle>
    </g>
</svg>

关键细节解释

  • getScreenCTM():获取元素从自身局部坐标系到屏幕坐标系的变换矩阵
  • matrix.inverse():生成逆矩阵,用来把屏幕坐标转回到元素的局部坐标系
  • createSVGPoint() + matrixTransform():完成坐标转换,确保差值计算是在元素自己的坐标系里进行的,这样不管元素怎么变换,拖拽逻辑都能正常工作
  • 新增的mouseup事件:防止鼠标松开后还一直触发拖拽,避免异常

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 17:40:28