Skip to content

Eyes that follow the cursor ​

Pip, the mascot from Idle + random fidget, watches your pointer: move it anywhere on this page and the pupils turn towards it, while Pip keeps breathing, blinking and waving. The sample comp is Mascot (folder Interactive examples).

Loading the player…
Live · dotlottie-webDownload .lottie

What the file can and cannot do ​

A dotLottie state machine hears the pointer (Pointer move, Pointer enter, Click), but it is not told where the pointer is: there is no input that holds its position. So the page measures the pointer and hands the file a value. The file only has to expose something to receive it: a slot.

Pip's pupils sit in a group called Gaze whose Position is a slot, gaze. The page sets it a few pixels off centre, towards the pointer. Nothing in the state machine changes: the eyes follow while every state plays, so there is no jump between poses.

In After Effects ​

  1. Inside each eye, put the pupil in its own group (Gaze) and leave that group's Position at 0, 0. The pupil inside it can still be animated (Pip glances left and right in look).
  2. Interactive Studio → Slots and themes → Add a token: pick the left eye's Gaze › Position and name it gaze; then the right eye's, named gaze +2. After Effects allows one Essential Graphics name per property, so the second needs its own: AXO reads a name ending in +2, +3, … as the same slot and writes one gaze for both.

Why a group around the pupil

A slot on a property that has keyframes holds it still: the slot's value replaces the animation. The wrapper keeps the slot on a property that never moves by itself, and the animated pupil inside it keeps its keys.

In AXO's preview: open the file, Slots › gaze › Follow pointer: the same effect without a page.

On a web page ​

js
// the version these pages use
import { DotLottie } from "https://cdn.jsdelivr.net/npm/@lottiefiles/dotlottie-web@0.81.0/+esm";

const canvas = document.querySelector("#pip");
const pip = new DotLottie({ canvas, src: "mascot.lottie", autoplay: true });
pip.addEventListener("load", () => {
  pip.stateMachineLoad("pip");
  pip.stateMachineStart();
});

const REACH = 5; // px the pupils may move
window.addEventListener("pointermove", (e) => {
  const r = canvas.getBoundingClientRect();
  const dx = e.clientX - (r.left + r.width / 2);
  const dy = e.clientY - (r.top + r.height / 2);
  const d = Math.hypot(dx, dy) || 1;
  const k = Math.min(1, d / 300) * REACH; // nearer pointer, smaller turn
  pip.setVectorSlot("gaze", [(dx / d) * k, (dy / d) * k]);
});

The slot redraws on the next frame the player draws. Pip always plays something, so that is at once; a file that sits paused would need a setFrame after the slot to show it.

Variations ​

  • A head that turns: a Rotation slot on a wrapper group, set from the horizontal distance.
  • A card that tilts towards the pointer: Rotation on the card, Position on its shadow, the other way.
  • A spotlight: the Position of a soft circle behind the content.

AXO for After Effects