JSFiddle - React, Tailwind, and code Playground
by Sam Wray
HTML
<div id="app">
<p>Contrived OffscreenCanvas example</p>
Offscreen:
<canvas ref="canvas"></canvas>
<br>
(Onscreen?):
<canvas ref="oncanvas"></canvas>
<br><br>
<button @click="displayFib">Fibonacci 👌</button><br>
Fibonacci * {{ this.fibNum }} = {{ this.fib }}
<br>
<button @click="videoPlay">{{ paused ? 'Play' : 'Pause' }} video</button>
<hr>
<h2>Registered Modules</h2>
<ul>
<li
v-for="(module, moduleName) in registeredModules"
>
{{ moduleName }}
<input
type="button"
@click="toggleActiveModule($event, moduleName)"
:value="$store.state.active.indexOf(moduleName) > -1 ? 'deactivate' : 'activate'"
>
</li>
</ul>
<hr>
<h2>Module Controls</h2>
<div v-for="module in activeModules">
<h3>{{ module }}</h3>
<label
v-for="(prop, propName) in $store.state.registry[module].props"
>
{{ propName }}
<input
:type="jsTypeToInputType(prop.type)"
:min="prop.min"
:max="prop.max"
:value="$store.state.registry[module][propName]"
@input="updateModuleProp($event, propName, module)"
>
</label>
</div>
<hr>
<button @click="toggleStates">{{ showStates ? 'Hide' : 'Show' }} states</button>
<div class="flex" v-if="showStates">
<div>
<h2>Vuex state</h2>
<pre>{{ $store.state }}</pre>
</div>
<div>
<h2>Worker state</h2>
<pre>{{ JSON.stringify(workerState, null, 2) }}</pre>
</div>
</div>
</div>
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
<script src="https://unpkg.com/[email protected]/umd/comlink.js"></script>
CSS
body {
font-family: Input, Menlo, monospace;
background-color: #ddd;
font-size: 20px;
}
canvas {
background-color: #000;
}
.flex {
display: flex;
}
.flex > * {
flex-grow: 1;
}
JavaScript
const module1 = {
meta: {
name: 'Module 1',
},
props: {
textSize: {
type: 'float',
min: 0,
max: 100,
default: 30,
},
text: {
type: 'string',
default: 'đź‘˝',
},
},
draw({ canvas, context, delta }) {
const { width, height } = canvas;
context.font = `${this.textSize}pt sans-serif`;
context.textAlign = 'center';
context.fillStyle = 'rgba(0,0,0,0.11)';
context.fillRect(0, 0, width, height);
context.fillStyle = `hsl(${delta / 40}, 80%, 50%)`;
context.fillText(
this.text,
(width / 2) + ((10 * Math.sin(delta / 400)) * (7 * -Math.sin(delta / 380))),
(height / 2) + ((18 * -Math.cos(delta / 100)) * (2 * Math.cos(delta / 480)))
);
},
};
const module2 = {
meta: {
name: 'Module 2',
},
props: {
circleSize: {
type: 'float',
min: 0,
max: 100,
default: 50,
},
},
draw: function({ canvas, context, delta }) {
const { width, height } = canvas;
context.fillStyle = `hsla(${delta / 100}, 50%, 50%, 0.15)`;
context.fillRect(
0,
0,
width,
height
);
context.fillStyle = `hsla(${delta / 200}, 50%, 80%, 1)`;
context.beginPath();
context.arc(
((width / 2) * (-Math.sin(delta / 1000) + 1)),
((height / 2) * (-Math.cos(delta / 1000) + 1)),
this.circleSize,
0,
Math.PI * 2
);
context.fill();
},
};
//////
const workerScript = `
const state = {
registry: {},
active: [],
};
let canvas;
let context;
let frame;
function render(delta) {
requestAnimationFrame(render);
if (!canvas) return;
const { width, height } = canvas;
if (frame) context.drawImage(frame, 0, 0, width, height);
state.active.forEach((moduleName) => {
state.registry[moduleName].draw({
canvas,
context,
delta: Date.now(),
});
});
}
requestAnimationFrame(render);
self.onmessage = function(e) {
...