An interactive X-ray effect for SVG illustration
I’m a fan of the interactive illustration present on sites like robbowen.digital and cassie.codes, specifically the self-portraits. Recently, I began an ongoing makeover for this site, including a refresh of my own little selfie icon.
I originally imagined I’d set up something similar to the user-animated illustrations in the above links. Instead, I chose to continue the half-assed visual theme from my previous version of this site: surface skin versus underlying structure, the axis of activity for a front-end developer.
Here’s the approach I took to create an illustrated selfie with interactive x-ray effect, radiation-free!
Surface and structure
I feel some regret I’d already resolved the selfie icon before thinking about a bone layer. Life drawing teachers everywhere collectively sigh. Let’s pretend I purposely took the cartoon-first approach of the Korean artist, Hyungkoo Lee:
These sit in a single SVG
element where, by default, the bone layer will visually overlap the skin layer. A mask element can help manage when the bone layer should be revealed. Here’s some fairly standard SVG markup at this stage:
Creating and referencing an SVG mask
First up, here’s what the basic mask markup looks like:
It’s composed of two SVG elements: a <circle>
and <clipPath>
. A circle is simple and makes visual sense for this project but any complex shape can be used as a mask. The <clipPath>
element assigns the circle as the mask shape for the bone layer.
A <circle>
is a vector shape, one of three graphical object types allowable within an SVG: vector, bitmap, and text. Wrapping the entire mask definition in a <defs>
element provides control over how it will be displayed, rather than automatically being rendered as a visual element.
The mask is applied to the bone layer by wrapping the relevant <path>
elements with a <g>
, with the mask referenced by ID:
The final structure of the prepped SVG will look like this:
Controlling the X-ray effect
The mask is driven by user interaction, so it’s time to leverage some browser-native API goodness with a small amount of JS. Here’s what needs to happen:
- the screen coordinates of the input device will be captured
- these coordinates will be translated to the SVG’s coordinate system
- the mask position will be updated as the input device moves
Set up helpful declarations
createSVGPoint()
sets up an object with properties describing a point in an SVG’s coordinate system. This will be helpful when translating the screen coordinate context to the SVG’s own system.
Capture and manage input coordinates
Ultimately, getCoords
returns a set of 2D coordinates that will visually sync the input device position with the SVG’s own coordinate system (i.e. the mask will align perfectly to the mouse pointer location). For conciseness, the getCoords
function does some double-handling here:
- The user’s screen coordinates are assigned as property values of the
coords
object.coords
, per the declaration above, is anSVGPoint()
object. Properties of this object relate directly to the SVG’s own coordinate system. - In general, the return handles mapping from one coordinate system—the screen—to another: the SVG’s own coordinate system. This part is handled with the
getScreenCTM()
, which translates to the screen context. Chaininginverse()
here ensures the mapping occurs in reverse, translating to the document context. AmatrixTransform()
applied to thecoords
object finally returns the desired coordinates.
If that doesn’t make a whole lot of sense, apologies! There was a fair bit of trial and error here for me, and I don’t have a good grasp on this matrix transformation stuff (the math behind this is beyond my feeble brain).
Update the mask position
All that needs to be done from here to set the mask location is to update the cx
and cy
attributes of the mask element with the freshly mapped coordinates:
The mask is now synced to the input device location, like this: