Nig*_*ige 3 html javascript canvas html5-canvas
我正在努力找出并确定如何根据此示例放大我的鼠标位置。(https://stackblitz.com/edit/js-fxnmkm?file=index.js)
let node,
scale = 1,
posX = 0,
posY = 0,
node = document.querySelector('.frame');
const render = () => {
window.requestAnimationFrame(() => {
let val = `translate3D(${posX}px, ${posY}px, 0px) scale(${scale})`
node.style.transform = val
})
}
window.addEventListener('wheel', (e) => {
e.preventDefault();
// Zooming happens here
if (e.ctrlKey) {
scale -= e.deltaY * 0.01;
} else {
posX -= e.deltaX * 2;
posY -= e.deltaY * 2;
}
render();
});
Run Code Online (Sandbox Code Playgroud)
我想要的效果是基于这个例子(https://codepen.io/techslides/pen/zowLd?editors=0010)放大时。目前我上面的例子只缩放到“视口”的中心,但我希望它是我的光标当前所在的位置。
我已经在高低搜索了一个不是通过画布实现的解决方案。任何帮助,将不胜感激!
警告我使用滚轮事件的原因是为了模仿 Figma(设计工具)平移和缩放的交互。
Bli*_*n67 10
缩放和平移元素非常成问题。它可以完成,但问题列表很长。我永远不会实现这样的接口。
考虑使用画布,通过 2D 或 WebGL 来显示这样的内容,以节省您自己的许多问题。
答案的第一部分是使用画布实现的。一样的界面view
在平移和缩放元素的第二个示例中使用了。
由于您只是平移和缩放,因此可以使用一种非常简单的方法。
下面的示例实现了一个名为 view 的对象。这保存当前的比例和位置(平移)
它为用户交互提供了两个功能。
view.pan(amount)
将按距离(以像素为单位)平移视图amount.x
,amount.y
view.scaleAt(at, amount)
将缩放(在出变焦)由视图amount
(表示比例改变的数),在所保存的位置at.x
,at.y
以像素为单位。在示例中,视图被应用到画布渲染上下文中,view.apply()
并且每当视图发生变化时都会渲染一组随机框。平移和缩放是通过鼠标事件
使用鼠标按钮拖动平移,滚轮缩放
const ctx = canvas.getContext("2d");
canvas.width = 500;
canvas.height = 500;
const rand = (m = 255, M = m + (m = 0)) => (Math.random() * (M - m) + m) | 0;
const objects = [];
for (let i = 0; i < 100; i++) {
objects.push({x: rand(canvas.width), y: rand(canvas.height),w: rand(40),h: rand(40), col: `rgb(${rand()},${rand()},${rand()})`});
}
requestAnimationFrame(drawCanvas);
const view = (() => {
const matrix = [1, 0, 0, 1, 0, 0]; // current view transform
var m = matrix; // alias
var scale = 1; // current scale
var ctx; // reference to the 2D context
const pos = { x: 0, y: 0 }; // current position of origin
var dirty = true;
const API = {
set context(_ctx) { ctx = _ctx; dirty = true },
apply() {
if (dirty) { this.update() }
ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5])
},
get scale() { return scale },
get position() { return pos },
isDirty() { return dirty },
update() {
dirty = false;
m[3] = m[0] = scale;
m[2] = m[1] = 0;
m[4] = pos.x;
m[5] = pos.y;
},
pan(amount) {
if (dirty) { this.update() }
pos.x += amount.x;
pos.y += amount.y;
dirty = true;
},
scaleAt(at, amount) { // at in screen coords
if (dirty) { this.update() }
scale *= amount;
pos.x = at.x - (at.x - pos.x) * amount;
pos.y = at.y - (at.y - pos.y) * amount;
dirty = true;
},
};
return API;
})();
view.context = ctx;
function drawCanvas() {
if (view.isDirty()) {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
view.apply(); // set the 2D context transform to the view
for (i = 0; i < objects.length; i++) {
var obj = objects[i];
ctx.fillStyle = obj.col;
ctx.fillRect(obj.x, obj.y, obj.h, obj.h);
}
}
requestAnimationFrame(drawCanvas);
}
canvas.addEventListener("mousemove", mouseEvent, {passive: true});
canvas.addEventListener("mousedown", mouseEvent, {passive: true});
canvas.addEventListener("mouseup", mouseEvent, {passive: true});
canvas.addEventListener("mouseout", mouseEvent, {passive: true});
canvas.addEventListener("wheel", mouseWheelEvent, {passive: false});
const mouse = {x: 0, y: 0, oldX: 0, oldY: 0, button: false};
function mouseEvent(event) {
if (event.type === "mousedown") { mouse.button = true }
if (event.type === "mouseup" || event.type === "mouseout") { mouse.button = false }
mouse.oldX = mouse.x;
mouse.oldY = mouse.y;
mouse.x = event.offsetX;
mouse.y = event.offsetY
if(mouse.button) { // pan
view.pan({x: mouse.x - mouse.oldX, y: mouse.y - mouse.oldY});
}
}
function mouseWheelEvent(event) {
var x = event.offsetX;
var y = event.offsetY;
if (event.deltaY < 0) { view.scaleAt({x, y}, 1.1) }
else { view.scaleAt({x, y}, 1 / 1.1) }
event.preventDefault();
}
Run Code Online (Sandbox Code Playgroud)
body {
background: gainsboro;
margin: 0;
}
canvas {
background: white;
box-shadow: 1px 1px 1px rgba(0, 0, 0, .2);
}
Run Code Online (Sandbox Code Playgroud)
<canvas id="canvas"></canvas>
Run Code Online (Sandbox Code Playgroud)
element.style.transform
此示例使用元素样式变换属性来缩放和平移。
请注意,我使用 2D 矩阵而不是 3d 矩阵,因为这会引入许多与下面使用的简单缩放和平移不兼容的问题。
请注意,并非在所有情况下都将 CSS 转换应用于元素的左上角。在下面的示例中,原点位于元素的中心。因此,在缩放点时,必须通过减去元素大小的一半来调整缩放。元素大小不受变换的影响。
注意边框、内边距和边距也会改变原点的位置。使用view.scaleAt(at, amount)
at
必须相对于元素的左上角最像素
请注意,当您缩放和平移元素时,您需要考虑更多问题和注意事项,太多而无法放在一个答案中。这就是为什么这个答案从画布示例开始,因为它是迄今为止管理可缩放视觉内容的更安全的方法。
使用鼠标按钮拖动平移,滚轮缩放。如果您失去了位置(将页面放大或平移过远,请重新启动代码段)
const view = (() => {
const matrix = [1, 0, 0, 1, 0, 0]; // current view transform
var m = matrix; // alias
var scale = 1; // current scale
const pos = { x: 0, y: 0 }; // current position of origin
var dirty = true;
const API = {
applyTo(el) {
if (dirty) { this.update() }
el.style.transform = `matrix(${m[0]},${m[1]},${m[2]},${m[3]},${m[4]},${m[5]})`;
},
update() {
dirty = false;
m[3] = m[0] = scale;
m[2] = m[1] = 0;
m[4] = pos.x;
m[5] = pos.y;
},
pan(amount) {
if (dirty) { this.update() }
pos.x += amount.x;
pos.y += amount.y;
dirty = true;
},
scaleAt(at, amount) { // at in screen coords
if (dirty) { this.update() }
scale *= amount;
pos.x = at.x - (at.x - pos.x) * amount;
pos.y = at.y - (at.y - pos.y) * amount;
dirty = true;
},
};
return API;
})();
document.addEventListener("mousemove", mouseEvent, {passive: false});
document.addEventListener("mousedown", mouseEvent, {passive: false});
document.addEventListener("mouseup", mouseEvent, {passive: false});
document.addEventListener("mouseout", mouseEvent, {passive: false});
document.addEventListener("wheel", mouseWheelEvent, {passive: false});
const mouse = {x: 0, y: 0, oldX: 0, oldY: 0, button: false};
function mouseEvent(event) {
if (event.type === "mousedown") { mouse.button = true }
if (event.type === "mouseup" || event.type === "mouseout") { mouse.button = false }
mouse.oldX = mouse.x;
mouse.oldY = mouse.y;
mouse.x = event.pageX;
mouse.y = event.pageY;
if(mouse.button) { // pan
view.pan({x: mouse.x - mouse.oldX, y: mouse.y - mouse.oldY});
view.applyTo(zoomMe);
}
event.preventDefault();
}
function mouseWheelEvent(event) {
const x = event.pageX - (zoomMe.width / 2);
const y = event.pageY - (zoomMe.height / 2);
if (event.deltaY < 0) {
view.scaleAt({x, y}, 1.1);
view.applyTo(zoomMe);
} else {
view.scaleAt({x, y}, 1 / 1.1);
view.applyTo(zoomMe);
}
event.preventDefault();
}
Run Code Online (Sandbox Code Playgroud)
body {
user-select: none;
-moz-user-select: none;
}
.zoomables {
pointer-events: none;
border: 1px solid black;
}
#zoomMe {
position: absolute;
top: 0px;
left: 0px;
}
Run Code Online (Sandbox Code Playgroud)
<img id="zoomMe" class="zoomables" src="https://i.stack.imgur.com/C7qq2.png?s=328&g=1">
Run Code Online (Sandbox Code Playgroud)
第二个链接的放大有点极端,所以我尝试添加一些约束。您可以取消注释并玩更多。目前,外观和工作原理与恕我直言完全相同。
const container = document.querySelector('.container');
const image = document.querySelector('.image');
const speed = 0.5;
let size = {
w: image.offsetWidth,
h: image.offsetHeight
};
let pos = { x: 0, y: 0 };
let target = { x: 0, y: 0 };
let pointer = { x: 0, y: 0 };
let scale = 1;
window.addEventListener('wheel', event => {
event.preventDefault();
pointer.x = event.pageX - container.offsetLeft;
pointer.y = event.pageY - container.offsetTop;
target.x = (pointer.x - pos.x) / scale;
target.y = (pointer.y - pos.y) / scale;
scale += -1 * Math.max(-1, Math.min(1, event.deltaY)) * speed * scale;
// Uncomment to constrain scale
// const max_scale = 4;
// const min_scale = 1;
// scale = Math.max(min_scale, Math.min(max_scale, scale));
pos.x = -target.x * scale + pointer.x;
pos.y = -target.y * scale + pointer.y;
// Uncomment for keeping the image within area (works with min scale = 1)
// if (pos.x > 0) pos.x = 0;
// if (pos.x + size.w * scale < size.w) pos.x = -size.w * (scale - 1);
// if (pos.y > 0) pos.y = 0;
// if (pos.y + size.h * scale < size.h) pos.y = -size.h * (scale - 1);
image.style.transform = `translate(${pos.x}px,${pos.y}px) scale(${scale},${scale})`;
}, { passive: false });
Run Code Online (Sandbox Code Playgroud)
.container {
width: 400px;
height: 400px;
overflow: hidden;
outline: 1px solid gray;
}
.image {
width: 100%;
height: 100%;
transition: transform .3s;
transform-origin: 0 0;
}
img {
width: auto;
height: auto;
max-width: 100%;
}
Run Code Online (Sandbox Code Playgroud)
<div class="container">
<div class="image">
<img src="https://picsum.photos/400/400" />
</div>
</div>
Run Code Online (Sandbox Code Playgroud)