如何在基于光标位置的缩放变换中保持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})`; });
关键改动点
- 移除
mousemove事件中直接设置box.style.transformOrigin的代码,只记录transformOrigin变量。 - 在
scaleInput的input事件中,先设置元素的transformOrigin为当前记录的光标对应原点,再应用缩放变换。
这样修改后,只有当用户调整缩放比例时,元素才会以当前光标位置为原点进行缩放;移动光标时仅更新显示信息和记录原点,不会触发元素位移。
内容的提问来源于stack exchange,提问作者Amanda Ferrari
相关产品推荐
相关产品推荐

