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

如何在基于光标位置的缩放变换中保持transform-origin稳定?

修复Canvas元素围绕光标缩放时的意外位移问题

问题描述

我有一个Canvas组件,通过transform-origin实现围绕光标位置缩放元素。当scale>1时,仅移动光标(无其他交互)就会导致元素意外位移。期望实现仅在scale变化时,元素才围绕光标位置缩放,且不新增HTML元素。

当前实现代码

HTML

<input type="range" min="0.5" max="2" step="0.1" value="1">
<div class="canvas">
    <div class="cursor"></div>
    <div class="box"></div>
    <div class="info"></div>
</div>

CSS

.canvas {
    position: relative;
    width: 400px;
    height: 400px;
    border: 2px solid #ccc;
    background: #f8f8f8;
    margin: 20px;
}
.cursor {
    position: absolute;
    width: 8px;
    height: 8px;
    background: red;
    border-radius: 50%;
    pointer-events: none;
    margin: -4px;
    z-index: 2;
}
.box {
    position: absolute;
    left: 100px;
    top: 100px;
    width: 128px;
    height: 128px;
    background: rgba(59, 130, 246, 0.5);
    border: 2px solid rgb(37, 99, 235);
    will-change: transform;
}
.info {
    position: absolute;
    bottom: 8px;
    left: 8px;
    font-size: 14px;
    color: #666;
}

JavaScript

const canvas = document.querySelector('.canvas');
const cursor = document.querySelector('.cursor');
const box = document.querySelector('.box');
const info = document.querySelector('.info');
const scaleInput = document.querySelector('input');

let scale = 1;
let transformOrigin = '0% 0%';

// Box's initial position and dimensions
const boxRect = {
    left: 100,
    top: 100,
    width: 128,
    height: 128
};

canvas.addEventListener('mousemove', (e) => {
    const rect = canvas.getBoundingClientRect();
    const mouseX = e.clientX - rect.left;
    const mouseY = e.clientY - rect.top;

    // Update cursor position
    cursor.style.left = mouseX + 'px';
    cursor.style.top = mouseY + 'px';

    // Calculate relative position from box's top-left corner
    const relativeX = ((mouseX - boxRect.left) / boxRect.width) * 100;
    const relativeY = ((mouseY - boxRect.top) / boxRect.height) * 100;

    // Set transform origin as percentage values
    transformOrigin = `${relativeX}% ${relativeY}%`;
    
    // Apply transform origin
    box.style.transformOrigin = transformOrigin;
    
    // Update info display
    info.textContent = `Transform Origin: (${Math.round(relativeX)}%, ${Math.round(relativeY)}%)`;
});

scaleInput.addEventListener('input', (e) => {
    scale = Number(e.target.value);
    box.style.transform = `scale(${scale})`;
});

问题原因

当前代码在鼠标移动时实时更新transform-origin,而transform-origin是所有后续变换的基准点。当元素已经处于缩放状态(scale>1)时,修改transform-origin会直接改变现有缩放变换的原点,导致元素位置发生偏移。

解决方案

核心思路:仅在调整缩放比例时,才更新transform-origin并应用缩放,鼠标移动时只记录目标原点,不修改元素的变换属性。

修改后的JavaScript代码:

const canvas = document.querySelector('.canvas');
const cursor = document.querySelector('.cursor');
const box = document.querySelector('.box');
const info = document.querySelector('.info');
const scaleInput = document.querySelector('input');

let scale = 1;
let transformOrigin = '0% 0%';

// Box's initial position and dimensions
const boxRect = {
    left: 100,
    top: 100,
    width: 128,
    height: 128
};

canvas.addEventListener('mousemove', (e) => {
    const rect = canvas.getBoundingClientRect();
    const mouseX = e.clientX - rect.left;
    const mouseY = e.clientY - rect.top;

    // Update cursor position
    cursor.style.left = mouseX + 'px';
    cursor.style.top = mouseY + 'px';

    // Calculate relative position from box's top-left corner
    const relativeX = ((mouseX - boxRect.left) / boxRect.width) * 100;
    const relativeY = ((mouseY - boxRect.top) / boxRect.height) * 100;

    // 仅记录目标变换原点,不直接应用到元素
    transformOrigin = `${relativeX}% ${relativeY}%`;
    
    // Update info display
    info.textContent = `Transform Origin: (${Math.round(relativeX)}%, ${Math.round(relativeY)}%)`;
});

scaleInput.addEventListener('input', (e) => {
    scale = Number(e.target.value);
    // 先更新变换原点,再应用缩放
    box.style.transformOrigin = transformOrigin;
    box.style.transform = `scale(${scale})`;
});

关键改动点

  1. 移除mousemove事件中直接设置box.style.transformOrigin的代码,只记录transformOrigin变量。
  2. 在scaleInput的input事件中,先设置元素的transformOrigin为当前记录的光标对应原点,再应用缩放变换。

这样修改后,只有当用户调整缩放比例时,元素才会以当前光标位置为原点进行缩放;移动光标时仅更新显示信息和记录原点,不会触发元素位移。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 19:17:44