I've been trying to learn HTML5 canvas for a little while, and I've made a short script that allows for strings to be passed into an object which then displays it to a canvas. However, its performance is very inconsistent, and I've got a few ideas why.
When using fillRect, does it benefit performance to use beginPath and endPath? As far as I can tell, they're really only for lines.
More importantly, it looks like there's some serious memory leak issues, and I'm guessing that when garbage collection kicks in, that's when the program chugs. I've tried to read up on how javascript handles memory allocation, but to be honest, it's all kind of beyond me. I don't have any variable declarations inside loops (except at the start of for loops, but I tried changing that and I saw no improvement), but the object I have for drawing on the canvas is inside a function, so maybe it's making a bunch of new variables for each time that object's called?
In short, I don't know how to make javascript work faster and handle memory more intelligently.
Here's a link to the application itself.
Here's the code that takes a string and displays it to the canvas, followed by the html and js that creates the object and passes the display info into it:
/* screen.js */
function screen(id, pSize, w, h) {
var pixelSize;
var screenWidth;
var screenHeight;
var canvas;
var context;
var palette = new Array(16);
palette = [
"#000000", "#111111", "#222222", "#333333", "#444444", "#555555", "#666666", "#777777",
"#888888", "#999999", "#AAAAAA", "#BBBBBB", "#CCCCCC", "#DDDDDD", "#EEEEEE", "#FFFFFF"
];
this.setScreenAttributes = function(pSize, w, h) {
pixelSize = pSize;
screenWidth = w;
screenHeight = h;
canvas.setAttribute("width", screenWidth * pixelSize);
canvas.setAttribute("height", screenHeight * pixelSize);
console.log("set screen attributes to " + screenWidth * pixelSize + "x" + screenHeight * pixelSize);
};
this.buildScreen = function(id, pSize, w, h) {
canvas = document.getElementById(id);
context = canvas.getContext("2d");
this.setScreenAttributes(pSize, w, h);
console.log("built screen " + id);
};
this.setPalette = function(pal) {
if (pal.length == 16) {
for (var i = 0; i < 16; i++)
palette[i] = pal[i];
} else
console.log("error, palettes must contain 16 colors");
};
this.returnWidth = function() {
return screenWidth;
};
this.returnHeight = function() {
return screenHeight;
};
this.draw16 = function(str) {
if (str.length == screenWidth * screenHeight) {
context.clearRect(0, 0, screenWidth * pixelSize, screenHeight * pixelSize);
for (var y = 0; y < screenHeight; y++) {
for (var x = 0; x < screenWidth; x++) {
switch (str.charAt((y * screenWidth) + x)) {
case '0':
context.fillStyle = palette[0];
break;
case '1':
context.fillStyle = palette[1];
break;
case '2':
context.fillStyle = palette[2];
break;
case '3':
context.fillStyle = palette[3];
break;
case '4':
context.fillStyle = palette[4];
break;
case '5':
context.fillStyle = palette[5];
break;
case '6':
context.fillStyle = palette[6];
break;
case '7':
context.fillStyle = palette[7];
break;
case '8':
context.fillStyle = palette[8];
break;
case '9':
context.fillStyle = palette[9];
break;
case 'a':
case 'A':
context.fillStyle = palette[10];
break;
case 'b':
case 'B':
context.fillStyle = palette[11];
break;
case 'c':
case 'C':
context.fillStyle = palette[12];
break;
case 'd':
case 'D':
context.fillStyle = palette[13];
break;
case 'e':
case 'E':
context.fillStyle = palette[14];
break;
case 'f':
case 'F':
context.fillStyle = palette[15];
break;
default:
rgba(255, 0, 0, 0);
console.log("error, wrong character in string passed to draw16!");
break;
}
context.fillRect(0 + pixelSize * x, 0 + pixelSize * y, pixelSize, pixelSize);
}
}
//console.log("drew to screen");
} else {
console.log("incorrect length of string passed into draw16!");
console.log("length is " + str.length + ", but should be " + (screenWidth * screenHeight) + "!");
}
};
this.buildScreen(id, pSize, w, h); // constructor
}
/* inline-js */
// Palettes to pass to screen
var p0 = ["#000000", "#111111", "#222222", "#333333", "#444444", "#555555", "#666666", "#777777", "#888888", "#999999", "#AAAAAA", "#BBBBBB", "#CCCCCC", "#DDDDDD", "#EEEEEE", "#FFFFFF"];
var p1 = ["#111111", "#222222", "#333333", "#444444", "#555555", "#666666", "#777777", "#888888", "#999999", "#AAAAAA", "#BBBBBB", "#CCCCCC", "#DDDDDD", "#EEEEEE", "#FFFFFF", "#000000"];
var p2 = ["#222222", "#333333", "#444444", "#555555", "#666666", "#777777", "#888888", "#999999", "#AAAAAA", "#BBBBBB", "#CCCCCC", "#DDDDDD", "#EEEEEE", "#FFFFFF", "#000000", "#111111"];
var p3 = ["#333333", "#444444", "#555555", "#666666", "#777777", "#888888", "#999999", "#AAAAAA", "#BBBBBB", "#CCCCCC", "#DDDDDD", "#EEEEEE", "#FFFFFF", "#000000", "#111111", "#222222"];
var p4 = ["#444444", "#555555", "#666666", "#777777", "#888888", "#999999", "#AAAAAA", "#BBBBBB", "#CCCCCC", "#DDDDDD", "#EEEEEE", "#FFFFFF", "#000000", "#111111", "#222222", "#333333"];
var p5 = ["#555555", "#666666", "#777777", "#888888", "#999999", "#AAAAAA", "#BBBBBB", "#CCCCCC", "#DDDDDD", "#EEEEEE", "#FFFFFF", "#000000", "#111111", "#222222", "#333333", "#444444"];
var p6 = ["#666666", "#777777", "#888888", "#999999", "#AAAAAA", "#BBBBBB", "#CCCCCC", "#DDDDDD", "#EEEEEE", "#FFFFFF", "#000000", "#111111", "#222222", "#333333", "#444444", "#555555"];
var p7 = ["#777777", "#888888", "#999999", "#AAAAAA", "#BBBBBB", "#CCCCCC", "#DDDDDD", "#EEEEEE", "#FFFFFF", "#000000", "#111111", "#222222", "#333333", "#444444", "#555555", "#666666"];
var p8 = ["#888888", "#999999", "#AAAAAA", "#BBBBBB", "#CCCCCC", "#DDDDDD", "#EEEEEE", "#FFFFFF", "#000000", "#111111", "#222222", "#333333", "#444444", "#555555", "#666666", "#777777"];
var p9 = ["#999999", "#AAAAAA", "#BBBBBB", "#CCCCCC", "#DDDDDD", "#EEEEEE", "#FFFFFF", "#000000", "#111111", "#222222", "#333333", "#444444", "#555555", "#666666", "#777777", "#888888"];
var pA = ["#AAAAAA", "#BBBBBB", "#CCCCCC", "#DDDDDD", "#EEEEEE", "#FFFFFF", "#000000", "#111111", "#222222", "#333333", "#444444", "#555555", "#666666", "#777777", "#888888", "#999999"];
var pB = ["#BBBBBB", "#CCCCCC", "#DDDDDD", "#EEEEEE", "#FFFFFF", "#000000", "#111111", "#222222", "#333333", "#444444", "#555555", "#666666", "#777777", "#888888", "#999999", "#AAAAAA"];
var pC = ["#CCCCCC", "#DDDDDD", "#EEEEEE", "#FFFFFF", "#000000", "#111111", "#222222", "#333333", "#444444", "#555555", "#666666", "#777777", "#888888", "#999999", "#AAAAAA", "#BBBBBB"];
var pD = ["#DDDDDD", "#EEEEEE", "#FFFFFF", "#000000", "#111111", "#222222", "#333333", "#444444", "#555555", "#666666", "#777777", "#888888", "#999999", "#AAAAAA", "#BBBBBB", "#CCCCCC"];
var pE = ["#EEEEEE", "#FFFFFF", "#000000", "#111111", "#222222", "#333333", "#444444", "#555555", "#666666", "#777777", "#888888", "#999999", "#AAAAAA", "#BBBBBB", "#CCCCCC", "#DDDDDD"];
var pF = ["#FFFFFF", "#000000", "#111111", "#222222", "#333333", "#444444", "#555555", "#666666", "#777777", "#888888", "#999999", "#AAAAAA", "#BBBBBB", "#CCCCCC", "#DDDDDD", "#EEEEEE"];
var t;
var i = 0;
var running = true;
var command = '(x * y / 2) % 16';
var testStr = "";
var s = new screen("mycanvas", 5, 160, 100);
function drawTest() { // Creates the string
testStr = "";
for (var y = 0; y < s.returnHeight(); y++) {
for (var x = 0; x < s.returnWidth(); x++) {
switch (parseInt(eval(command))) {
case 0:
testStr = testStr.concat('0');
break;
case 1:
testStr = testStr.concat('1');
break;
case 2:
testStr = testStr.concat('2');
break;
case 3:
testStr = testStr.concat('3');
break;
case 4:
testStr = testStr.concat('4');
break;
case 5:
testStr = testStr.concat('5');
break;
case 6:
testStr = testStr.concat('6');
break;
case 7:
testStr = testStr.concat('7');
break;
case 8:
testStr = testStr.concat('8');
break;
case 9:
testStr = testStr.concat('9');
break;
case 10:
testStr = testStr.concat('A');
break;
case 11:
testStr = testStr.concat('B');
break;
case 12:
testStr = testStr.concat('C');
break;
case 13:
testStr = testStr.concat('D');
break;
case 14:
testStr = testStr.concat('E');
break;
case 15:
testStr = testStr.concat('F');
break;
default:
testStr = testStr.concat('X');
console.log("incorrect entry in testStrPass: " + temp);
break;
}
}
}
};
function runTest() { // Cycles through palettes
if (running == true) {
if (i >= 16) {
i = 0
};
switch (i) {
case 0:
s.setPalette(p0);
break;
case 1:
s.setPalette(p1);
break;
case 2:
s.setPalette(p2);
break;
case 3:
s.setPalette(p3);
break;
case 4:
s.setPalette(p4);
break;
case 5:
s.setPalette(p5);
break;
case 6:
s.setPalette(p6);
break;
case 7:
s.setPalette(p7);
break;
case 8:
s.setPalette(p8);
break;
case 9:
s.setPalette(p9);
break;
case 10:
s.setPalette(pA);
break;
case 11:
s.setPalette(pB);
break;
case 12:
s.setPalette(pC);
break;
case 13:
s.setPalette(pD);
break;
case 14:
s.setPalette(pE);
break;
case 15:
s.setPalette(pF);
break;
}
s.draw16(testStr); // Draws to screen
i++;
t = setTimeout(runTest, 16);
}
};
function stop() {
clearTimeout(t);
running = false;
};
function restart() {
if (running == false) {
running = true;
runTest();
}
};
document.getElementById("stopButton").addEventListener("click", function() {
stop()
});
document.getElementById("restartButton").addEventListener("click", function() {
restart()
});
document.getElementById("evalButton").addEventListener("click", function() {
command = document.getElementById('code').value;
s.setScreenAttributes(document.getElementById('pSize').value, document.getElementById('x').value, document.getElementById('y').value);
testStr = "";
drawTest();
restart()
});
document.addEventListener("DOMContentLoaded", function() {
drawTest();
runTest();
});
body{margin-bottom: 50vh;}
<canvas id="mycanvas" style="border: 1px solid #000000"></canvas>
<br />
<button id="stopButton">stop</button>
<button id="restartButton">restart</button>
<br /><br /> pixel size: <input type="text" id="pSize" size="2" value="5" /> x: <input type="text" id="x" size="2" value="160" /> y: <input type="text" id="y" size="2" value="100" />
<br />
<input type="text" id="code" value="(x * y / 2) % 16" /><button id="evalButton">evaluate</button>
For some reason the stackoverflow snippet thing says that the constructor isn't right, but it works in Chrome, Firefox, Edge, and iOS Safari, so I don't know what it thinks is wrong.