问题:我正在使用HTML画布。我的画布有一个背景图像,多个人可以实时绘制(通过socket.io),但如果放大则绘制中断。
原因:要计算从哪里开始和结束一行,我将捕获时的输入规范化为0到1(包括0和1),如下所示:
// Pseudocode
line.x = mousePosition.x / canvas.width;
line.y = mousePosition.y / canvas.height;
因此,canvas
可以是任何大小和任何位置。
要实现缩放滚动功能,我只需translate
根据当前鼠标位置,scale
画布按因子2,然后translate
返回负值当前鼠标位置(建议here)。
问题出在哪里
当我缩放时,画布似乎没有原始尺寸的概念。
例如,假设我有1000px的方形画布。使用上面标准化的x
和y
,左上角为0, 0
,右下角为1, 1
。
然后我通过缩放2倍缩放到中心。我希望我的新左上角是0.5, 0.5
而我的右下角是0.75, 0.75
,但事实并非如此。即使我放大,左上角仍为0, 0
,右下角为1, 1
。
结果是,当我放大并绘制时,线条会出现在我们根本没有放大的位置。如果我放大到中间并在左上角“画了”,我会看到什么,直到我滚动出来看到这条线实际上是在原来的左上角绘制的。
我需要知道的事情:当缩放时,是否有办法了解新原点相对于未缩放画布的内容,或者隐藏了多少画布?这些中的任何一个都可以放大并绘制并使其正确跟踪。
如果我完全不在这里,并且有更好的方法来解决这个问题,我会全力以赴。如果您需要其他信息,我会尽我所能。
答案 0 :(得分:0)
使用HTMLElement.prototype.getBoundingClientRect()获取DOM中画布的显示大小和位置。从显示的大小和原始大小,计算画布的比例。
示例:
canvas.addEventListener("click", function (event) {
var b = canvas.getBoundingClientRect();
var scale = canvas.width / parseFloat(b.width);
var x = (event.clientX - b.left) * scale;
var y = (event.clientY - b.top) * scale;
// Marks mouse position
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.arc(x, y, 10, 0, 2 * Math.PI);
ctx.stroke();
});
答案 1 :(得分:0)
我不清楚您所说的“放大”是什么意思。
缩放=
使画布的尺寸不同了吗?
更改了画布上的变换
使用了CSS转换吗?
使用CSS缩放了吗?
我将假设它是transform
在画布上,在这种情况下,它就像
function getElementRelativeMousePosition(e) {
return [e.offsetX, e.offsetY];
}
function getCanvasRelativeMousePosition(e) {
const pos = getElementRelativeMousePosition(e);
pos[0] = pos[0] * ctx.canvas.width / ctx.canvas.clientWidth;
pos[1] = pos[1] * ctx.canvas.height / ctx.canvas.clientHeight;
return pos;
}
function getComputedMousePosition(e) {
const pos = getCanvasRelativeMousePosition(e);
const p = new DOMPoint(...pos);
const point = inverseOriginTransform.transformPoint(p);
return [point.x, point.y];
}
inverseOriginTransform
与您用来缩放和滚动画布内容的任何变换相反。
const settings = {
zoom: 1,
xoffset: 0,
yoffset: 0,
};
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const lines = [
[[100, 10], [200, 30]],
[[50, 50], [100, 30]],
];
let newStart;
let newEnd;
let originTransform = new DOMMatrix();
let inverseOriginTransform = new DOMMatrix();
function setZoomAndOffsetTransform() {
originTransform = new DOMMatrix();
originTransform.translateSelf(settings.xoffset, settings.yoffset);
originTransform.scaleSelf(settings.zoom, settings.zoom);
inverseOriginTransform = originTransform.inverse();
}
const ui = document.querySelector('#ui')
addSlider(settings, 'zoom', ui, 0.25, 3, draw);
addSlider(settings, 'xoffset', ui, -100, +100, draw);
addSlider(settings, 'yoffset', ui, -100, +100, draw);
draw();
function updateAndDraw() {
draw();
}
function getElementRelativeMousePosition(e) {
return [e.offsetX, e.offsetY];
}
function getCanvasRelativeMousePosition(e) {
const pos = getElementRelativeMousePosition(e);
pos[0] = pos[0] * ctx.canvas.width / ctx.canvas.clientWidth;
pos[1] = pos[1] * ctx.canvas.height / ctx.canvas.clientHeight;
return pos;
}
function getTransformRelativeMousePosition(e) {
const pos = getCanvasRelativeMousePosition(e);
const p = new DOMPoint(...pos);
const point = inverseOriginTransform.transformPoint(p);
return [point.x, point.y];
}
canvas.addEventListener('mousedown', (e) => {
const pos = getTransformRelativeMousePosition(e);
if (newStart) {
} else {
newStart = pos;
newEnd = pos;
}
});
canvas.addEventListener('mousemove', (e) => {
if (newStart) {
newEnd = getTransformRelativeMousePosition(e);
draw();
}
});
canvas.addEventListener('mouseup', (e) => {
if (newStart) {
lines.push([newStart, newEnd]);
newStart = undefined;
}
});
function draw() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.save();
setZoomAndOffsetTransform();
ctx.setTransform(
originTransform.a,
originTransform.b,
originTransform.c,
originTransform.d,
originTransform.e,
originTransform.f);
ctx.beginPath();
for (const line of lines) {
ctx.moveTo(...line[0]);
ctx.lineTo(...line[1]);
}
if (newStart) {
ctx.moveTo(...newStart);
ctx.lineTo(...newEnd);
}
ctx.stroke();
ctx.restore();
}
function addSlider(obj, prop, parent, min, max, callback) {
const valueRange = max - min;
const sliderRange = 100;
const div = document.createElement('div');
div.class = 'range';
const input = document.createElement('input');
input.type = 'range';
input.min = 0;
input.max = sliderRange;
const label = document.createElement('span');
label.textContent = `${prop}: `;
const valueElem = document.createElement('span');
function setInputValue(v) {
input.value = (v - min) * sliderRange / valueRange;
}
input.addEventListener('input', (e) => {
const v = parseFloat(input.value) * valueRange / sliderRange + min;
valueElem.textContent = v.toFixed(1);
obj[prop] = v;
callback();
});
const v = obj[prop];
valueElem.textContent = v.toFixed(1);
setInputValue(v);
div.appendChild(input);
div.appendChild(label);
div.appendChild(valueElem);
parent.appendChild(div);
}
canvas { border: 1px solid black; }
#app { display: flex; }
<div id="app"><canvas></canvas><div id="ui"></div>
注意:我没有费心将缩放始终从中心缩放。为此,需要在缩放更改时调整xoffset和yoffset。