Делаю игру и решил на слоях Canvas размещать независимые локации. При включении одного слоя отключается предыдущий. Так я рассчитывал минимизировать нагрузку на компьютер (всегда работает код обслуживающий только одну страницу), но на практике переходя с одной локации на другую начинает притормаживать и в итоге зависает код (если щелкать туда обратно). Для читабельности упростил все и выложил пример без анимации и картинок, но при долгом переключении даже он зависает, а при обнавлении страници все начинает работать хорошо (моментально). Для создания слоев использую код CanvasStack-2v01.js
Куда тратятся ресурсы и как правильно применять слои?
Код HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="../Game/CSS/style.css">
<title>Document game</title>
</head>
<body>
<canvas id="canvas" width = 400 height = 200></canvas>
<script src="../Game/JS/CanvasStack-2v01.js"></script>
<script src="../Game/JS/layerOne.js"></script>
<script src="../Game/JS/layerTwo.js"></script>
</body>
</html>
Файл с первым слоем LayerOne.js
let launchLayerOne = function() {
let canvas_stack = new CanvasStack('canvas');
const layer1 = canvas_stack.createLayer();
const layer1_canvas = document.getElementById(layer1);
const layer1_ctx = layer1_canvas.getContext('2d');
layer1_canvas.style.background = 'black';
game();
function game() {
render();
requestAnimationFrame(game);
};
function render() {
layer1_ctx.rect(50, 50, 100, 100);
layer1_ctx.fillStyle = 'rgba(247, 7, 7, 0.8)';
layer1_ctx.fill();
};
layer1_canvas.onmousedown = function(e) {
let rect = this.getBoundingClientRect();
x = e.clientX - rect.left;
y = e.clientY - rect.top;
let detect
layer1_ctx.isPointInPath(x, y) ? detect = 1 : detect = 0;
if(detect === 1) {
launchLayerTwo();
canvas_stack.deleteLayer(layer1);
}
};
};
launchLayerOne()
Файл со вторым слоем LayerTwo.js
let launchLayerTwo = function() {
let canvas_stack = new CanvasStack('canvas');
const layer2 = canvas_stack.createLayer();
const layer2_canvas = document.getElementById(layer2);
const layer2_ctx = layer2_canvas.getContext('2d');
layer2_canvas.style.background = 'grey';
game();
function game() {
render();
requestAnimationFrame(game);
};
function render() {
layer2_ctx.rect(250, 50, 100, 100);
layer2_ctx.fillStyle = 'rgba(13, 171, 10, 0.8)';
layer2_ctx.fill();
};
layer2_canvas.onmousedown = function(e) {
let rect = this.getBoundingClientRect();
x = e.clientX - rect.left;
y = e.clientY - rect.top;
let detect
layer2_ctx.isPointInPath(x, y) ? detect = 1 : detect = 0;
if(detect === 1) {
launchLayerOne();
canvas_stack.deleteLayer(layer2);
}
};
};
Файл с кодом для создания слоев CanvasStack-2v01.js
var CanvasStack;
(function()
{
"use strict";
class Layer
{
constructor(canvasID, canvasElement)
{
this.id = canvasID;
this.cElem = canvasElement;
this.dragObjects = [];
}
}
CanvasStack = class{
constructor(cvsID, stackLimit){
const savThis = this;
function setResizeHandler(resizeLayers, timeout){
let timer_id = undefined;
window.addEventListener("resize", ()=>{
if(timer_id != undefined)
{
clearTimeout(timer_id);
timer_id = undefined;
}
timer_id = setTimeout(()=>{
timer_id = undefined;
resizeLayers();
savThis.bkgCanvas.resizeFns.forEach((currFn)=>currFn());
}, timeout);
});
}
function resizeLayers(){
const t = savThis.bkgCanvas.offsetTop + savThis.bkgCanvas.clientTop,
l = savThis.bkgCanvas.offsetLeft + savThis.bkgCanvas.clientLeft,
w = savThis.bkgCanvas.offsetWidth,
h = savThis.bkgCanvas.offsetHeight;
// check if canvas size changed when window resized, allow some rounding error in layout calcs
if ((Math.abs(w - savThis.rawWidth)/w < 0.01) && (Math.abs(h - savThis.rawHeight)/h < 0.01))
{
// canvas size didn't change so return
return;
}
// canvas has been resized so resize all the overlay canvases
for (let j=1; j<savThis.bkgCanvas.layers.length; j++) // bkg is layer[0]
{
let ovl = savThis.bkgCanvas.layers[j].cElem;
if (ovl) // may have been deleted so empty slot
{
ovl.style.top = t+'px';
ovl.style.left = l+'px';
ovl.style.width = w+'px';
ovl.style.height = h+'px';
ovl.width = w; // reset canvas attribute to pixel width
ovl.height = h;
}
}
}
// check if this is a context for an overlay
if (cvsID.indexOf("_ovl_") !== -1)
{
console.error("CanvasStack: canvas must be a background canvas not an overlay");
return {};
}
this.cId = cvsID;
this.stackLimit = stackLimit || 6;
this.bkgCanvas = document.getElementById(cvsID);
this.rawWidth = this.bkgCanvas.offsetWidth;
this.rawHeight = this.bkgCanvas.offsetHeight;
this.bkgCanvas.resizeFns = [];
if (!this.bkgCanvas.hasOwnProperty('layers'))
{
// create an array to hold all the overlay canvases for this canvas
this.bkgCanvas.layers = [];
// make a Layer object for the bkgCanvas
let bkgL = new Layer(this.cId, this.bkgCanvas); // bkgCanvas is always layer[0]
this.bkgCanvas.layers[0] = bkgL;
// make sure the overlay canvases always match the bkgCanvas size
setResizeHandler(resizeLayers, 250);
}
if (!this.bkgCanvas.hasOwnProperty('unique'))
{
this.bkgCanvas.unique = 0;
}
}
createLayer(){
const w = this.rawWidth,
h = this.rawHeight,
nLyrs = this.bkgCanvas.layers.length; // bkg is layer 0 so at least 1
// check background canvas is still there
if (!(this.bkgCanvas && this.bkgCanvas.layers))
{
console.log("CanvasStack: missing background canvas");
return;
}
if (this.bkgCanvas.layers.length >= this.stackLimit)
{
console.error("CanvasStack: too many layers");
return;
}
this.bkgCanvas.unique += 1; // a private static variable
const uniqueTag = this.bkgCanvas.unique.toString();
const ovlId = this.cId+"_ovl_"+uniqueTag;
const ovlHTML = "<canvas id='"+ovlId+"' style='position:absolute' width='"+w+"' height='"+h+"'></canvas>";
const topCvs = this.bkgCanvas.layers[nLyrs-1].cElem;
topCvs.insertAdjacentHTML('afterend', ovlHTML);
const newCvs = document.getElementById(ovlId);
newCvs.style.backgroundColor = "transparent";
newCvs.style.left = (this.bkgCanvas.offsetLeft+this.bkgCanvas.clientLeft)+'px';
newCvs.style.top = (this.bkgCanvas.offsetTop+this.bkgCanvas.clientTop)+'px';
// make it the same size as the background canvas
newCvs.style.width = this.bkgCanvas.offsetWidth+'px';
newCvs.style.height = this.bkgCanvas.offsetHeight+'px';
let newL = new Layer(ovlId, newCvs);
// save the ID in an array to facilitate removal
this.bkgCanvas.layers.push(newL);
return ovlId; // return the new canvas id
}
deleteLayer(ovlyId){
// check background canvas is still there
if (!(this.bkgCanvas && this.bkgCanvas.layers))
{
console.log("CanvasStack: missing background canvas");
return;
}
for (let i=1; i<this.bkgCanvas.layers.length; i++)
{
if (this.bkgCanvas.layers[i].id === ovlyId)
{
let ovlNode = this.bkgCanvas.layers[i].cElem;
if (ovlNode)
{
ovlNode.parentNode.removeChild(ovlNode);
}
// now delete layers array element
this.bkgCanvas.layers.splice(i,1); // delete the Layer object
}
}
}
deleteAllLayers(){
// check background canvas is still there
if (!(this.bkgCanvas && this.bkgCanvas.layers))
{
console.log("CanvasStack: missing background canvas");
return;
}
for (let i=this.bkgCanvas.layers.length-1; i>0; i--) // don't delete layers[0] its the bakg canavs
{
let ovlNode = this.bkgCanvas.layers[i].cElem;
if (ovlNode)
{
let orphan = ovlNode.parentNode.removeChild(ovlNode);
orphan = null;
}
// now delete layers array element
this.bkgCanvas.layers.splice(i,1);
}
// clear any resize callbacks, the layers are gone
this.bkgCanvas.resizeFns.length = 0; // remove any added handlers, leave the basic
}
addResizeCallback(callbackFn){
this.bkgCanvas.resizeFns.push(callbackFn);
}
};
}());