# 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.

Source: https://ngwr.dev/animations/fluid-cursor  
Kind: Animation, Component, WebGL  
Added in v15.7

## Installation

Import the class, and `@use` the entry point in your global styles. Nothing warns you when the `@use` is missing.

```typescript
import { Component } from '@angular/core';
import { WrFluidCursor } from 'ngwr/animations/fluid-cursor';

@Component({ imports: [WrFluidCursor] })
export class MyComponent {}
```

```scss
@use 'ngwr/animations/fluid-cursor';
```

## 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.

```angular-html
<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.

```scss
.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.

```angular-html
<wr-fluid-cursor rainbow />
```

```angular-html
<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.

```angular-html
<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.

```angular-html
<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.

```angular-html
<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

- [Flow Lines](https://ngwr.dev/animations/flow-lines) — A 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.
- [Spotlight](https://ngwr.dev/animations/spotlight) — A 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.
- [Colors](https://ngwr.dev/guides/tokens/colors) — The --wr-color-* tokens, and which one to use for fills, text and borders.
- [Alert](https://ngwr.dev/reference/components/alert) — An inline status message.
