Skip to content

Commit a76f61d

Browse files
authored
Merge pull request mdn#159 from bsmth/11591-OffScreenCanvas
11591 OffscreenCanvas worker thread example
2 parents 4c59c84 + 144e4e1 commit a76f61d

File tree

4 files changed

+266
-0
lines changed

4 files changed

+266
-0
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# offscreen-canvas-worker
2+
3+
This example shows how to use an `OffscreenCanvasContext` with a web worked.
4+
[View this demo live](https://mdn.github.io/dom-examples/web-workers/offscreen-canvas-worker/).
5+
6+
To run this code locally you'll need to serve it. If you have [node](https://nodejs.org/) installed, navigate to the folder containing the code and run `lite-server`:
7+
8+
```bash
9+
cd web-workers/offscreen-canvas-worker
10+
npx lite-server
11+
```
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
5+
6+
<title>OffscreenCanvas API</title>
7+
8+
<link rel="stylesheet" href="style.css" />
9+
</head>
10+
<body>
11+
<header>
12+
<h1>OffscreenCanvas and worker threads</h1>
13+
<p>
14+
<b>Note:</b> your browser must support
15+
<a
16+
href="https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas"
17+
><code>OffscreenCanvas</code></a
18+
>.
19+
</p>
20+
<p>This example has two canvases with incrementing counters.</p>
21+
22+
<p>
23+
Canvas A is being drawn to on the main thread. Canvas B uses an
24+
<a
25+
href="https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas"
26+
><code>OffscreenCanvas</code></a
27+
>
28+
so that we can draw to it in a worker thread. The purpose of this
29+
example is to show how a worker thread can keep the rest of our UI
30+
responsive.
31+
</p>
32+
33+
<p>
34+
The button below each canvas creates <b>blocking work</b> on either the
35+
main thread (Canvas A) or a worker thread (Canvas B). When a thread is
36+
blocked, incrementing the related counter from that thread is also
37+
blocked.
38+
</p>
39+
</header>
40+
41+
<main>
42+
<div class="canvases">
43+
<div>
44+
<span class="canvas-title">Canvas A</span>
45+
<canvas id="main" width="200" height="200"></canvas>
46+
<div>
47+
<button id="main-button" onclick="slowMainThread()">
48+
Block main thread
49+
</button>
50+
</div>
51+
</div>
52+
<div>
53+
<span class="canvas-title">Canvas B</span>
54+
<canvas id="worker" width="200" height="200"></canvas>
55+
56+
<div>
57+
<button id="worker-button" onclick="slowdownWorker()">
58+
Block worker thread
59+
</button>
60+
</div>
61+
</div>
62+
</div>
63+
<div id="canvas-description">
64+
<p>
65+
<b>When the main thread is blocked</b>, all UI elements are frozen.<br />
66+
The hover effect on buttons is also blocked:
67+
</p>
68+
<button>Example button</button>
69+
<p>
70+
<b>When the worker thread is blocked</b>, the main thread is free to
71+
do work such as the element hover effects and animations. Blocking the
72+
worker thread will still prevent Canvas B's counter from being
73+
updated, but the rest of the UI stays responsive while this is true.
74+
</p>
75+
</div>
76+
77+
<footer>
78+
<p>
79+
This demo is based on an example by
80+
<a href="https://glitch.com/@PicchiKevin">Kevin Picchi</a>.
81+
</p>
82+
</footer>
83+
</main>
84+
85+
<script>
86+
const canvasA = document.getElementById("main");
87+
const canvasB = document.getElementById("worker");
88+
89+
const ctxA = canvasA.getContext("2d");
90+
const canvasWidth = ctxA.width;
91+
const canvasHeight = ctxA.height;
92+
93+
// Create a counter for Canvas A
94+
let counter = 0;
95+
setInterval(() => {
96+
redrawCanvasA();
97+
counter++;
98+
}, 100);
99+
100+
// Redraw Canvas A counter
101+
function redrawCanvasA() {
102+
ctxA.clearRect(0, 0, canvasA.width, canvasA.height);
103+
ctxA.font = "24px Verdana";
104+
ctxA.textAlign = "center";
105+
ctxA.fillText(counter, canvasA.width / 2, canvasA.height / 2);
106+
}
107+
108+
// This function creates heavy (blocking) work on a thread
109+
function fibonacci(num) {
110+
if (num <= 1) {
111+
return 1;
112+
}
113+
return fibonacci(num - 1) + fibonacci(num - 2);
114+
}
115+
116+
// Call our Fibonacci function on the main thread
117+
function slowMainThread() {
118+
fibonacci(42);
119+
}
120+
121+
// Set up a worker thread to render Canvas B
122+
const worker = new Worker("worker.js");
123+
124+
// Use the OffscreenCanvas API and send to the worker thread
125+
const canvasWorker = canvasB.transferControlToOffscreen();
126+
worker.postMessage({ canvas: canvasWorker }, [canvasWorker]);
127+
128+
// A 'slowDown' message we can catch in the worker to start heavy work
129+
function slowdownWorker() {
130+
worker.postMessage("slowDown");
131+
}
132+
</script>
133+
</body>
134+
</html>
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
html,
2+
body {
3+
font-family: sans-serif;
4+
margin: 2rem;
5+
}
6+
7+
main,
8+
footer {
9+
display: flex;
10+
align-content: left;
11+
justify-content: left;
12+
flex-direction: column;
13+
flex-wrap: wrap;
14+
text-align: left;
15+
margin-bottom: 4rem;
16+
}
17+
18+
header {
19+
font-size: 1em;
20+
min-height: 1em;
21+
line-height: 1.5em;
22+
margin: 1rem;
23+
max-width: 50%;
24+
}
25+
26+
button,
27+
canvas {
28+
border-radius: 10px;
29+
}
30+
31+
canvas {
32+
border: 2px solid black;
33+
margin: 2rem;
34+
}
35+
36+
button {
37+
background: #797676;
38+
padding: 10px;
39+
margin: 10px;
40+
border: 0;
41+
color: white;
42+
font-size: 1em;
43+
max-width: 150px;
44+
}
45+
46+
/* Hover effect for buttons */
47+
button:hover,
48+
canvas:hover {
49+
background: rgb(242, 134, 102);
50+
transition: transform 0.5s;
51+
transform: scale(1.3, 1.3);
52+
color: black;
53+
}
54+
55+
#canvas-description {
56+
max-width: 50%;
57+
text-align: left;
58+
}
59+
60+
.ui-example {
61+
margin: 2rem;
62+
}
63+
64+
.ui-example button:hover {
65+
transition: transform 0.5s;
66+
transform: scale(1.3, 1.3);
67+
}
68+
69+
.canvas-title {
70+
display: block;
71+
font-size: 1.4em;
72+
font-weight: bold;
73+
margin-bottom: 1rem;
74+
}
75+
76+
.canvases {
77+
text-align: center;
78+
display: flex;
79+
flex-wrap: wrap;
80+
justify-content: left;
81+
align-content: left;
82+
padding: 2rem;
83+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
let canvasB = null;
2+
let ctxWorker = null;
3+
4+
// Waiting to receive the OffScreenCanvas
5+
self.onmessage = (event) => {
6+
if (event.data === "slowDown") {
7+
fibonacci(42);
8+
} else {
9+
canvasB = event.data.canvas;
10+
ctxWorker = canvasB.getContext("2d");
11+
startCounting();
12+
}
13+
};
14+
15+
// Fibonacci function to add some delay to the thread
16+
function fibonacci(num) {
17+
if (num <= 1) {
18+
return 1;
19+
}
20+
return fibonacci(num - 1) + fibonacci(num - 2);
21+
}
22+
23+
// Start the counter for Canvas B
24+
let counter = 0;
25+
function startCounting() {
26+
setInterval(() => {
27+
redrawCanvasB();
28+
counter++;
29+
}, 100);
30+
}
31+
32+
// Redraw Canvas B text
33+
function redrawCanvasB() {
34+
ctxWorker.clearRect(0, 0, canvasB.width, canvasB.height);
35+
ctxWorker.font = "24px Verdana";
36+
ctxWorker.textAlign = "center";
37+
ctxWorker.fillText(counter, canvasB.width / 2, canvasB.height / 2);
38+
}

0 commit comments

Comments
 (0)