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. The pointer is a soft weight on it: nearby lines are pushed away, take on the accent where they bend the most and spring back with a small bounce when it moves on, so a moving pointer leaves a short living wake. A click sends a ring out across the field. The hero of this site runs it.

Installation

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

import { Component } from '@angular/core';
import { WrFlowLines } from 'ngwr/animations/flow-lines';

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

Runs on the GPU

It draws on the GPU through WebGL, at a capped resolution, only while on screen and the tab is visible, and at 30 frames a second until a wake, a ripple or the intro is live. Without WebGL it draws nothing. Use one WebGL effect per view, and keep it off low-end and battery-sensitive pages if in doubt.

Basic

Put <wr-flow-lines /> first inside the region it should fill. The drawing is one WebGL pass: every pixel measures its distance to the nearest line of the bent field, so a line is a crisp anti-aliased hairline and a curve is smooth whatever bends it, never a chain of segments. The context is made when the host is first on screen and sized, and the loop runs while it stays on screen and the document is visible: thirty frames a second while only the drift moves, the screen's rate while a wake, a ripple or the intro is live. The parent takes the pointer, passively; a moving finger is ignored, so a scroll over the section is never fought, and a tap may ripple.

<section class="hero">
  <wr-flow-lines />
  <h1>Angular components that bind straight to Signal Forms.</h1>
</section>

Move the pointer through the field.

The lines give way, colour where they bend, and spring back behind it.

The parent

The host is absolute, inset: 0, at z-index: -1, under no pointer, and the field fades out softly at the top and the bottom; the sides run on to the edge. 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.

.hero {
  position: relative;
  isolation: isolate;
}

Calmer

spacing is the distance between two lines, amplitude how far the drift bends a line at most, both in px, speed how fast the drift moves and push how hard the pointer pushes; none turns the push off. Wider lines, a lower and slower wave and a lighter touch is a field that barely stirs.

<wr-flow-lines spacing="14" amplitude="16" speed="0.5" push="0.6" />

Default

As it comes.

spacing="14" amplitude="16" speed="0.5" push="0.6"

Calm.

Vertical

orientation set to vertical runs the lines from top to bottom. Everything else, the drift, the pointer's push and the ripple, works the same way; twist tilts from whichever way they run.

<wr-flow-lines orientation="vertical" />

Lines from top to bottom.

Dense, with a twist

Closer lines, a finer lineWidth (px) and a twist (degrees; positive falls toward the inline end, mirrored under RTL) is a sheet of silk rather than a ruled page; a higher amplitude bends it further. The canvas renders under a pixel budget of its own, so a wide section is never a wide texture, and the cost is per pixel, not per line.

<wr-flow-lines spacing="7" lineWidth="0.75" twist="-8" amplitude="36" />

A tilted sheet.

Dashed

dashed cuts every line into dashes that stream along it, each line starting its dashes at a place of its own, so the field reads as a current rather than a grid. speed moves the dashes as it moves the drift.

<wr-flow-lines dashed speed="1.4" />

A current.

The push and the ripple

reach is how far from the pointer a line is still pushed, in px, with a smooth falloff to nothing at the edge; push is how far it moves them, a seventh of reach at one, nothing right at the pointer and the most about halfway out, so the lines part round it like water round a stone; bounce is how far a released line swings past its place before it settles, none settling without a swing. A click sends a ring out across the field, lifting the lines as it passes and tinting its crest; ripple turns it off.

<wr-flow-lines ripple="false" />

Default

Click anywhere.

ripple="false" bounce="0"

No ring, no swing.

Intro

intro draws the lines in from the inline start the first time the host is on screen, each line a little behind the one before, over about a second and a half. Under reduced motion the field is whole from the first frame.

<wr-flow-lines intro />

Drawn in on arrival.

Colours

A line is --wr-flow-lines-color, the muted ink by default, and a pushed line and a ripple's crest blend toward --wr-flow-lines-accent, the primary by default; set either on the host, where the defaults are declared. Both are read through resolveCssColorChannels and re-read when the scheme changes. The surface is read too: on a dark one the lines lay a little more light than they lay ink on a light one, so the field weighs the same under both schemes.

<wr-flow-lines style="--wr-flow-lines-color: var(--wr-color-info); --wr-flow-lines-accent: var(--wr-color-warning)" />

Info, bending into warning.

API

NameDescriptionTypeDefault
spacingBetween two lines, in CSS px, four at least.number10
lineWidthHow thick a line is, in CSS px.number1
amplitudeHow far the drift bends a line at most, in CSS px.number28
orientationWhich way the lines run across the region.WrFlowLinesOrientation'horizontal'
twistThe lines' tilt from their orientation, in degrees: positive falls toward the inline end.number0
speedHow fast the drift and the dashes move, a multiplier: none holds the field still between pushes.number1
reachHow far from the pointer a line is still pushed, in CSS px.number160
pushHow hard the pointer pushes, a multiplier: one moves a line a seventh of reach at most; none turns the push off.number1
bounceHow much a released line swings past its place, a multiplier: none settles without a swing.number1
rippleWhether a click, or a tap, sends a ring out across the field.booleantrue
introWhether the lines draw in from the inline start when first on screen.booleanfalse
dashedWhether the lines are dashed, with the dashes streaming along them.booleanfalse

Accessibility

PartBehaviour
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 field behaves as before.
Touch A moving finger pushes nothing, nothing is prevented and touch-action is left alone, so a scroll that starts over the region scrolls. A tap sends a ripple, as a click does.
Reduced motion Under prefers-reduced-motion: reduce one still frame of the whole field is drawn, with no push, no ripple and no intro, and nothing moves; a preference that flips mid-flight stops the loop at once. Under forced-colors: active nothing is drawn.
Contrast A line is a hairline laid in the muted ink at well under full coverage, so the text over it reads as it does on the plain surface; the accent shows only where a line is pushed.
No WebGLWithout WebGL nothing is drawn; a lost context is let go and rebuilt when the browser restores it.
ServerThe 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/flow-lines declares each default on its own element.

VariableDefaultDeclared on
--wr-flow-lines-accentvar(--wr-color-primary).wr-flow-lines
--wr-flow-lines-colorvar(--wr-color-on-surface-muted).wr-flow-lines

See also