TJT.Dev


Q5 Canvas

:: 2023-03-22

I can't just make it easy. I said I would just use processing and be done with it, no problem. When I started out as a programmer things were easier in many ways. I'd approach a problem, figure out a solution, impliment said solution, move on with my life. Now it's more like:

  1. Approach a problem.
  2. Think of a dozen more problems, and a dozen different solutions.
  3. ???
  4. Impliment a solution.
  5. Hopefully move on?

Professionally I really need to work on that 2. ??? step. I find it is a step that is easy to get mired in if I'm not structured about how I approach it. Lot's of hand drawn mind mapping I think?

So problem was/is, "I want to build out 2d graphics and post them up on this website." 8 years ago, I would have found p5.js and ran with it. Now I think about this and I'm wondering about build processes, ergonomics, programming paradigms, extensability, performance implications, and many other things. I need to iterate through all sorts of ideas and vet different libraries and build infrastructure around how it is all used.

I think I came to something, but I'm not sure, I may want to adjust moving forward, here it is anyways. First off, p5 is out, it's 800kb minified with many features I'm not going to want from it. On top of that it is massively polluting to the global scope of the page, and I have to jump through hoops to use multiple instances of it on the same page. No thank you. Instead I found, q5.js[1], this has mostly the same API, with some added considerations around not defaulting to exploiting the global scope. And it's 80kb, un-minified, one source file[2]. It's almost what I want, the API is a bit off, but because it is so small I can pull it directly into my source and modify it as needed in short order.

OK, finally, I also need to think about the ergonomics around wanting to put these directly into a post, with the potential for many different pieces being made overtime. This I accomplished with a small little web component and js import statements.

class Q5Canvas extends HTMLElement {
constructor() {
super();
}

async connectedCallback() {
const [{ setup, draw }, { q5 }] = await Promise.all([
import(this.attributes.src.value),
import("/js/vendor/q5.js"),
]);
q5(this, { setup, draw });
}
}

customElements.define("q5-canvas", Q5Canvas);

What this does is creates a q5-canvas custom component that takes a source file. When that component mounts it asyncronously loads the source as well as the slightly modified q5.js[3] then instantiates q5 on the custom element itself. The end result is that I can have a sketch file, that just defines the event functions for the sketch, and that can be embedded into any blog post with a simple call to this custom element. Those scripts don't need to be referenced anywhere else and they don't get loaded until they are needed. Though I do need to load the source for the custom component definition ahead of time.

It's not perfect, I want to do some followups, first most I would like to trigger the load on click rather than on mount, and add some nice UI for it. Also I would like to further modify the q5 code to take a canvas, rather than a parent component, that way it is making no DOM manipulations and it's area of concern is just the canvas context that it was given. Also I would like to add types for that library, though for now it is lovely to just be working with static JS files with no bundler. But a bundler will be needed eventually.

I should also say, this kind of pattern could easily be used for other JS apps that just need to hook into one part of the DOM. React apps come to mind here, I can see myself wanting to build out some dynamic UI with React, then plugging that into a blog post somewhere using a similar custom component. Also potentially WASM based logic, such as illustrations made through Nannou[4].


  1. https://github.com/LingDong-/q5xjs, Ling Dong ↩︎

  2. I've found, as an aside to bundle size, having small single file libraries are extremly flexible in unexpected ways for me. In this and other cases, it has been extremly valuable to pull in the whole source code and just modify it as I please in a way that massive libraries could never be used. I believe this to be very undervalued. ↩︎

  3. https://tjt.dev/js/vendor/q5.js ↩︎

  4. https://github.com/nannou-org/nannou ↩︎