I'm super excited about Svelte! Compiling JS instead of doing virtual-DOM diffing is loads faster and opens up some amazing possibilities – once you get past the slightly weird syntax, that is.

A great test for whether I understand the basics of a framework is writing a Knob/Dial component. It should work the same as in most music software these days – drag up, and it rotates clockwise, increasing the value; drag down, and the inverse happens.

A Knob component with external state

This is a good test because it has three "representations" of a value:

  1. The actual value, maybe something like a volume from -12dB to +24dB;
  2. The pixel offset of the user's cursor from their initial click location; and
  3. The amount of rotation applied to the knob itself.

Keeping these three values in-sync is an interesting problem. Dragging the cursor changes the pixel offset, which should affect both the rotation of the dial and the value reported by the knob component.

But not only that: the knob should also be "settable" from outside. In React-land, we think of this as a "controlled component" – if, say, the volume is being automated from elsewhere, it should be possible to control the knob (and its underlying values) without using the cursor. (In the gif above I've simulated this with a "set to 50%" button.)

I've found this is a great way to dig into whether I fully understand how data should flow in any given framework. For example, what's the "source of truth" of the three values: the pixel offset, the rotation, or the underlying value?

Almost always, it's a better idea to use the underlying value as the source of truth. But there's a few issues which occasionally crop up with this approach:

  1. The underlying value isn't actually accessible synchronously – e.g. all you have is a set() method, and a get() method which returns a Promise!
  2. The underlying value can't be changed many times a second – maybe it's a parameter in a physics calculation happening in a web worker;
  3. The underlying value isn't linear – for example, it makes sense to increase amplitude logarithmically for a perceieved linear increase in volume.

Ideally, the Knob component should be built in such a way that these details don't matter at all. Integrating/extending this component to handle such cases is a good exercise in decoupling the UI and the inner workings of an application.

Here's my code for the animated example above – it's using Svelte's 2-way data binding, and anecdotally it seems a lot more performant than the React alternative – I think this is a case where the lack of virtual DOM diffing really shines!

https://svelte.dev/repl/e76f28b965334d37a792ed1f2ecb67b1?version=3.29.4

And the code is copied here, if the Svelte online REPL doesn't work for you:

Enjoy!