A re-implementation of an old UI element just made in svg and JS as a Vue 3 component using the composition API built with Vite.
# The Plan
I had written in basic JS against and svg an arm schematic which reuses markup inside itself and apply rotate transforms, by nesting the transforms you get the linked rotations you expect from the arm joints. The code in this blog is simplified to highlight concept, the running source code is available above.
# Rewrite It In Vue
Mapping sliders to svg IDs tightly couples the logic and presentation, I think it's also just about the limit of easy interactivity to add.
If you're unfamiliar with svg or haven't seen <use>
before here's a quick into;
<g>
to svg is like <div>
, it means nothing itself except to group what's below. Every segment is a group, and in that group is the next segment. By applying rotate transforms to the groups you articulate each segment and its children. Second thing to note is the ID #node
in the first group, that circle and 2 lines is an arm segment. By including xlink namespace in the svg root we get access to <use>
whose source can be any anchor in the svg. We can design 1 arm segment and re-ruse that over and over.
Lots of that should sound familiar, we're going from svg > group > use > use > use
to Vue Base Component > Joint Component > Joint Component > Joint Component
. svg has one more handy trick, if we wrap the zero-segment design code inside <defs>
(definitions) it will exist for use in the svg but isn't drawn. That way every segment that appears is a clone and none are special, which refactors to Arm.vue
here (forget the end for a minute):
<svg xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<g id="joint" stroke-width="2" fill="none" transform="translate(350 400)">
<circle cx="0" cy="0" r="30"></circle>
<line x1="-30" y1="0" x2="-30" y2="-100"></line>
<line x1="30" y1="0" x2="30" y2="-100"></line>
</g>
</defs>
<g style="transform-origin: 350px 400px;">
<arm-joint />
</g>
</svg>
This now works for N arm segments, so we actually have Arm Base Component > Joint Component > *
but for that we need ArmJoint.vue
to be recursive. You must define a name
used by <component :is="<name>"
to make it dynamic and refer to itself. Once you enter recursion you must have a way to break out, we'll solve that by keeping track of the segment count inside the recursion. Once we hit zero then draw the ArmEnd.vue
.
# Counting The Recursive Components
# The Ugly Parts
# Vue Component Events
This doesn't use a global state handler so each component must provide an event binding to propagate clicks up to the parents. I've left that out from this post since it's an area I want to tidy up.
# SVG positioning
I'll spare repeating the whole set of components but due to the way I constructed the svg placing the groups by translated offsets, we're going to have to calculate those offsets each iteration. So definitely view the actual components code on gitlab where "ArmJoint" computes its offsets.
# In Reality and the End Result
# The robot arm
Pretty quickly it was apparent that without any positional feedback from the motors in the arm the whole UI wasn't going to work. A workaround would be to "spring" the UI back to centre upon input release, thereby using the input as a binary up/down for the chosen joint. The problem remains however of indicating limits, as this was going to be used as a demonstration for primary or highschool students I can't leave the arm to drive into itself, it would strip the gears and students would do that all day.
# The rewrite
I took a lot to re-implement what as some JS selection, event handlers and clever svg. Some amount of effort went into just learning more about vue, but we're still not caught up to before.
With my current event handling in vue it's difficult to bind controls to or from the arm. The console does print out the segment name and angle ready to setup a listener.
At one point vite threw back this gem upon building for production rather than a dev serve and I just can't tell what it actually had issue with. I just carried on with something else and eventually it would build. It's a little scary to get stopped by a brick wall of an error right as you try to deploy.
All told, Vite was super fast as promised, vue 3 and the composition API is very pleasant.