I'm using Canvas2D and the (CanvasRenderingContext2D)[https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D] in the web to make a game (using TypeScript). What I'm trying to achieve is saving the current state of the canvas render, similarly to a screenshot, and storing it in memory. I want this so that I can create a mini-map of my world, without overhead of re-rendering. Is this feasible, or does this require a WebGL renderer? Couldn't find anything about this while googling around
Some psuedo-code of what I'm trying to achieve
//First we would render the world
//...
//Then save a render of the canvas
var texture = ctx.saveRenderOrSomeShit();
//Render hud
//...
//Render the world as part of the hud to top right or something similar
ctx.fillRect(texture, etc etc etc);
//Done rendering
Thanks for reading!
ctx.drawImage()
does accept HTMLCanvasElement
or OffscreenCanvas
instances as input, so you could create one such canvas, draw there, and then use it as your texture:
const texture = new OffscreenCanvas(50, 50);
const texCtx = texture.getContext("2d");
texCtx.fillStyle = "red";
texCtx.roundRect(0, 0, 50, 50, [30, 10]);
texCtx.fill();
const main = document.querySelector("canvas");
const ctx = main.getContext("2d");
main.addEventListener("mousemove", (evt) => {
ctx.clearRect(0, 0, main.width, main.height);
const x = evt.clientX - main.offsetLeft - texture.width / 2;
const y = evt.clientY - main.offsetTop - texture.height / 2;
ctx.drawImage(texture, x, y);
});
ctx.drawImage(texture, 50, 50);
canvas { border: 1px solid }
<canvas></canvas>
However having one full canvas + 2D context per texture is a bit consumptive memory wise, so instead you could create ImageBitmap
objects from your canvas, but this is async, so you'd need to prepare them ahead of time.
(async () => {
const main = document.querySelector("canvas");
main.width = 50;
main.height = 50;
const ctx = main.getContext("2d");
ctx.fillStyle = "red";
ctx.roundRect(0, 0, 50, 50, [30, 10]);
ctx.fill();
const texture = await createImageBitmap(main);
main.width = 300;
main.height = 150;
main.addEventListener("mousemove", (evt) => {
ctx.clearRect(0, 0, main.width, main.height);
const x = evt.clientX - main.offsetLeft - texture.width / 2;
const y = evt.clientY - main.offsetTop - texture.height / 2;
ctx.drawImage(texture, x, y);
});
ctx.drawImage(texture, 50, 50);
})();
canvas { border: 1px solid }
<canvas></canvas>
And since it seems that you were hoping to use this texture as a fillStyle
, then note that you can also use a canvas as source for createPattern()
:
const main = document.querySelector("canvas");
main.width = 50;
main.height = 50;
const ctx = main.getContext("2d");
ctx.fillStyle = "red";
ctx.roundRect(0, 0, 50, 50, [30, 10]);
ctx.fill();
const texture = ctx.createPattern(main, "no-repeat");
main.width = 300;
main.height = 150;
ctx.fillStyle = texture;
main.addEventListener("mousemove", (evt) => {
ctx.resetTransform();
ctx.clearRect(0, 0, main.width, main.height);
const x = evt.clientX - main.offsetLeft - 50 / 2;
const y = evt.clientY - main.offsetTop - 50 / 2;
// Patterns position is always relative to the context's CTM
// So we need to transform it rather than just using x, y of our path
ctx.translate(x, y);
ctx.fillRect(0, 0, 50, 50);
});
ctx.translate(50, 50);
ctx.fillRect(0, 0, 50, 50);
canvas { border: 1px solid }
<canvas></canvas>