Learning Hyperactiv

 from Red Blob Games
25 Jul 2019

I’m trying out hyperactiv[1], a proxy based observable system that reminds me of Vue and Svelte.

  1. Vue and Svelte are designed to update the DOM, but I often want to work with Canvas/WebGL drawing.
  2. Hyperactiv uses proxies, which Vue was supposed to get in early 2019 before they greatly expanded the scope of Vue 3.
  3. Hyperactiv is much smaller than Vue, because it does so much less. And it can be loaded from a CDN, unlike Svelte.

I set up a simple example here that redraws the canvas with flicker to make it obvious it’s being redrawn:

Three canvases

Here’s the setup code:

const { computed, observe } = hyperactiv;

function getContext(id) {
    const canvas = document.getElementById(id);
    canvas.width = canvas.height = 100;
    const ctx = canvas.getContext('2d');
    return ctx;

const ctx1 = getContext('canvas1'),
      ctx2 = getContext('canvas2'),
      ctx3 = getContext('canvas3');

Now to the good stuff. We can wrap objects we want to watch with observe:

let settings = observe({
    x: 5,
    y: 10,
    w: 3,
    h: 5,

Then we can set up actions using computed:

computed(() => {
    const {x, w} = settings;
    const hue = 30 + Math.random() * 60 | 0;
    ctx1.fillStyle = `hsl(${hue}, 50%, 50%`;
    ctx1.fillRect(0, 0, 100, 100);
    ctx1.fillStyle = "black";
    ctx1.fillRect(x - w/2, 0, w, 100);

This action will redraw the canvas, but only when the settings object is changed. But not any field! I only use the x and w fields of the object, so it will call this redraw function only when one of those two fields changes. Try it yourself: move the x slider and watch the first canvas is redrawn but the second one is not. x=

Let’s add another redraw function:

computed(() => {
    const {y, h} = settings;
    const hue = 30 + Math.random() * 60 | 0;
    ctx2.fillStyle = `hsl(${hue}, 50%, 50%`;
    ctx2.fillRect(0, 0, 100, 100);
    ctx2.fillStyle = "black";
    ctx2.fillRect(0, y - h/2, 100, h);

This one only uses y and h. Try it yourself: move the y slider and watch the second canvas is redrawn but the first one is not. y=

Let’s set up a third one that uses x and y but not w or h:

computed(() => {
    const {x, y} = settings;
    const hue = 30 + Math.random() * 60 | 0;
    ctx3.fillStyle = `hsl(${hue}, 50%, 50%`;
    ctx3.fillRect(0, 0, 100, 100);
    ctx3.fillStyle = "black";
    ctx3.fillRect(x - 2, y - 2, 5, 5);

Moving either slider will redraw this diagram.

I think this is pretty nice. You can set a variable settings.x = 30; and it will automatically redraw the canvases that use x.

But wait! Unlike Vue and Svelte, this doesn’t do anything with the DOM. That means it didn’t tie into the sliders. Let’s add that:

for (let field of ['x', 'y', 'w', 'h']) {
    let inputs = document.querySelectorAll(`input[name=${field}]`);
    computed(() => {
        for (let input of inputs) {
            input.value = settings[field];
               function() { settings[field] = this.valueAsNumber; });

Now if we change the setting anywhere, it will update the slider to match. It works with multiple sliders tied to the same variable, too. Try changing x with any of these controls:


I can do the same for output values:

for (let field of ['x', 'y', 'w', 'h']) {
    let outputs = document.querySelectorAll(`output[name=${field}]`);
    computed(() => {
        for (let output of outputs) {
            output.innerText = settings[field];

My initial impression is that the 1kB hyperactiv library seems useful for some of my projects. With minimal code I can connect sliders to redraw just the things that need to be redrawn without having to explicitly list dependencies. And I can also automatically output values to the DOM without much effort. And it seems like it could work well with the 3kB lit-html[2] DOM library (see discussion[3]). However, it doesn’t work with Map and Set types; it only observes regular objects and arrays.

I don’t have any reason to believe this library is better than others, and I should consider looking at:

Email me , or tweet @redblobgames, or comment: