AnimationAdded in v15.2

Iridescence

Folded silk behind your content, its hue turning with each fold, stirred by the pointer.

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 { WrIridescence } from 'ngwr/bits/iridescence';

@Component({ imports: [WrIridescence] })
export class MyComponent {}
Angular adaptation of an effect by David Haz / reactbits.dev.

Playground

Put it first in a positioned box; it fills that box. Give the content after it a position of its own, or the silk paints over it. Move the pointer across it to stir the folds.

<section style="position: relative; min-height: 18rem; overflow: hidden">
  <wr-iridescence
    [speed]="1"
    [amplitude]="1"
  />
</section>

Customize

Colour
Speed
1.0
Amplitude
1.0
Follow pointer

Behind a hero

Copy goes on a card, because text on the light default needs a card or a scrim (see Text contrast). The pointer still stirs the folds around it, and the card keeps every click.

Drape it behind the launch

The folds stay behind the card, and the button stays clickable.

Get started
<section class="hero">
  <wr-iridescence />
  <wr-card class="hero__card">
    <h3>Drape it behind the launch</h3>
    <p>The folds stay behind the card, and the button stays clickable.</p>
    <wr-btn color="primary">Get started</wr-btn>
  </wr-card>
</section>

<style>
  .hero {
    position: relative;
    display: grid;
    place-items: center;
    min-height: 22rem;
    padding: 2rem;
    overflow: hidden;
  }
  .hero__card { max-width: 26rem; text-align: center; }
</style>

Tint

color is multiplied over the picture, so white leaves it as it is. It takes any CSS colour, and a theme token follows the colour scheme. Pass an opaque colour: the canvas drops alpha and the stand-in keeps it.

<section style="position: relative; min-height: 12rem; overflow: hidden">
  <wr-iridescence color="var(--wr-color-primary)" />
</section>

Text contrast

On the default tints, --wr-color-on-surface holds 4.5:1 in the dark scheme and not in the light one, so text on the light default needs a scrim or a card.

SchemeTintTextMedian pixelWorst 1% of pixelsSame, worst frameWorst pixel
Light#ffffff#1118278.57:13.93:12.80:12.64:1
Dark#5c669e#e9ebef7.18:15.01:14.89:14.80:1

Measured in Chromium with a software renderer (SwiftShader) at 320×180: 240 phase combinations with the pointer at rest and 960 frames with it at the four corners, each figure the lower of the two. Worst 1% is the 1st-percentile pixel in light and the 99th in dark. Not measured: a real GPU, prefers-contrast: more, an amplitude other than 1, any other tint or text colour.

The CSS stand-in under the canvas, shown before the first frame and without WebGL2, reads 4.55:1 in light and 5.28:1 in dark at those percentiles, in a 960×540 screenshot sampled every 37th pixel.

API

NameDescriptionTypeDefault
colorTint multiplied over the picture, any opaque CSS colour: the canvas drops alpha and the stand-in keeps it. Unset, empty or blank, it follows the theme.string | nullnull
speedSpeed of the folds; 0 holds them, a negative value reverses them. Changing it never jumps them.number1
amplitudeHow hard the pointer stirs the folds; 0 leaves the pointer out, a negative value stirs the other way.number1
followPointerWhether the pointer stirs the folds at all.booleantrue

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/bits/iridescence declares each default on its own element.

VariableDefaultDeclared on
--wr-iridescence-colorlight-dark(#ffffff, #5c669e).wr-iridescence

See also