Search code examples
javascriptcssnode.jscanvashtml5-canvas

How can I make infinite grid with canvas?


Nice example-grid can be in this site: https://playgameoflife.com, where you can click and move around this grid. So I would like to learn how to make such an endless grid that you can move on.

Snippet: https://jsfiddle.net/omar_red/wfsLuynd/1/

canvas = document.querySelector('.field');
ctx = canvas.getContext('2d');

canvas.width = window.screen.width;
canvas.height = window.screen.height;


for (let x = 0.5; x < canvas.width; x += 10) {
    ctx.moveTo(x, 0);
    ctx.lineTo(x, canvas.height);
}

for (let y = 0.5; y < canvas.height; y += 10) {
    ctx.moveTo(0, y);
    ctx.lineTo(canvas.width, y);
}

ctx.strokeStyle = "#888";
ctx.stroke();
body {
  margin: 0;
  padding: 0;
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>Infinite Grid</title>
</head>
<body>
  <canvas class="field"></canvas>
</body>
</html>


Solution

  • You would have to handle the mouse events to know how much the cursor is moving with the left mouse button down.

    The idea is then to move the coordinate system of the canvas by the same amounts as the cursor is moving. To avoid that blanks appear at the side we are moving away from, draw the grid 3 times as wide and high. That way you cannot get to the edge of the grid with one "drag" operation.

    Then, when the button is released, restore the coordinate system back to its original state. So actually you undo the whole move. This is not apparent to the user, who gets the impression the grid just stops moving and snaps to a nice spot.

    If you have real content in your "world" (like Conway's cells), then you will need to track how much your world coordinates have moved, and of course, those would not flip back to the original state. To fill in the cells in your grid, you would need to map world coordinates to grid coordinates. I have not covered this aspect here, as that would lead too far from your question.

    So in the below implementation there is just the grid moving, but there is no notion of world coordinates or world content:

    let canvas = document.querySelector('.field');
    let ctx = canvas.getContext('2d');
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    
    function draw() {
        let step = 10;
        let left = 0.5 - Math.ceil(canvas.width / step) * step;
        let top = 0.5 - Math.ceil(canvas.height / step) * step;
        let right = 2*canvas.width;
        let bottom = 2*canvas.height;
        ctx.clearRect(left, top, right - left, bottom - top);
        ctx.beginPath();
        for (let x = left; x < right; x += step) {
            ctx.moveTo(x, top);
            ctx.lineTo(x, bottom);
        }
        for (let y = top; y < bottom; y += step) {
            ctx.moveTo(left, y);
            ctx.lineTo(right, y);
        }
        ctx.strokeStyle = "#888";
        ctx.stroke();
    }
    
    
    // Mouse event handling:
    let start;
    const getPos = (e) => ({
        x: e.clientX - canvas.offsetLeft,
        y: e.clientY - canvas.offsetTop 
    });
    
    const reset = () => {
        start = null;
        ctx.setTransform(1, 0, 0, 1, 0, 0); // reset translation
        draw();
    }
    
    canvas.addEventListener("mousedown", e => {
        reset();
        start = getPos(e)
    });
    
    canvas.addEventListener("mouseup", reset);
    canvas.addEventListener("mouseleave", reset);
    
    canvas.addEventListener("mousemove", e => {
        // Only move the grid when we registered a mousedown event
        if (!start) return;
        let pos = getPos(e);
        // Move coordinate system in the same way as the cursor
        ctx.translate(pos.x - start.x, pos.y - start.y);
        draw();
        start = pos;
    });
    
    draw(); // on page load
    body, html {
        width: 100%;
        height: 100%;
        margin: 0;
        padding:0;
        overflow:hidden;
    }
    
    canvas { background: silver; }
    <canvas class="field"></canvas>