Pointer actions
Shapes can react to the pointer: lean toward it, run from it, or be picked up and thrown. The same actions also work on single nodes of a path.
Follow mouse
cursorAction with kind: "follow" leans a layer toward the pointer: strength percent of the way, never more than maxDistance pixels. mirror leans the other way. Here the body leans, and the pupils follow all the way.
import { createStage, cursorAction, frameClock } from "@stew/engine";
const stage = createStage(), clock = frameClock();
// the body leans toward the pointer...
const lean = {
kind: "follow",
strength: 22,
maxDistance: 48,
radius: 0,
smooth: 0.4,
lockX: false,
lockY: false,
mirror: false,
};
cursorAction({ el: body, layer: bodyLayer, anchor: null }, lean, stage, clock);
// ...and the pupils look at it
const look = {
kind: "follow",
strength: 100,
maxDistance: 4,
radius: 0,
smooth: 0.12,
lockX: false,
lockY: false,
mirror: false,
};
cursorAction({ el: eyes, layer: pupils, anchor: null }, look, stage, clock);Run away
With kind: "runAway", a layer flees once the pointer comes within radius pixels: up to strength pixels, further the closer it gets. Outside the radius it comes home.
import { createStage, cursorAction, frameClock } from "@stew/engine";
const stage = createStage(), clock = frameClock();
const flee = {
kind: "runAway",
strength: 70,
maxDistance: 0,
radius: 150,
smooth: 0.35,
lockX: false,
lockY: false,
mirror: false,
};
// each circle measures its body and moves the layer round it
for (const { body, layer } of circles) {
cursorAction({ el: body, layer, anchor: null }, flee, stage, clock);
}Drag
dragAction lets the pointer pick a shape up and move it. Let go fast and it glides on (throw), then springs home (spring). jelly tilts it with its speed, and lift makes it a little bigger while held.
It tells the page as it goes: stew:grab, stew:release and, let go fast, stew:throw, on the shape, bubbling. Rules listen for these.
Springs and throws, in Physics, has the exact rules: how fast, how far, how long.
import { createStage, dragAction, frameClock, GRAB, RELEASE, THROW } from "@stew/engine";
const stage = createStage(), clock = frameClock();
const feel = {
spring: 0.5,
throw: 0.6,
jelly: 0.6,
lift: 0.5,
lockX: false,
lockY: false,
};
dragAction({ el: star, layer: starLayer }, feel, stage, clock);
dragAction({ el: box, layer: boxLayer }, feel, stage, clock);
// what it tells the page bubbles up from the shape
for (const name of [GRAB, RELEASE, THROW]) {
svg.addEventListener(name, () => log(name));
}On single nodes
nodeBend pulls single nodes of a path, each by its own action, and they all bend the outline together. Here every node is pulled toward the pointer and pushed away from it at once.
import { createStage, frameClock, nodeBend } from "@stew/engine";
const stage = createStage(), clock = frameClock();
const toward = {
kind: "follow",
strength: 14,
maxDistance: 24,
radius: 0,
smooth: 0.45,
lockX: false,
lockY: false,
mirror: false,
};
const away = {
kind: "runAway",
strength: 34,
maxDistance: 0,
radius: 120,
smooth: 0.22,
lockX: false,
lockY: false,
mirror: false,
};
// the template says which coordinate pairs each action moves:
// here two actions per node, 12 nodes
nodeBend(
{ path: blob, outline: [blob], carryable: null, template },
nodes.flatMap(() => [toward, away]),
stage, clock,
);Targets and layers
An action measures one element and moves another: the shape (el) and a layer around it (layer), usually a <g>. Measuring the shape and moving its layer keeps the push from feeding back into itself.
Pushes are worked out in screen pixels, then turned into the layer's own frame, so a flipped, turned or scaled group around it can't reverse or stretch the push. Actions write through a stage, so several on one layer add up.
cursorAction(target, params, stage, clock)- Follow mouse or Run away on a shape.
dragAction(target, params, stage, clock)- Drag on a shape.
nodeBend(target, actions, stage, clock)- Follow mouse, Run away or Drag on single nodes of a path, one action per node in the template.
kind"follow" | "runAway"required- Lean toward the pointer, or flee it.
strengthnumberrequired- Follow: percent of the way. Run away: pixels of push.
maxDistancenumberrequired- Follow: the most it leans, in pixels.
radiusnumberrequired- Run away: pixels within which it flees.
smoothnumberrequired- Seconds it takes to get there.
lockX, lockYbooleanrequired- A locked axis never moves.
mirrorbooleanrequired- Follow: lean away instead.
springnumberrequired- Springs home after it's let go: 0.6 + 1.1 x spring seconds. 0 stays put.
thrownumberrequired- Glides on with its speed when let go. 0 stops dead.
jellynumberrequired- Tilts with its speed, up to 16 degrees x jelly.
liftnumberrequired- Bigger while held: 8% x lift.
lockX, lockYbooleanrequired- A locked axis stays.
CursorTarget{ el, layer, anchor }- The shape it measures, the layer it moves, and the point it measures from (
null= the shape's middle). DragTarget{ el, layer }- The shape that tells the page what happens, and the layer it moves.
NodeBendTarget{ path, outline, carryable, template }- The path, every element drawn from its outline, and which coordinate pairs each action moves.