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:
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 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
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.
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 an SVGPoint() 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. Chaining inverse() here ensures the mapping occurs in reverse, translating to the document context. A matrixTransform() applied to the coords 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: