import { Component } from '@angular/core';
import { WrFluidCursor } from 'ngwr/animations/fluid-cursor';
@Component({ imports: [WrFluidCursor] })
export class MyComponent {}Fluid Cursor
Coloured ink behind a region's content, stirred by the pointer: a move lays dye and momentum into a fluid, and the dye swirls into curling eddies, folds over itself, drifts on with the motion the pointer gave it and fades out over a few seconds. A fast stroke leaves a long sweeping trail with vortices peeling off; a slow one soft billows; a click a radial splash.
Installation
Import the class, and @use the entry point in your global styles. Nothing warns you when the @use is missing.
Runs on the GPU
Basic
Put <wr-fluid-cursor /> first inside the region it should fill. The parent takes the pointer, passively: the host and its canvas never do, and a finger that draws is never stopped from scrolling. The fluid is a stable-fluids solver in WebGL, one canvas; the context is made at the first move, not at mount, and the frame loop runs only while the host is on screen, the document is visible and the last move is nearer than trail, then stops on its own. Move the pointer over the box; click in it.
<footer class="site-footer">
<wr-fluid-cursor />
…
</footer>The parent
The host is absolute, inset: 0, at z-index: -1, under no pointer and masked out at the edges, so ink carried to the border dissolves rather than ending in a straight cut. The parent provides two things: it is positioned, so the host fills it, and isolated, so the host's negative z-index lands at the floor of the parent's own stacking context, under the content and over the page background.
.site-footer {
position: relative;
isolation: isolate;
}Colours
A stroke cycles through --wr-fluid-cursor-color-1 to -5, primary, danger, warning, success and info by default, one full cycle every 900px of travel; set them on the host, where the defaults are declared. rainbow cycles the hue circle instead. The surface decides how the ink is laid: on a light one it covers as ink, its colour at full saturation under a coverage that rises with its density, so it reads as ink on white and not as a glow; on a dark one the thin end is pulled down, so a trail goes out crisply instead of hanging as haze.
<wr-fluid-cursor rainbow /><wr-fluid-cursor style="--wr-fluid-cursor-color-1: var(--wr-color-warning); --wr-fluid-cursor-color-2: var(--wr-color-danger)" />Calmer
swirl is how strongly the eddies are fed back, force how much of the pointer's speed the fluid takes, size the radius of what a move lays, in px, intensity how much dye it lays, trail how long the dye lasts, in seconds, and momentum how long the motion does. Less swirl and force with a wider, longer trail is slow ink in still water.
<wr-fluid-cursor swirl="0.3" force="0.5" size="40" intensity="0.6" trail="5" />Click splash
A primary click or a tap on the parent splashes the ink outward from the point, a ring of eight rays in the next colour of the cycle; clickSplash false turns it off. The listener is passive and stops nothing, so a link or a button over the ink works as before.
<wr-fluid-cursor [clickSplash]="false" />Quality
quality sizes the grids and the pressure solve: the velocity grid is 96, 128 or 192 texels on its short side, the dye grid 384, 640 or 1024 and no finer than the canvas, and the solve 12, 20 or 28 iterations. Both grids keep the region's aspect ratio and sit under the same pixel budget as the other canvas backdrops, so a wide footer is never a wide texture; shading false drops the relief lighting, the cheapest pass to lose.
<wr-fluid-cursor quality="high" />API
| Name | Description | Type | Default |
|---|---|---|---|
intensity | How much dye a move lays, a multiplier. | number | 1 |
size | The radius of a splat, in CSS px: how wide the ink the pointer lays is. | number | 24 |
force | How much of the pointer's speed the fluid takes, a multiplier. | number | 1 |
swirl | How strongly the eddies are fed back, a multiplier: more curls the trail tighter, none lets it smooth out. | number | 1 |
trail | How long the dye lasts, in seconds, from a fresh splat to gone. | number | 3 |
momentum | How long the motion the pointer gave lasts, in seconds. | number | 6 |
shading | Whether the dye is shaded by its own relief, a slight lighting from the top left. | boolean | true |
rainbow | Whether a stroke cycles the hue circle instead of the theme's palette. | boolean | false |
clickSplash | Whether a click or a tap splashes the ink outward from the point. | boolean | true |
quality | How finely the fluid is simulated: the grids and the pressure solve. | WrFluidCursorQuality | 'medium' |
Accessibility
| Part | Behaviour |
|---|---|
| Screen readers | Decorative: the host is aria-hidden, holds no text, takes no pointer and is never focusable. The listeners on the parent are passive and stop nothing, so every control over the ink behaves as before. |
| Touch | A finger draws like a mouse, and is never stopped from scrolling: nothing is prevented and touch-action is left alone, so a scroll that starts over the region scrolls. |
| Reduced motion | Under prefers-reduced-motion: reduce nothing is drawn and no context is made; a trail caught moving when it is turned on is cleared. Under forced-colors: active nothing is drawn either. |
| No WebGL | Without WebGL2, or WebGL1 with half-float render targets, nothing is drawn; a lost context is let go and rebuilt when the browser restores it. |
| Server | The empty canvas is rendered, absolutely positioned, so the browser's first draw shifts no layout. |
CSS variables
Set these on the element itself, with a rule more specific than its Declared on selector. In a global stylesheet, put a class of yours on an ancestor it really has: <body> for an overlay panel, which renders outside your markup. In a component stylesheet the selector alone works, but only for an element in that component's own template. A value on a wrapper or :root is shadowed, because ngwr/animations/fluid-cursor declares each default on its own element.
| Variable | Default | Declared on |
|---|---|---|
--wr-fluid-cursor-color-1 | var(--wr-color-primary) | .wr-fluid-cursor |
--wr-fluid-cursor-color-2 | var(--wr-color-danger) | .wr-fluid-cursor |
--wr-fluid-cursor-color-3 | var(--wr-color-warning) | .wr-fluid-cursor |
--wr-fluid-cursor-color-4 | var(--wr-color-success) | .wr-fluid-cursor |
--wr-fluid-cursor-color-5 | var(--wr-color-info) | .wr-fluid-cursor |
See also
Animation Flow LinesA dense field of thin parallel lines behind a section's content that flows like silk: a slow, smooth noise field bends every line in long gentle waves, neighbours moving together, so the field reads like a surface or a contour map in a breeze.Animation SpotlightA soft circle of light sits under the pointer on a card's surface and follows it, and the card's border brightens a little near it.Guide ColorsThe --wr-color-* tokens, and which one to use for fills, text and borders.Component AlertAn inline status message.