我正在尝试在画布元素上实现缩放功能,其中:
但是,当我尝试在缩放后更新画布大小和滚动条位置时,缩放不再按预期运行。内容发生变化,滚动条无法正确更新。
let canvas;
let ctx;
let originalCanvasHeight, originalCanvasWidth;
setTimeout(() => {
canvas = document.getElementById("canvas")
ctx = canvas.getContext("2d")
originalCanvasWidth = canvas.getBoundingClientRect().width;
originalCanvasHeight = canvas.getBoundingClientRect().height;
console.log(originalCanvasHeight)
ctx.fillStyle = "red"
ctx.fillRect(50, 50, 40, 40)
}, 2000)
let scale = 1;
const scaleOffset = {
x: 0,
y: 0
}
const zoom = (e) => {
const mousex = 70
const mousey = 70
const zoomStep = 0.5
const targetScale = scale + (e.deltaY > 0 ? -zoomStep : zoomStep)
console.log(targetScale)
const scaleChangeFactor = parseFloat((targetScale / scale).toFixed(2))
// Adjust offsets so that the zoom centers on the mouse position
scaleOffset.x = parseFloat(
(mousex - (mousex - scaleOffset.x) * scaleChangeFactor).toFixed(2),
)
scaleOffset.y = parseFloat(
(mousey - (mousey - scaleOffset.y) * scaleChangeFactor).toFixed(2),
)
canvas.width = originalCanvasWidth * targetScale
canvas.height = originalCanvasHeight * targetScale
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.setTransform(1, 0, 0, 1, 0, 0)
ctx.translate(scaleOffset.x, scaleOffset.y)
ctx.scale(targetScale, targetScale)
ctx.fillStyle = "red";
ctx.fillRect(50, 50, 40, 40);
scale = targetScale;
}
.canvas-container {
box-sizing: border-box;
height: 80vh;
overflow: auto;
width: 100%;
padding: 0;
margin: 0;
border: 2px solid red;
position: relative;
}
.canvas {
min-height: calc(100% - 10px);
/* to avoid rounding issue related to fractional pixels calculation */
min-width: 100%;
/* to avoid rounding issue related to fractional pixels calculation */
background-image: linear-gradient( rgba(0, 0, 0, 0) 10px, rgba(0, 0, 0, .08) 11px), linear-gradient(90deg, rgba(0, 0, 0, 0) 10px, rgba(0, 0, 0, .08) 11px);
background-size: 11px 11px, 11px 11px;
box-sizing: border-box;
border: 3px solid green;
height: 100%;
}
<button onClick="zoom(event)">Zoom</button>
<div ref="canvasContainer" class="canvas-container">
<canvas id="canvas" class="canvas"></canvas>
</div>
问题:
缩放不居中:调整画布大小或更新滚动条后,缩放不会保持在鼠标位置居中。
滚动条未更新:我尝试使用scrollLeft和scrollTop值更新滚动位置,但它们似乎无法正确调整缩放。
画布调整大小会破坏缩放:当我动态调整画布大小时,缩放行为会被破坏,并且我会失去鼠标周围的正确焦点。
预期行为:
缩放行为:缩放应保持在鼠标位置的中心。
画布和滚动条更新:画布应变形,滚动条应根据缩放的内容移动。
无需调整大小:我不想直接调整画布大小。我希望变换能够处理缩放。
因此,我正在全球范围内尝试重现figma工坊的行为。
我尝试了很多方法但没有效果,我想知道我错过了什么
为什么当我调整画布大小并更新滚动条时缩放行为停止工作?如何保持缩放以鼠标为中心并正确更新滚动条而不导致意外行为?
NB:我模拟鼠标位置,位于矩形的中心,因为我通过单击按钮触发缩放。
请注意,当我注释掉画布大小的调整和滚动条的替换时,缩放可以工作,但这不是预期的行为
关键是使用
ctx.translate()
和 ctx.scale()
将变换应用于画布,而无需调整实际画布的大小。然后手动调整滚动位置以保持缩放以鼠标位置为中心。
let canvas, ctx;
let originalCanvasWidth, originalCanvasHeight;
setTimeout(() => {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
originalCanvasWidth = canvas.getBoundingClientRect().width;
originalCanvasHeight = canvas.getBoundingClientRect().height;
ctx.fillStyle = "red";
ctx.fillRect(50, 50, 40, 40);
}, 2000);
let scale = 1;
let offsetX = 0;
let offsetY = 0;
const zoom = (e) => {
const canvasContainer = document.querySelector(".canvas-container");
const rect = canvas.getBoundingClientRect();
// Simulated mouse position at the center of the rectangle
const mousex = 70; // Simulated X coordinate
const mousey = 70; // Simulated Y coordinate
// Calculate the position relative to the canvas's top left corner
const mouseXRelative = mousex - rect.left + canvasContainer.scrollLeft;
const mouseYRelative = mousey - rect.top + canvasContainer.scrollTop;
// Zoom in or out
const zoomStep = 0.1;
const direction = e.deltaY > 0 ? -zoomStep : zoomStep;
const newScale = scale + direction;
if (newScale < 0.1) return; // Prevent negative or too small scale
// Calculate scaling factor
const scaleFactor = newScale / scale;
// Adjust offsets to center zoom on the mouse position
offsetX = mouseXRelative - (mouseXRelative - offsetX) * scaleFactor;
offsetY = mouseYRelative - (mouseYRelative - offsetY) * scaleFactor;
// Apply transformations
ctx.setTransform(1, 0, 0, 1, 0, 0); // Reset transformation matrix
ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas
ctx.translate(offsetX, offsetY);
ctx.scale(newScale, newScale);
ctx.fillStyle = "red";
ctx.fillRect(50, 50, 40, 40);
// Adjust the container's scroll position to reflect zoom changes
canvasContainer.scrollLeft = offsetX * scaleFactor;
canvasContainer.scrollTop = offsetY * scaleFactor;
// Update scale
scale = newScale;
};
// Event listener for zoom (e.g., wheel event)
document.querySelector("button").addEventListener("click", zoom);
确保
canvas
具有正确的 css 属性来显示滚动条,并且 scrollLeft
和 scrollTop
根据转换后的内容进行更新。