Canvas and Web Workers
section
Includes: tests 84, 85, 86, 87, 99, 101, 102, 116
q-canvas, q-worker, worker signals, Promise-returning worker methods, and canvas compatibility tests.
Back to test index.
84. q-canvas screensaver bounce (start/stop button)
pending
Expected: t84-ok | frames=
button#t84-toggle {
type: "button"
text { start }
onclick {
var running = String(t84Timer.running) === "true" || t84Timer.running === true || Number(t84Timer.running) === 1;
if (running) {
t84Timer.stop();
this.textContent = "start";
return;
}
window.__t84 = null;
$("#t84-out").textContent = "waiting";
$("#t84-done").textContent = "0";
t84Timer.start();
this.textContent = "stop";
}
}
q-canvas myCanvas84 {
width: 180
height: 96
}
div#t84-out { text { waiting } }
div#t84-meta { text { frames=0|bounces=0 } }
div#t84-done { text { 0 } }
q-timer t84Timer {
interval: 30
repeat: true
running: false
ontimeout {
var c = myCanvas84;
if (!c || !c.context) { return; }
if (!window.__t84) {
window.__t84 = { x: 60, y: 34, vx: 1, vy: -1, r: 14, rv: 1, rr: 40, gg: 120, bb: 210, dr: 1, dg: 1, db: 1, f: 0, b: 0 };
}
var s = window.__t84;
var w = Number(c.width || 180);
var h = Number(c.height || 96);
s.x = s.x + s.vx;
s.y = s.y + s.vy;
if (s.x - s.r <= 0 || s.x + s.r >= w) { s.vx = -s.vx; s.b = s.b + 1; }
if (s.y - s.r <= 0 || s.y + s.r >= h) { s.vy = -s.vy; s.b = s.b + 1; }
if (s.x - s.r < 0) { s.x = s.r; }
if (s.x + s.r > w) { s.x = w - s.r; }
if (s.y - s.r < 0) { s.y = s.r; }
if (s.y + s.r > h) { s.y = h - s.r; }
var edgeLimit = Math.floor(Math.min(s.x, w - s.x, s.y, h - s.y) - 1);
if (!Number.isFinite(edgeLimit)) { edgeLimit = 6; }
if (edgeLimit < 6) { edgeLimit = 6; }
if (edgeLimit > 22) { edgeLimit = 22; }
if (s.r > edgeLimit) { s.r = edgeLimit; s.rv = -Math.abs(s.rv); }
s.r = s.r + s.rv;
if (s.r >= edgeLimit) { s.r = edgeLimit; s.rv = -Math.abs(s.rv); }
if (s.r <= 6) { s.r = 6; s.rv = Math.abs(s.rv); }
s.rr = s.rr + s.dr; if (s.rr >= 255 || s.rr <= 0) { s.dr = -s.dr; }
s.gg = s.gg + s.dg; if (s.gg >= 255 || s.gg <= 0) { s.dg = -s.dg; }
s.bb = s.bb + s.db; if (s.bb >= 255 || s.bb <= 0) { s.db = -s.db; }
var ctx = c.context;
ctx.clearRect(0, 0, w, h);
ctx.fillStyle = "rgba(" + s.rr + "," + s.gg + "," + s.bb + ",0.85)";
ctx.beginPath();
ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
ctx.fill();
s.f = s.f + 1;
$("#t84-meta").textContent = "frames=" + s.f + "|bounces=" + s.b;
if (s.f > 44 && s.b > 0) {
$("#t84-out").textContent = "t84-ok";
$("#t84-done").textContent = "1";
}
}
}
onReady {
var acc = document.querySelector("#acc-main");
if (acc) {
acc.resultReported(84, this.innerHTML);
}
}
85. q-canvas diagonal pixel-runner (start/stop button)
pending
Expected: t85-ok
button#t85-toggle {
type: "button"
text { start }
onclick {
var running = String(t85Timer.running) === "true" || t85Timer.running === true || Number(t85Timer.running) === 1;
if (running) {
t85Timer.stop();
this.textContent = "start";
return;
}
window.__t85 = null;
$("#t85-out").textContent = "waiting";
$("#t85-done").textContent = "0";
t85Timer.start();
this.textContent = "stop";
}
}
q-canvas myCanvas85 {
width: 164
height: 92
}
div#t85-out { text { waiting } }
div#t85-done { text { 0 } }
q-timer t85Timer {
interval: 30
repeat: true
running: false
ontimeout {
var c = myCanvas85;
if (!c || !c.context) { return; }
if (!window.__t85) { window.__t85 = { x: 2, y: 2, vx: 1, vy: 1, rr: 120, gg: 40, bb: 180, dr: 1, dg: -1, db: 1, f: 0, b: 0 }; }
var s = window.__t85;
var w = Number(c.width || 164);
var h = Number(c.height || 92);
s.x = s.x + s.vx;
s.y = s.y + s.vy;
if (s.x <= 0 || s.x >= w - 2) { s.vx = -s.vx; s.b = s.b + 1; }
if (s.y <= 0 || s.y >= h - 2) { s.vy = -s.vy; s.b = s.b + 1; }
s.rr = s.rr + s.dr; if (s.rr <= 0 || s.rr >= 255) { s.dr = -s.dr; }
s.gg = s.gg + s.dg; if (s.gg <= 0 || s.gg >= 255) { s.dg = -s.dg; }
s.bb = s.bb + s.db; if (s.bb <= 0 || s.bb >= 255) { s.db = -s.db; }
c.context.clearRect(0, 0, w, h);
c.context.fillStyle = "rgba(" + s.rr + "," + s.gg + "," + s.bb + ",0.9)";
c.context.fillRect(s.x, s.y, 2, 2);
s.f = s.f + 1;
if (s.f > 42 && s.b > 0) {
$("#t85-out").textContent = "t85-ok";
$("#t85-done").textContent = "1";
}
}
}
onReady {
var acc = document.querySelector("#acc-main");
if (acc) {
acc.resultReported(85, this.innerHTML);
}
}
86. q-canvas transparent overlay (start/stop button)
pending
Expected: base-86 | t86-ok
button#t86-toggle {
type: "button"
text { start }
onclick {
var running = String(t86Timer.running) === "true" || t86Timer.running === true || Number(t86Timer.running) === 1;
if (running) {
t86Timer.stop();
this.textContent = "start";
return;
}
window.__t86 = null;
$("#t86-out").textContent = "waiting";
$("#t86-done").textContent = "0";
t86Timer.start();
this.textContent = "stop";
}
}
div#t86-wrap {
style {
position: relative;
width: 180px;
height: 96px;
border: 1px solid #94a3b8;
background-color: #e2e8f0;
}
div#t86-base { text { base-86 } }
}
q-canvas myCanvas86 {
width: 180
height: 96
}
div#t86-out { text { waiting } }
div#t86-done { text { 0 } }
q-timer t86Timer {
interval: 30
repeat: true
running: false
ontimeout {
var c = myCanvas86;
var wrap = $("#t86-wrap");
if (!c || !c.context || !wrap) { return; }
if (c.parentNode !== wrap) {
c.style.position = "absolute";
c.style.left = "0px";
c.style.top = "0px";
c.style.pointerEvents = "none";
c.style.backgroundColor = "transparent";
wrap.appendChild(c);
}
if (!window.__t86) { window.__t86 = { x: 9, y: 9, vx: 1, vy: 1, f: 0 }; }
var s = window.__t86;
var w = Number(c.width || 180);
var h = Number(c.height || 96);
s.x = s.x + s.vx;
s.y = s.y + s.vy;
if (s.x <= 7 || s.x >= w - 7) { s.vx = -s.vx; }
if (s.y <= 7 || s.y >= h - 7) { s.vy = -s.vy; }
c.context.clearRect(0, 0, w, h);
c.context.fillStyle = "rgba(30,64,175,0.92)";
c.context.beginPath();
c.context.arc(s.x, s.y, 7, 0, Math.PI * 2);
c.context.fill();
s.f = s.f + 1;
if (s.f > 34) {
$("#t86-out").textContent = "t86-ok";
$("#t86-done").textContent = "1";
}
}
}
onReady {
var acc = document.querySelector("#acc-main");
if (acc) {
acc.resultReported(86, this.innerHTML);
}
}
87. q-canvas independent animated surfaces (start/stop button)
pending
Expected: t87-ok
button#t87-toggle {
type: "button"
text { start }
onclick {
var running = String(t87Timer.running) === "true" || t87Timer.running === true || Number(t87Timer.running) === 1;
if (running) {
t87Timer.stop();
this.textContent = "start";
return;
}
window.__t87 = null;
$("#t87-out").textContent = "waiting";
$("#t87-done").textContent = "0";
t87Timer.start();
this.textContent = "stop";
}
}
q-canvas myCanvas87a { width: 96 height: 48 }
q-canvas myCanvas87b { width: 96 height: 48 }
div#t87-out { text { waiting } }
div#t87-done { text { 0 } }
q-timer t87Timer {
interval: 30
repeat: true
running: false
ontimeout {
var a = myCanvas87a;
var b = myCanvas87b;
if (!a || !b || !a.context || !b.context) { return; }
if (!window.__t87) { window.__t87 = { xa: 2, xb: 2, f: 0 }; }
var s = window.__t87;
s.xa = (s.xa + 1) % 90;
s.xb = (s.xb + 1) % 90;
a.context.clearRect(0, 0, 96, 48);
b.context.clearRect(0, 0, 96, 48);
a.context.fillStyle = "rgba(16,185,129,0.9)";
b.context.fillStyle = "rgba(244,63,94,0.9)";
a.context.fillRect(s.xa, 12, 6, 6);
b.context.fillRect(s.xb, 26, 6, 6);
s.f = s.f + 1;
if (s.f > 28) {
$("#t87-out").textContent = "t87-ok";
$("#t87-done").textContent = "1";
}
}
}
onReady {
var acc = document.querySelector("#acc-main");
if (acc) {
acc.resultReported(87, this.innerHTML);
}
}
99. q-worker method returns Promise + auto property resolve assignment
pending
Expected: A,B,C
q-worker myworker99 {
q-property myprops: q-array { "A", "B", "C" }
function dowork() {
var rv = [];
for (var i = 0; i < 3; i += 1) {
rv.push(this.component.myprops[i]);
}
return rv.join(",");
}
}
q-component mycomp99 {
q-property someprop: "waiting"
myworker99 worker { }
function report99() {
var acc = document.querySelector("#acc-main");
var host = this.closest("q-html");
if (!acc || !host) { return; }
acc.resultReported(99, host.innerHTML);
}
onReady {
this.component.someprop = worker.dowork();
}
onsomepropchanged {
this.component.report99();
}
div#t99-out { text { ${this.component.someprop} } }
}
mycomp99 { }
onReady { }
101. q-worker property mutation + q-signal emission from worker call
pending
Expected: 5 | id="t101-signal"
q-worker calcworker101 {
q-property base: 2
q-signal done(result)
function compute(step) {
this.component.base = Number(this.component.base || 0) + Number(step || 0);
this.component.done(this.component.base);
return this.component.base;
}
}
q-component host101 {
q-property out: "waiting"
q-property signalOut: "waiting"
calcworker101 worker { }
function report101() {
var acc = document.querySelector("#acc-main");
var host = this.closest("q-html");
if (!acc || !host) { return; }
acc.resultReported(101, host.innerHTML);
}
onReady {
worker.done.connect(function(evt) {
var args = evt && Array.isArray(evt.args) ? evt.args : [evt];
this.component.signalOut = String(args.length > 0 && typeof args[0] !== "undefined" ? args[0] : "none");
});
this.component.out = worker.compute(3);
}
onoutchanged {
this.component.report101();
}
onsignaloutchanged {
this.component.report101();
}
div#t101-out { text { ${this.component.out} } }
div#t101-signal { text { ${this.component.signalOut} } }
}
host101 { }
onReady { }
102. q-worker + q-canvas minimal start/stop
pending
Expected: Start | Stop | worker102inst | t102-canvas
q-worker worker102 {
q-signal updateShapeData(payload)
function draw(stateJson, width, height, epochMs) {
var st = null;
try { st = JSON.parse(String(stateJson || "")); } catch (e) { st = null; }
if (!st) {
st = { x: 24, y: 24, vx: 1.7, vy: 1.3, r: 14, c1: 120, c2: 80, c3: 200 };
}
var w = Math.max(80, Number(width || 320));
var h = Math.max(60, Number(height || 180));
st.x = Number(st.x || 0) + Number(st.vx || 0);
st.y = Number(st.y || 0) + Number(st.vy || 0);
st.r = Math.max(8, Math.min(30, Number(st.r || 14) + Math.sin(Number(epochMs || Date.now()) * 0.002) * 0.22));
if (st.x <= st.r || st.x >= (w - st.r)) { st.vx = -Number(st.vx || 0); }
if (st.y <= st.r || st.y >= (h - st.r)) { st.vy = -Number(st.vy || 0); }
st.c1 = (Number(st.c1 || 0) + 1) % 256;
st.c2 = (Number(st.c2 || 0) + 2) % 256;
st.c3 = (Number(st.c3 || 0) + 3) % 256;
var payload = JSON.stringify(st);
this.component.updateShapeData(payload);
return payload;
}
}
worker102 worker102inst { }
q-component host102 {
q-property running: 0
q-property state: ""
function drawSomeShapes(payloadJson) {
var cv = myCanvas102;
if (!cv || !cv.context) { return; }
var st = null;
try { st = JSON.parse(String(payloadJson || "")); } catch (e) { st = null; }
if (!st) { return; }
var w = Number(cv.width || 320);
var h = Number(cv.height || 180);
cv.context.clearRect(0, 0, w, h);
cv.context.fillStyle = "rgba(15,23,42,0.25)";
cv.context.fillRect(0, 0, w, h);
cv.context.fillStyle = "rgb(" + String(Number(st.c1 || 0)) + "," + String(Number(st.c2 || 0)) + "," + String(Number(st.c3 || 0)) + ")";
cv.context.beginPath();
cv.context.arc(Number(st.x || 0), Number(st.y || 0), Number(st.r || 10), 0, Math.PI * 2);
cv.context.fill();
}
function draw() {
if (Number(this.component.running || 0) !== 1) { return; }
var cv = myCanvas102;
var w = Number(cv && cv.width ? cv.width : 320);
var h = Number(cv && cv.height ? cv.height : 180);
var next = worker102inst.draw(this.component.state, w, h, Date.now());
if (next && typeof next.then === "function") {
var self = this;
next.then(function(payload) { self.component.state = String(payload || ""); });
} else {
this.component.state = String(next || "");
}
}
onReady {
worker102inst.updateShapeData.connect(function(payload) {
this.component.state = String(payload || "");
this.component.drawSomeShapes(this.component.state);
});
var wrap = this.querySelector("#t102-wrap");
if (wrap && myCanvas102 && myCanvas102.parentNode !== wrap) {
wrap.appendChild(myCanvas102);
}
}
div#t102-controls {
button#t102-start {
type: "button"
onclick {
host102inst.running = 1;
t102Timer.start();
$("#t102-status").textContent = "running";
$("#t102-done").textContent = "0";
}
text { Start }
}
button#t102-stop {
type: "button"
onclick {
host102inst.running = 0;
t102Timer.stop();
$("#t102-status").textContent = "stopped";
$("#t102-done").textContent = "1";
}
text { Stop }
}
div#t102-status { text { idle } }
div#t102-done { text { 0 } }
}
div#t102-worker-ref { text { worker102inst } }
div#t102-wrap {
style {
width: 320px;
height: 180px;
border: 1px solid #94a3b8;
background-color: rgba(15,23,42,0.08);
}
}
}
host102 host102inst { }
q-canvas myCanvas102 {
id: "t102-canvas"
width: 320
height: 180
}
q-timer t102Timer {
interval: 10
repeat: true
running: false
ontimeout {
host102inst.draw();
}
}
onReady {
var acc = document.querySelector("#acc-main");
if (acc) {
acc.resultReported(102, this.innerHTML);
}
}
116. q-painter remains compatible with q-canvas rendering
pending
Expected: t116-ok
q-painter painter116 {
q-property tone: "rgba(220,240,255,0.8)"
onpaint {
this.fillStyle = this.tone;
this.fillRect(0, 0, this.width, this.height);
}
}
q-style style116 {
width: 90px
height: 26px
border: 1px solid #1e293b
q-style-painter {
background { painter116 }
}
}
q-theme theme116 {
.t116-panel { style116 }
}
theme116 {
div#t116-painter.t116-panel { text { } }
}
q-canvas myCanvas116 {
width: 48
height: 24
}
div#t116-out { text { waiting } }
onReady {
var host = this;
setTimeout(function() {
var acc = document.querySelector("#acc-main");
var out = host.querySelector("#t116-out");
var panel = host.querySelector("#t116-painter");
var c = myCanvas116;
var support = !!(CSS && CSS.paintWorklet && typeof CSS.paintWorklet.addModule === "function");
var okCanvas = false;
if (c && c.context) {
c.context.clearRect(0, 0, Number(c.width || 48), Number(c.height || 24));
c.context.fillStyle = "#22c55e";
c.context.fillRect(2, 2, 18, 10);
var px = c.context.getImageData(3, 3, 1, 1).data;
okCanvas = !!(px && px.length >= 4 && Number(px[3]) > 0);
}
var bg = panel ? String(panel.style.backgroundImage || "").trim() : "";
var okPainter = support ? (bg.indexOf("paint(") >= 0) : true;
var ok = okCanvas && okPainter;
var value = (ok ? "t116-ok" : "t116-fail") + "|canvas=" + String(okCanvas ? 1 : 0) + "|painter=" + String(okPainter ? 1 : 0);
if (out) {
out.textContent = value;
}
if (acc && typeof acc.outcomeReported === "function") {
acc.outcomeReported(116, ok, value);
} else if (acc && typeof acc.resultReported === "function") {
acc.resultReported(116, value);
}
}, 520);
}