与其他 HTML5 如何创建网格 问题不同,我想知道如何制作一个可拖动和可缩放的网格。
绘制网格非常简单:
var c = document.getElementById('canvas');
var ctx = c.getContext('2d');
var width = window.innerWidth;
var height = window.innerHeight;
c.width = width;
c.height = height;
drawGrid(width, height, 40);
function drawGrid(gridWidth, gridHeight, boxSize) {
ctx.clearRect(0, 0, c.width, c.height);
ctx.beginPath();
for (var i = 0; i <= gridWidth; i += boxSize) {
ctx.moveTo(i, 0);
ctx.lineTo(i, gridHeight);
}
for (var i = 0; i <= gridHeight; i += boxSize) {
ctx.moveTo(0, i);
ctx.lineTo(gridWidth, i);
}
ctx.strokeStyle = "rgba( 210, 210, 210, 1 )";
ctx.stroke();
}
html,
body {
overflow: hidden;
}
#canvas {
position: absolute;
top: 0;
left: 0;
}
<canvas id="canvas"></canvas>
现在要使其可拖动,有很多方法可以做到,但我专注于创建类似于此的无限移动网格的错觉:
Example Image (抱歉,积分还不够)
随着图形向右移动,从 Canvas 尺寸中隐藏的线被移回开头,反之亦然。我不太确定如何使用鼠标移动网格以及缩放。与 SVG 不同,缩放时线条往往会变得模糊。 创建无限移动和缩放网格的最快方法是什么?
编辑: 我采用了类似的方法来移动网格,使用图像图案来填充屏幕。
var c = document.getElementById("canvas"),
ctx = c.getContext("2d");
var width = window.innerWidth;
var height = window.innerHeight;
var itemIsSelected = false;
var clicked = function(e) {
var x = e.pageX;
var y = e.pageY;
}
draw(width, height);
draggable('#app');
function draw(width, height) {
c.width = width;
c.height = height;
generateBackground();
}
function draggable(item) {
var isMouseDown = false;
document.onmousedown = function(e) {
e.preventDefault();
clicked.x = e.pageX;
clicked.y = e.pageY;
$(item).css('cursor', 'all-scroll');
isMouseDown = true;
};
document.onmouseup = function(e) {
e.preventDefault();
isMouseDown = false;
$(item).css('cursor', 'default');
};
document.onmousemove = function(e) {
e.preventDefault();
if (isMouseDown == true) {
var mouseX = e.pageX;
var mouseY = e.pageY;
generateBackground(mouseX, mouseY, clicked.x, clicked.y);
}
};
}
function generateBackground(x, y, initX, initY) {
distanceX = x - initX;
distanceY = y - initY;
ctx.clearRect(0, 0, c.width, c.height);
var bgImage = document.getElementById("bg")
var pattern = ctx.createPattern(bgImage, "repeat");
ctx.rect(0, 0, width, height);
ctx.fillStyle = pattern;
ctx.fill();
ctx.translate(Math.sqrt(distanceX), Math.sqrt(distanceY));
}
html,
body {
overflow: hidden;
}
#canvas {
top: 0;
left: 0;
position: absolute;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="canvas"></canvas>
<img src="https://i.imgur.com/2MupHjw.png" id="bg" hidden>
此方法不允许我向左或向上滚动。它还会加快一段距离后的拖动速度,并且不能很好地处理负运动。
最佳答案
平移和缩放
我没有太多的时间,所以代码将不得不做大部分的解释。
下面的示例使用鼠标和鼠标滚轮进行平移和缩放。
对象 panZoom
包含有关缩放 (scale
) 和平移位置 (x
, y
) 的信息)
要平移,只需使用鼠标位置的变化来改变 panZoom
x
, y
位置。您不需要缩放鼠标移动,因为它们不受缩放的影响。
缩放是通过函数 panZoom.scaleAt(x,y,scale)
其中 x
, y
是鼠标位置和 scaleBy是缩放比例的量。例如 panZoom.scaleAt(100,100, 2)
将在位置 100,100 处放大 2 倍,而 panZoom.scaleAt(100,100, 1/2)
将同时缩小 2 倍位置。有关详细信息,请参阅 update
函数。
要在 panZoom
坐标系中绘制,您需要调用函数 panZoom.apply
来设置上下文转换以匹配 panZoom 的设置。函数 drawGrid
就是一个例子。它绘制一个网格以适应当前的平移和缩放。
请注意,要恢复正常的屏幕坐标,只需调用 ctx.setTransform(1,0,0,1,0,0)
,如果您想清除 Canvas ,则需要这样做。
片段更新 2021
我已经添加了
drawGrid(gridScreenSize, adaptive)
的参数gridScreenSize
屏幕像素中的网格大小(自适应模式)。在世界像素中(静态模式)adaptive
如果true
网格比例适应世界比例。false
网格大小是固定的。当 false 常量gridLimit
设置要渲染的最大行数
scaleRate
控制缩放率的常量。查看评论了解更多。显示文本以显示当前比例。
用于在静态和自适应网格大小之间切换的复选框。
const ctx = canvas.getContext("2d");
requestAnimationFrame(update);
const mouse = {x : 0, y : 0, button : false, wheel : 0, lastX : 0, lastY : 0, drag : false};
const gridLimit = 64; // max grid lines for static grid
const gridSize = 128; // grid size in screen pixels for adaptive and world pixels for static
const scaleRate = 1.02; // Closer to 1 slower rate of change
// Less than 1 inverts scaling change and same rule
// Closer to 1 slower rate of change
const topLeft = {x: 0, y: 0}; // holds top left of canvas in world coords.
function mouseEvents(e){
const bounds = canvas.getBoundingClientRect();
mouse.x = e.pageX - bounds.left - scrollX;
mouse.y = e.pageY - bounds.top - scrollY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
if(e.type === "wheel"){
mouse.wheel += -e.deltaY;
e.preventDefault();
}
}
["mousedown", "mouseup", "mousemove"].forEach(name => document.addEventListener(name,mouseEvents));
document.addEventListener("wheel",mouseEvents, {passive: false});
const panZoom = {
x : 0,
y : 0,
scale : 1,
apply() { ctx.setTransform(this.scale, 0, 0, this.scale, this.x, this.y) },
scaleAt(x, y, sc) { // x & y are screen coords, not world
this.scale *= sc;
this.x = x - (x - this.x) * sc;
this.y = y - (y - this.y) * sc;
},
toWorld(x, y, point = {}) { // converts from screen coords to world coords
const inv = 1 / this.scale;
point.x = (x - this.x) * inv;
point.y = (y - this.y) * inv;
return point;
},
}
function drawGrid(gridScreenSize = 128, adaptive = true){
var scale, gridScale, size, x, y, limitedGrid = false;
if (adaptive) {
scale = 1 / panZoom.scale;
gridScale = 2 ** (Math.log2(gridScreenSize * scale) | 0);
size = Math.max(w, h) * scale + gridScale * 2;
x = ((-panZoom.x * scale - gridScale) / gridScale | 0) * gridScale;
y = ((-panZoom.y * scale - gridScale) / gridScale | 0) * gridScale;
} else {
gridScale = gridScreenSize;
size = Math.max(w, h) / panZoom.scale + gridScale * 2;
panZoom.toWorld(0,0, topLeft);
x = Math.floor(topLeft.x / gridScale) * gridScale;
y = Math.floor(topLeft.y / gridScale) * gridScale;
if (size / gridScale > gridLimit) {
size = gridScale * gridLimit;
limitedGrid = true;
}
}
panZoom.apply();
ctx.lineWidth = 1;
ctx.strokeStyle = "#000";
ctx.beginPath();
for (i = 0; i < size; i += gridScale) {
ctx.moveTo(x + i, y);
ctx.lineTo(x + i, y + size);
ctx.moveTo(x, y + i);
ctx.lineTo(x + size, y + i);
}
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset the transform so the lineWidth is 1
ctx.stroke();
info.textContent = "Scale: 1px = " + (1/panZoom.scale).toFixed(4) + " world px ";
limitedGrid && (info.textContent += " Static grid limit " + (gridLimit * gridLimit) + " cells");
}
function drawPoint(x, y) {
const worldCoord = panZoom.toWorld(x, y);
panZoom.apply();
ctx.lineWidth = 1;
ctx.strokeStyle = "red";
ctx.beginPath();
ctx.moveTo(worldCoord.x - 10, worldCoord.y);
ctx.lineTo(worldCoord.x + 10, worldCoord.y);
ctx.moveTo(worldCoord.x, worldCoord.y - 10);
ctx.lineTo(worldCoord.x, worldCoord.y + 10);
ctx.setTransform(1, 0, 0, 1, 0, 0); //reset the transform so the lineWidth is 1
ctx.stroke();
}
var w = canvas.width;
var h = canvas.height;
function update(){
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.globalAlpha = 1; // reset alpha
if (w !== innerWidth || h !== innerHeight) {
w = canvas.width = innerWidth;
h = canvas.height = innerHeight;
} else {
ctx.clearRect(0, 0, w, h);
}
if (mouse.wheel !== 0) {
let scale = 1;
scale = mouse.wheel < 0 ? 1 / scaleRate : scaleRate;
mouse.wheel *= 0.8;
if(Math.abs(mouse.wheel) < 1){
mouse.wheel = 0;
}
panZoom.scaleAt(mouse.x, mouse.y, scale); //scale is the change in scale
}
if (mouse.button) {
if (!mouse.drag) {
mouse.lastX = mouse.x;
mouse.lastY = mouse.y;
mouse.drag = true;
} else {
panZoom.x += mouse.x - mouse.lastX;
panZoom.y += mouse.y - mouse.lastY;
mouse.lastX = mouse.x;
mouse.lastY = mouse.y;
}
} else if (mouse.drag) {
mouse.drag = false;
}
drawGrid(gridSize, adaptiveGridCb.checked);
drawPoint(mouse.x, mouse.y);
requestAnimationFrame(update);
}
canvas { position : absolute; top : 0px; left : 0px; }
div {
position : absolute;
top : 5px;
left : 5px;
font-family: arial;
font-size: 16px;
background: #FFFD;
}
<canvas id="canvas"></canvas>
<div>
<label for="adaptiveGridCb">Adaptive grid</label>
<input id="adaptiveGridCb" type="checkbox" checked/>
<span id="info"></span>
</div>
关于javascript - 在 HTML5 中创建可拖动和可缩放的网格,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53310138/