// get canvas element
var canvas = document.getElementById('canvas');
var canvasWidth = 500;
var canvasHeight = 500;
var ctx = canvas.getContext('2d');
// create a second canvas, only for the ImageData
var imageDataCanvas = document.createElement('canvas');
// <note>
// if you don't read the data, don't call getImageData
// simply create a new blank ImageData
// </note>
var imageData = ctx.createImageData(canvasWidth, canvasHeight);
// set the canvas size to the ImageData's size
imageDataCanvas.width = imageData.width;
imageDataCanvas.height = imageData.height;
var imageDataCtx = imageDataCanvas.getContext("2d");
// <note>
// don't assign a second buffer
// eats memory for nothing and .set is not that fast
// instead work directly with the one from the ImageData
// </note>
var buf = imageData.data.buffer;
var data = new Uint32Array(buf);
// made a function so it changes every frame
function makeNoise() {
// little noise
for (var y = 0; y < canvasHeight; ++y) {
for (var x = 0; x < canvasWidth; ++x) {
var value = x * y & 0xff;
data[y * canvasWidth + x] =
(255 << 24) | // alpha
(Math.floor(Math.random() * 256) << 24) | // blue
(Math.floor(Math.random() * 256) << 12) | // green
Math.floor(Math.random() * 256) << 6; // red
}
}
}
// no need to set anything anymore, we did modify the ArrayBuffer directly
// lets start zooming
// max and minimum level of zooming
let MAX_ZOOM = 5;
let MIN_ZOOM = 0.5;
let SCROLL_SENSITIVITY = 0.0005;
let cameraZoom = 1;
function adjustZoom(zoomAmount) {
if (zoomAmount)
{
cameraZoom += zoomAmount;
}
cameraZoom = Math.min( cameraZoom, MAX_ZOOM );
cameraZoom = Math.max( cameraZoom, MIN_ZOOM );
}
function draw() {
// update the pixels
makeNoise();
// draw the pixels on the second canvas
// <note>
// no need to clear this context
// putImageData takes care of it for us
// </note>
imageDataCtx.putImageData(imageData, 0, 0);
// <note>
// don't set the width or height of your canvas in the rendering loop
// resizing the context will reset all the properties of the context to their default
// which is already slow enough
// but it will even reassign a new drawing-buffer
// meaning GC will have to kick-in to get back the previous buffer
// (at least in Chromium browsers)
// </note>
// reset to identity matrix
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvasWidth, canvasHeight)
// set scaling origin
ctx.translate( canvasWidth / 2, canvasHeight / 2 );
// scaling
ctx.scale(cameraZoom, cameraZoom);
// move back for drawing
ctx.translate( -canvasWidth / 2, -canvasHeight / 2 );
// <note> clear only at identity matrix </note>
// draw pixels
// avoid blur
ctx.imageSmoothingEnabled = false;
ctx.drawImage(imageDataCanvas, 0, 0);
// draw rectangle
ctx.fillStyle = "#FF0000";
ctx.fillRect( 100, 100, 100, 100 );
requestAnimationFrame( draw );
}
// wheel listener
canvas.addEventListener( 'wheel', (e) => {
e.preventDefault(); // prevent scroll
adjustZoom(e.deltaY*SCROLL_SENSITIVITY);
});
draw();
canvas {
position: absolute;
top: 0;
left: 0;
image-rendering: pixelated;
}
<canvas id="canvas" width="500" height="500"></canvas>