在HTML5中创建可拖动和可缩放的网格

时间:2018-11-14 23:11:17

标签: javascript jquery html html5-canvas

与其他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 (对不起,还没有足够的学分)

当图形向右移动时,画布大小中隐藏的线将移回到开头,反之亦然。我不太确定如何使用鼠标移动网格以及缩放比例。与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>

此方法不允许我向左或向上滚动。它也可以加速一定距离后的拖动,并且不能很好地处理负向运动。

1 个答案:

答案 0 :(得分:4)

平移和缩放

我没有太多的常规,所以代码将不得不做大部分解释。

下面的示例使用鼠标和鼠标滚轮进行平移和缩放。

对象panZoom保存有关缩放(scale)和平移位置(xy)的信息

要平移,只需使用鼠标位置的更改来更改panZoom xy的位置。您无需缩放鼠标移动,因为它们不会受到缩放的影响。

通过功能panZoom.scaleAt(x,y,scale)进行缩放,其中xy是鼠标位置,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),如果要清除画布,则需要这样做。

const ctx = canvas.getContext("2d");
requestAnimationFrame(update);
const mouse  = {x : 0, y : 0, button : false, wheel : 0, lastX : 0, lastY : 0, drag : false};


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", "wheel"].forEach(name => document.addEventListener(name,mouseEvents));


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;
    },
}

function drawGrid(){
    const scale = 1 / panZoom.scale;
    var gridScale = 2 ** (Math.log2(128 * scale) | 0);
    var size = Math.max(w, h) * scale + gridScale * 2;
    var x = ((-panZoom.x * scale - gridScale) / gridScale | 0) * gridScale;
    var y = ((-panZoom.y * scale - gridScale) / gridScale | 0) * gridScale;
    panZoom.apply();
    ctx.lineWidth = 1;
    ctx.strokeStyle = "black";
    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();
}   


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 / 1.01 : 1.01;
        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();
    requestAnimationFrame(update);
}
canvas { position : absolute; top : 0px; left : 0px; }
<canvas id="canvas"></canvas>