interactive-bg 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +202 -2
- package/dist/core/canvas2d-renderer.cjs +119 -0
- package/dist/core/canvas2d-renderer.cjs.map +1 -0
- package/dist/core/canvas2d-renderer.d.cts +7 -0
- package/dist/core/canvas2d-renderer.d.ts +7 -0
- package/dist/core/canvas2d-renderer.js +119 -0
- package/dist/core/canvas2d-renderer.js.map +1 -0
- package/dist/core/color.cjs +46 -0
- package/dist/core/color.cjs.map +1 -0
- package/dist/core/color.d.cts +6 -0
- package/dist/core/color.d.ts +6 -0
- package/dist/core/color.js +46 -0
- package/dist/core/color.js.map +1 -0
- package/dist/core/engine.cjs +367 -0
- package/dist/core/engine.cjs.map +1 -0
- package/dist/core/engine.d.cts +69 -0
- package/dist/core/engine.d.ts +69 -0
- package/dist/core/engine.js +366 -0
- package/dist/core/engine.js.map +1 -0
- package/dist/core/style.cjs +21 -0
- package/dist/core/style.cjs.map +1 -0
- package/dist/core/style.d.cts +85 -0
- package/dist/core/style.d.ts +85 -0
- package/dist/core/style.js +21 -0
- package/dist/core/style.js.map +1 -0
- package/dist/core/surface.cjs +25 -0
- package/dist/core/surface.cjs.map +1 -0
- package/dist/core/surface.d.cts +42 -0
- package/dist/core/surface.d.ts +42 -0
- package/dist/core/surface.js +24 -0
- package/dist/core/surface.js.map +1 -0
- package/dist/core/webgl-renderer.cjs +137 -0
- package/dist/core/webgl-renderer.cjs.map +1 -0
- package/dist/core/webgl-renderer.d.cts +8 -0
- package/dist/core/webgl-renderer.d.ts +8 -0
- package/dist/core/webgl-renderer.js +137 -0
- package/dist/core/webgl-renderer.js.map +1 -0
- package/dist/core/webgl.cjs +62 -0
- package/dist/core/webgl.cjs.map +1 -0
- package/dist/core/webgl.d.cts +15 -0
- package/dist/core/webgl.d.ts +15 -0
- package/dist/core/webgl.js +60 -0
- package/dist/core/webgl.js.map +1 -0
- package/dist/dots/canvas2d.cjs +14 -0
- package/dist/dots/canvas2d.cjs.map +1 -0
- package/dist/dots/canvas2d.d.cts +5 -0
- package/dist/dots/canvas2d.d.ts +5 -0
- package/dist/dots/canvas2d.js +13 -0
- package/dist/dots/canvas2d.js.map +1 -0
- package/dist/dots/index.cjs +16 -0
- package/dist/dots/index.cjs.map +1 -0
- package/dist/dots/index.d.cts +5 -0
- package/dist/dots/index.d.ts +5 -0
- package/dist/dots/index.js +15 -0
- package/dist/dots/index.js.map +1 -0
- package/dist/dots/webgl.cjs +14 -0
- package/dist/dots/webgl.cjs.map +1 -0
- package/dist/dots/webgl.d.cts +5 -0
- package/dist/dots/webgl.d.ts +5 -0
- package/dist/dots/webgl.js +13 -0
- package/dist/dots/webgl.js.map +1 -0
- package/dist/index.cjs +24 -0
- package/dist/index.d.cts +18 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +11 -0
- package/dist/index.umd.js +211 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/plus/canvas2d.cjs +14 -0
- package/dist/plus/canvas2d.cjs.map +1 -0
- package/dist/plus/canvas2d.d.cts +5 -0
- package/dist/plus/canvas2d.d.ts +5 -0
- package/dist/plus/canvas2d.js +13 -0
- package/dist/plus/canvas2d.js.map +1 -0
- package/dist/plus/index.cjs +16 -0
- package/dist/plus/index.cjs.map +1 -0
- package/dist/plus/index.d.cts +5 -0
- package/dist/plus/index.d.ts +5 -0
- package/dist/plus/index.js +15 -0
- package/dist/plus/index.js.map +1 -0
- package/dist/plus/webgl.cjs +14 -0
- package/dist/plus/webgl.cjs.map +1 -0
- package/dist/plus/webgl.d.cts +5 -0
- package/dist/plus/webgl.d.ts +5 -0
- package/dist/plus/webgl.js +13 -0
- package/dist/plus/webgl.js.map +1 -0
- package/dist/shared/buffer.cjs +20 -0
- package/dist/shared/buffer.cjs.map +1 -0
- package/dist/shared/buffer.d.cts +11 -0
- package/dist/shared/buffer.d.ts +11 -0
- package/dist/shared/buffer.js +19 -0
- package/dist/shared/buffer.js.map +1 -0
- package/dist/shared/grid-webgl.cjs +63 -0
- package/dist/shared/grid-webgl.cjs.map +1 -0
- package/dist/shared/grid-webgl.d.cts +10 -0
- package/dist/shared/grid-webgl.d.ts +10 -0
- package/dist/shared/grid-webgl.js +61 -0
- package/dist/shared/grid-webgl.js.map +1 -0
- package/dist/shared/grid.cjs +133 -0
- package/dist/shared/grid.cjs.map +1 -0
- package/dist/shared/grid.d.cts +72 -0
- package/dist/shared/grid.d.ts +72 -0
- package/dist/shared/grid.js +123 -0
- package/dist/shared/grid.js.map +1 -0
- package/dist/shared/noise.cjs +166 -0
- package/dist/shared/noise.cjs.map +1 -0
- package/dist/shared/noise.d.cts +4 -0
- package/dist/shared/noise.d.ts +4 -0
- package/dist/shared/noise.js +166 -0
- package/dist/shared/noise.js.map +1 -0
- package/dist/styles/dots/canvas2d.cjs +99 -0
- package/dist/styles/dots/canvas2d.cjs.map +1 -0
- package/dist/styles/dots/canvas2d.d.cts +7 -0
- package/dist/styles/dots/canvas2d.d.ts +7 -0
- package/dist/styles/dots/canvas2d.js +99 -0
- package/dist/styles/dots/canvas2d.js.map +1 -0
- package/dist/styles/dots/definition.cjs +15 -0
- package/dist/styles/dots/definition.cjs.map +1 -0
- package/dist/styles/dots/definition.d.cts +7 -0
- package/dist/styles/dots/definition.d.ts +7 -0
- package/dist/styles/dots/definition.js +16 -0
- package/dist/styles/dots/definition.js.map +1 -0
- package/dist/styles/dots/motion.cjs +6 -0
- package/dist/styles/dots/motion.cjs.map +1 -0
- package/dist/styles/dots/motion.d.cts +2 -0
- package/dist/styles/dots/motion.d.ts +2 -0
- package/dist/styles/dots/motion.js +6 -0
- package/dist/styles/dots/motion.js.map +1 -0
- package/dist/styles/dots/options.cjs +33 -0
- package/dist/styles/dots/options.cjs.map +1 -0
- package/dist/styles/dots/options.d.cts +33 -0
- package/dist/styles/dots/options.d.ts +33 -0
- package/dist/styles/dots/options.js +32 -0
- package/dist/styles/dots/options.js.map +1 -0
- package/dist/styles/dots/webgl.cjs +113 -0
- package/dist/styles/dots/webgl.cjs.map +1 -0
- package/dist/styles/dots/webgl.d.cts +4 -0
- package/dist/styles/dots/webgl.d.ts +4 -0
- package/dist/styles/dots/webgl.js +113 -0
- package/dist/styles/dots/webgl.js.map +1 -0
- package/dist/styles/plus/canvas2d.cjs +88 -0
- package/dist/styles/plus/canvas2d.cjs.map +1 -0
- package/dist/styles/plus/canvas2d.d.cts +8 -0
- package/dist/styles/plus/canvas2d.d.ts +8 -0
- package/dist/styles/plus/canvas2d.js +88 -0
- package/dist/styles/plus/canvas2d.js.map +1 -0
- package/dist/styles/plus/definition.cjs +17 -0
- package/dist/styles/plus/definition.cjs.map +1 -0
- package/dist/styles/plus/definition.d.cts +9 -0
- package/dist/styles/plus/definition.d.ts +9 -0
- package/dist/styles/plus/definition.js +18 -0
- package/dist/styles/plus/definition.js.map +1 -0
- package/dist/styles/plus/motion.cjs +18 -0
- package/dist/styles/plus/motion.cjs.map +1 -0
- package/dist/styles/plus/motion.d.cts +5 -0
- package/dist/styles/plus/motion.d.ts +5 -0
- package/dist/styles/plus/motion.js +18 -0
- package/dist/styles/plus/motion.js.map +1 -0
- package/dist/styles/plus/options.cjs +45 -0
- package/dist/styles/plus/options.cjs.map +1 -0
- package/dist/styles/plus/options.d.cts +45 -0
- package/dist/styles/plus/options.d.ts +45 -0
- package/dist/styles/plus/options.js +43 -0
- package/dist/styles/plus/options.js.map +1 -0
- package/dist/styles/plus/turns.cjs +102 -0
- package/dist/styles/plus/turns.cjs.map +1 -0
- package/dist/styles/plus/turns.d.cts +17 -0
- package/dist/styles/plus/turns.d.ts +17 -0
- package/dist/styles/plus/turns.js +102 -0
- package/dist/styles/plus/turns.js.map +1 -0
- package/dist/styles/plus/webgl.cjs +121 -0
- package/dist/styles/plus/webgl.cjs.map +1 -0
- package/dist/styles/plus/webgl.d.cts +8 -0
- package/dist/styles/plus/webgl.d.ts +8 -0
- package/dist/styles/plus/webgl.js +121 -0
- package/dist/styles/plus/webgl.js.map +1 -0
- package/dist/styles/topo/canvas2d.cjs +77 -0
- package/dist/styles/topo/canvas2d.cjs.map +1 -0
- package/dist/styles/topo/canvas2d.d.cts +4 -0
- package/dist/styles/topo/canvas2d.d.ts +4 -0
- package/dist/styles/topo/canvas2d.js +77 -0
- package/dist/styles/topo/canvas2d.js.map +1 -0
- package/dist/styles/topo/contours.cjs +83 -0
- package/dist/styles/topo/contours.cjs.map +1 -0
- package/dist/styles/topo/contours.d.cts +9 -0
- package/dist/styles/topo/contours.d.ts +9 -0
- package/dist/styles/topo/contours.js +83 -0
- package/dist/styles/topo/contours.js.map +1 -0
- package/dist/styles/topo/definition.cjs +13 -0
- package/dist/styles/topo/definition.cjs.map +1 -0
- package/dist/styles/topo/definition.d.cts +7 -0
- package/dist/styles/topo/definition.d.ts +7 -0
- package/dist/styles/topo/definition.js +14 -0
- package/dist/styles/topo/definition.js.map +1 -0
- package/dist/styles/topo/field.cjs +118 -0
- package/dist/styles/topo/field.cjs.map +1 -0
- package/dist/styles/topo/field.d.cts +26 -0
- package/dist/styles/topo/field.d.ts +26 -0
- package/dist/styles/topo/field.js +118 -0
- package/dist/styles/topo/field.js.map +1 -0
- package/dist/styles/topo/options.cjs +39 -0
- package/dist/styles/topo/options.cjs.map +1 -0
- package/dist/styles/topo/options.d.cts +52 -0
- package/dist/styles/topo/options.d.ts +52 -0
- package/dist/styles/topo/options.js +38 -0
- package/dist/styles/topo/options.js.map +1 -0
- package/dist/styles/topo/terrain.cjs +22 -0
- package/dist/styles/topo/terrain.cjs.map +1 -0
- package/dist/styles/topo/terrain.d.cts +11 -0
- package/dist/styles/topo/terrain.d.ts +11 -0
- package/dist/styles/topo/terrain.js +22 -0
- package/dist/styles/topo/terrain.js.map +1 -0
- package/dist/styles/topo/webgl.cjs +150 -0
- package/dist/styles/topo/webgl.cjs.map +1 -0
- package/dist/styles/topo/webgl.d.cts +7 -0
- package/dist/styles/topo/webgl.d.ts +7 -0
- package/dist/styles/topo/webgl.js +150 -0
- package/dist/styles/topo/webgl.js.map +1 -0
- package/dist/topo/canvas2d.cjs +14 -0
- package/dist/topo/canvas2d.cjs.map +1 -0
- package/dist/topo/canvas2d.d.cts +5 -0
- package/dist/topo/canvas2d.d.ts +5 -0
- package/dist/topo/canvas2d.js +13 -0
- package/dist/topo/canvas2d.js.map +1 -0
- package/dist/topo/index.cjs +16 -0
- package/dist/topo/index.cjs.map +1 -0
- package/dist/topo/index.d.cts +5 -0
- package/dist/topo/index.d.ts +5 -0
- package/dist/topo/index.js +15 -0
- package/dist/topo/index.js.map +1 -0
- package/dist/topo/webgl.cjs +14 -0
- package/dist/topo/webgl.cjs.map +1 -0
- package/dist/topo/webgl.d.cts +5 -0
- package/dist/topo/webgl.d.ts +5 -0
- package/dist/topo/webgl.js +13 -0
- package/dist/topo/webgl.js.map +1 -0
- package/package.json +192 -4
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
const require_surface = require("./surface.cjs");
|
|
2
|
+
// src/core/engine.ts
|
|
3
|
+
var engineDefaults = Object.freeze({
|
|
4
|
+
renderer: "auto",
|
|
5
|
+
backgroundColor: null,
|
|
6
|
+
fadeOutTop: 0,
|
|
7
|
+
fadeOutRight: 0,
|
|
8
|
+
fadeOutBottom: 0,
|
|
9
|
+
fadeOutLeft: 0,
|
|
10
|
+
interactive: true,
|
|
11
|
+
maxDpr: 1.5,
|
|
12
|
+
maxFps: 60,
|
|
13
|
+
idleFps: 30,
|
|
14
|
+
respectReducedMotion: true
|
|
15
|
+
});
|
|
16
|
+
/**
|
|
17
|
+
* Mount an interactive background.
|
|
18
|
+
*
|
|
19
|
+
* - `target` `null`/omitted: a fixed, full-viewport canvas is placed behind the page.
|
|
20
|
+
* - `target` is a `<canvas>`: it is drawn into directly and sized by your CSS.
|
|
21
|
+
* - `target` is any other element (or a selector): a canvas is appended that
|
|
22
|
+
* fills it. The element gets `position: relative` if it was `static`.
|
|
23
|
+
*
|
|
24
|
+
* @example createBackground('#hero', topo({ levels: 14 }), { maxFps: 30 })
|
|
25
|
+
*/
|
|
26
|
+
function createBackground(target, style, engineOptions = {}) {
|
|
27
|
+
const host = typeof target === "string" ? document.querySelector(target) : target;
|
|
28
|
+
if (typeof target === "string" && !host) throw new Error(`interactive-bg: no element matches "${target}"`);
|
|
29
|
+
const definition = style.definition;
|
|
30
|
+
let options = { ...style.options };
|
|
31
|
+
let engine = {
|
|
32
|
+
...engineDefaults,
|
|
33
|
+
...engineOptions
|
|
34
|
+
};
|
|
35
|
+
let backgroundColor = resolveColor(engine.backgroundColor);
|
|
36
|
+
let fade = resolveFade(engine);
|
|
37
|
+
const ownsCanvas = !(host instanceof HTMLCanvasElement);
|
|
38
|
+
const hostPosition = ownsCanvas && host ? host.style.position : null;
|
|
39
|
+
let canvas = ownsCanvas ? mountCanvas(host ?? null) : host;
|
|
40
|
+
const surface = require_surface.createSurface(() => canvas, definition.renderers, options, engine.renderer, () => {
|
|
41
|
+
needsDraw = true;
|
|
42
|
+
wake();
|
|
43
|
+
}, ownsCanvas ? () => {
|
|
44
|
+
const fresh = mountCanvas(host ?? null);
|
|
45
|
+
canvas.remove();
|
|
46
|
+
canvas = fresh;
|
|
47
|
+
} : void 0) ?? blankSurface(definition);
|
|
48
|
+
let width = 0;
|
|
49
|
+
let height = 0;
|
|
50
|
+
let dpr = 1;
|
|
51
|
+
let pointerX = 0;
|
|
52
|
+
let pointerY = 0;
|
|
53
|
+
let targetX = 0;
|
|
54
|
+
let targetY = 0;
|
|
55
|
+
let influence = 0;
|
|
56
|
+
let targetInfluence = 0;
|
|
57
|
+
/** Clicks since the last rendered frame, and the array handed to the style for the current one (swapped each frame). */
|
|
58
|
+
let clicks = [];
|
|
59
|
+
let frameClicks = [];
|
|
60
|
+
const pointer = {
|
|
61
|
+
x: 0,
|
|
62
|
+
y: 0,
|
|
63
|
+
influence: 0
|
|
64
|
+
};
|
|
65
|
+
const frameState = {
|
|
66
|
+
width: 0,
|
|
67
|
+
height: 0,
|
|
68
|
+
dpr: 1,
|
|
69
|
+
dt: 0,
|
|
70
|
+
reducedMotion: false,
|
|
71
|
+
pointer,
|
|
72
|
+
clicks: frameClicks
|
|
73
|
+
};
|
|
74
|
+
/** The style reported a short-lived effect still playing in its last frame. */
|
|
75
|
+
let styleBusy = false;
|
|
76
|
+
let lastFrame = 0;
|
|
77
|
+
let rafId = 0;
|
|
78
|
+
let timerId = 0;
|
|
79
|
+
let paused = false;
|
|
80
|
+
let visible = true;
|
|
81
|
+
let intersecting = true;
|
|
82
|
+
let destroyed = false;
|
|
83
|
+
let needsDraw = true;
|
|
84
|
+
const reducedMotionQuery = window.matchMedia?.("(prefers-reduced-motion: reduce)");
|
|
85
|
+
let reducedMotion = reducedMotionQuery?.matches ?? false;
|
|
86
|
+
function holdStill() {
|
|
87
|
+
return engine.respectReducedMotion && reducedMotion;
|
|
88
|
+
}
|
|
89
|
+
/** Is the style's own ambient animation running (as opposed to only reacting to the pointer)? */
|
|
90
|
+
function animating() {
|
|
91
|
+
return surface.name !== null && definition.animates(options) && !holdStill();
|
|
92
|
+
}
|
|
93
|
+
/** Does something need the full frame rate: the pointer's effect easing, or a style effect playing? */
|
|
94
|
+
function busy() {
|
|
95
|
+
return styleBusy || clicks.length > 0 || pointerEasing();
|
|
96
|
+
}
|
|
97
|
+
/** Frame rate for the ambient animation; `0` means uncapped for the engine options. */
|
|
98
|
+
function effectiveIdleFps() {
|
|
99
|
+
const configured = engine.maxFps <= 0 ? engine.idleFps : engine.idleFps <= 0 ? engine.maxFps : Math.min(engine.maxFps, engine.idleFps);
|
|
100
|
+
const needed = definition.idleFps?.(options) ?? 0;
|
|
101
|
+
if (needed <= 0) return configured;
|
|
102
|
+
return configured <= 0 ? needed : Math.min(configured, needed);
|
|
103
|
+
}
|
|
104
|
+
function frameInterval(fps) {
|
|
105
|
+
return fps > 0 ? 1e3 / fps : 0;
|
|
106
|
+
}
|
|
107
|
+
function canRun() {
|
|
108
|
+
return !destroyed && !paused && visible;
|
|
109
|
+
}
|
|
110
|
+
/** Is the pointer's effect still catching up with the pointer? */
|
|
111
|
+
function pointerEasing() {
|
|
112
|
+
if (influence !== targetInfluence) return true;
|
|
113
|
+
return influence > 0 && (Math.abs(targetX - pointerX) > .5 || Math.abs(targetY - pointerY) > .5);
|
|
114
|
+
}
|
|
115
|
+
function resize() {
|
|
116
|
+
const rect = canvas.getBoundingClientRect();
|
|
117
|
+
const nextWidth = Math.max(1, Math.round(rect.width));
|
|
118
|
+
const nextHeight = Math.max(1, Math.round(rect.height));
|
|
119
|
+
const nextDpr = Math.min(window.devicePixelRatio || 1, Math.max(.5, engine.maxDpr));
|
|
120
|
+
if (nextWidth === width && nextHeight === height && nextDpr === dpr) return;
|
|
121
|
+
width = nextWidth;
|
|
122
|
+
height = nextHeight;
|
|
123
|
+
dpr = nextDpr;
|
|
124
|
+
canvas.width = Math.round(width * dpr);
|
|
125
|
+
canvas.height = Math.round(height * dpr);
|
|
126
|
+
needsDraw = true;
|
|
127
|
+
}
|
|
128
|
+
function frame(now) {
|
|
129
|
+
rafId = 0;
|
|
130
|
+
if (!canRun()) return;
|
|
131
|
+
const minInterval = frameInterval(busy() ? engine.maxFps : effectiveIdleFps());
|
|
132
|
+
if (lastFrame && now - lastFrame < minInterval * .85) {
|
|
133
|
+
schedule(minInterval - (now - lastFrame));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const dt = lastFrame ? Math.min((now - lastFrame) / 1e3, .1) : 0;
|
|
137
|
+
lastFrame = now;
|
|
138
|
+
const wasBusy = busy();
|
|
139
|
+
const ease = 1 - Math.exp(-dt * 8);
|
|
140
|
+
pointerX += (targetX - pointerX) * ease;
|
|
141
|
+
pointerY += (targetY - pointerY) * ease;
|
|
142
|
+
influence += (targetInfluence - influence) * ease;
|
|
143
|
+
if (Math.abs(targetInfluence - influence) < .001) influence = targetInfluence;
|
|
144
|
+
const moving = animating();
|
|
145
|
+
if (moving || wasBusy || needsDraw) {
|
|
146
|
+
needsDraw = false;
|
|
147
|
+
[frameClicks, clicks] = [clicks, frameClicks];
|
|
148
|
+
clicks.length = 0;
|
|
149
|
+
pointer.x = pointerX;
|
|
150
|
+
pointer.y = pointerY;
|
|
151
|
+
pointer.influence = influence;
|
|
152
|
+
frameState.width = width;
|
|
153
|
+
frameState.height = height;
|
|
154
|
+
frameState.dpr = dpr;
|
|
155
|
+
frameState.dt = dt;
|
|
156
|
+
frameState.reducedMotion = holdStill();
|
|
157
|
+
frameState.clicks = frameClicks;
|
|
158
|
+
styleBusy = surface.render(frameState, backgroundColor, fade);
|
|
159
|
+
}
|
|
160
|
+
if (busy()) schedule();
|
|
161
|
+
else if (moving) schedule(frameInterval(effectiveIdleFps()));
|
|
162
|
+
else lastFrame = 0;
|
|
163
|
+
}
|
|
164
|
+
/** Request a frame `delay` ms from now. */
|
|
165
|
+
function schedule(delay = 0) {
|
|
166
|
+
if (rafId || timerId || !canRun()) return;
|
|
167
|
+
if (delay > 8) timerId = window.setTimeout(onTimer, delay * .8);
|
|
168
|
+
else rafId = requestAnimationFrame(frame);
|
|
169
|
+
}
|
|
170
|
+
function onTimer() {
|
|
171
|
+
timerId = 0;
|
|
172
|
+
schedule();
|
|
173
|
+
}
|
|
174
|
+
/** Render as soon as possible, cutting short any idle sleep. */
|
|
175
|
+
function wake() {
|
|
176
|
+
if (timerId) clearTimeout(timerId);
|
|
177
|
+
timerId = 0;
|
|
178
|
+
schedule();
|
|
179
|
+
}
|
|
180
|
+
function cancel() {
|
|
181
|
+
if (rafId) cancelAnimationFrame(rafId);
|
|
182
|
+
if (timerId) clearTimeout(timerId);
|
|
183
|
+
rafId = timerId = 0;
|
|
184
|
+
}
|
|
185
|
+
function onPointerMove(event) {
|
|
186
|
+
if (!engine.interactive || !canRun()) return;
|
|
187
|
+
const rect = canvas.getBoundingClientRect();
|
|
188
|
+
const x = event.clientX - rect.left;
|
|
189
|
+
const y = event.clientY - rect.top;
|
|
190
|
+
const inside = x >= 0 && y >= 0 && x <= rect.width && y <= rect.height;
|
|
191
|
+
if (inside && targetInfluence === 0) {
|
|
192
|
+
pointerX = x;
|
|
193
|
+
pointerY = y;
|
|
194
|
+
}
|
|
195
|
+
targetX = x;
|
|
196
|
+
targetY = y;
|
|
197
|
+
targetInfluence = inside ? 1 : 0;
|
|
198
|
+
if (inside || influence > 0) wake();
|
|
199
|
+
}
|
|
200
|
+
function onPointerDown(event) {
|
|
201
|
+
if (!engine.interactive || !canRun()) return;
|
|
202
|
+
const rect = canvas.getBoundingClientRect();
|
|
203
|
+
const x = event.clientX - rect.left;
|
|
204
|
+
const y = event.clientY - rect.top;
|
|
205
|
+
if (x < 0 || y < 0 || x > rect.width || y > rect.height) return;
|
|
206
|
+
clicks.push({
|
|
207
|
+
x,
|
|
208
|
+
y
|
|
209
|
+
});
|
|
210
|
+
wake();
|
|
211
|
+
}
|
|
212
|
+
function onPointerLeave() {
|
|
213
|
+
targetInfluence = 0;
|
|
214
|
+
wake();
|
|
215
|
+
}
|
|
216
|
+
function onVisibilityChange() {
|
|
217
|
+
visible = document.visibilityState === "visible" && intersecting;
|
|
218
|
+
lastFrame = 0;
|
|
219
|
+
if (visible) wake();
|
|
220
|
+
else cancel();
|
|
221
|
+
}
|
|
222
|
+
function onReducedMotionChange(event) {
|
|
223
|
+
reducedMotion = event.matches;
|
|
224
|
+
needsDraw = true;
|
|
225
|
+
wake();
|
|
226
|
+
}
|
|
227
|
+
let dprQuery = null;
|
|
228
|
+
function watchDpr() {
|
|
229
|
+
dprQuery?.removeEventListener?.("change", onDprChange);
|
|
230
|
+
dprQuery = window.matchMedia?.(`(resolution: ${window.devicePixelRatio || 1}dppx)`) ?? null;
|
|
231
|
+
dprQuery?.addEventListener?.("change", onDprChange);
|
|
232
|
+
}
|
|
233
|
+
function onDprChange() {
|
|
234
|
+
resize();
|
|
235
|
+
wake();
|
|
236
|
+
watchDpr();
|
|
237
|
+
}
|
|
238
|
+
watchDpr();
|
|
239
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
240
|
+
resize();
|
|
241
|
+
wake();
|
|
242
|
+
});
|
|
243
|
+
resizeObserver.observe(canvas);
|
|
244
|
+
const intersectionObserver = typeof IntersectionObserver === "function" ? new IntersectionObserver((entries) => {
|
|
245
|
+
intersecting = entries[entries.length - 1].isIntersecting;
|
|
246
|
+
onVisibilityChange();
|
|
247
|
+
}) : null;
|
|
248
|
+
intersectionObserver?.observe(canvas);
|
|
249
|
+
window.addEventListener("pointermove", onPointerMove, { passive: true });
|
|
250
|
+
window.addEventListener("pointerdown", onPointerDown, { passive: true });
|
|
251
|
+
document.documentElement.addEventListener("pointerleave", onPointerLeave);
|
|
252
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
253
|
+
reducedMotionQuery?.addEventListener?.("change", onReducedMotionChange);
|
|
254
|
+
resize();
|
|
255
|
+
schedule();
|
|
256
|
+
return {
|
|
257
|
+
canvas,
|
|
258
|
+
style: definition.name,
|
|
259
|
+
renderer: surface.name,
|
|
260
|
+
get options() {
|
|
261
|
+
return options;
|
|
262
|
+
},
|
|
263
|
+
get engineOptions() {
|
|
264
|
+
return engine;
|
|
265
|
+
},
|
|
266
|
+
setOptions(next) {
|
|
267
|
+
options = {
|
|
268
|
+
...options,
|
|
269
|
+
...next
|
|
270
|
+
};
|
|
271
|
+
surface.update(options);
|
|
272
|
+
needsDraw = true;
|
|
273
|
+
wake();
|
|
274
|
+
},
|
|
275
|
+
setEngineOptions(next) {
|
|
276
|
+
const previous = engine;
|
|
277
|
+
engine = {
|
|
278
|
+
...engine,
|
|
279
|
+
...next
|
|
280
|
+
};
|
|
281
|
+
backgroundColor = resolveColor(engine.backgroundColor);
|
|
282
|
+
fade = resolveFade(engine);
|
|
283
|
+
if (engine.maxDpr !== previous.maxDpr) resize();
|
|
284
|
+
if (!engine.interactive) {
|
|
285
|
+
influence = targetInfluence = 0;
|
|
286
|
+
clicks.length = 0;
|
|
287
|
+
}
|
|
288
|
+
needsDraw = true;
|
|
289
|
+
wake();
|
|
290
|
+
},
|
|
291
|
+
pause() {
|
|
292
|
+
paused = true;
|
|
293
|
+
cancel();
|
|
294
|
+
},
|
|
295
|
+
resume() {
|
|
296
|
+
paused = false;
|
|
297
|
+
lastFrame = 0;
|
|
298
|
+
wake();
|
|
299
|
+
},
|
|
300
|
+
destroy() {
|
|
301
|
+
if (destroyed) return;
|
|
302
|
+
destroyed = true;
|
|
303
|
+
cancel();
|
|
304
|
+
resizeObserver.disconnect();
|
|
305
|
+
intersectionObserver?.disconnect();
|
|
306
|
+
window.removeEventListener("pointermove", onPointerMove);
|
|
307
|
+
window.removeEventListener("pointerdown", onPointerDown);
|
|
308
|
+
document.documentElement.removeEventListener("pointerleave", onPointerLeave);
|
|
309
|
+
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
310
|
+
reducedMotionQuery?.removeEventListener?.("change", onReducedMotionChange);
|
|
311
|
+
dprQuery?.removeEventListener?.("change", onDprChange);
|
|
312
|
+
if (!ownsCanvas) surface.clear();
|
|
313
|
+
surface.destroy();
|
|
314
|
+
if (ownsCanvas) {
|
|
315
|
+
if (surface.name === "webgl") canvas.getContext("webgl2")?.getExtension("WEBGL_lose_context")?.loseContext();
|
|
316
|
+
canvas.remove();
|
|
317
|
+
if (host && hostPosition !== null) host.style.position = hostPosition;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
/** Stands in when none of the style's renderers works here: draws nothing, and says so once. */
|
|
323
|
+
function blankSurface(definition) {
|
|
324
|
+
const needs = definition.renderers.map((r) => r.name === "webgl" ? "WebGL2" : "Canvas 2D").join(" or ");
|
|
325
|
+
console.warn(`interactive-bg: style "${definition.name}" needs ${needs}, which this browser doesn't provide`);
|
|
326
|
+
return {
|
|
327
|
+
name: null,
|
|
328
|
+
update() {},
|
|
329
|
+
render: () => false,
|
|
330
|
+
clear() {},
|
|
331
|
+
destroy() {}
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function resolveFade(engine) {
|
|
335
|
+
return {
|
|
336
|
+
top: Math.max(0, engine.fadeOutTop),
|
|
337
|
+
right: Math.max(0, engine.fadeOutRight),
|
|
338
|
+
bottom: Math.max(0, engine.fadeOutBottom),
|
|
339
|
+
left: Math.max(0, engine.fadeOutLeft)
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
function resolveColor(color) {
|
|
343
|
+
return color && color !== "transparent" ? color : null;
|
|
344
|
+
}
|
|
345
|
+
/** Create the background canvas inside `host`, or fixed behind the page when there is no host. */
|
|
346
|
+
function mountCanvas(host) {
|
|
347
|
+
const canvas = document.createElement("canvas");
|
|
348
|
+
canvas.setAttribute("aria-hidden", "true");
|
|
349
|
+
Object.assign(canvas.style, {
|
|
350
|
+
position: host ? "absolute" : "fixed",
|
|
351
|
+
inset: "0",
|
|
352
|
+
width: "100%",
|
|
353
|
+
height: "100%",
|
|
354
|
+
display: "block",
|
|
355
|
+
pointerEvents: "none",
|
|
356
|
+
zIndex: host ? "0" : "-1"
|
|
357
|
+
});
|
|
358
|
+
if (host) {
|
|
359
|
+
if (getComputedStyle(host).position === "static") host.style.position = "relative";
|
|
360
|
+
host.prepend(canvas);
|
|
361
|
+
} else document.body.prepend(canvas);
|
|
362
|
+
return canvas;
|
|
363
|
+
}
|
|
364
|
+
exports.createBackground = createBackground;
|
|
365
|
+
exports.engineDefaults = engineDefaults;
|
|
366
|
+
|
|
367
|
+
//# sourceMappingURL=engine.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.cjs","names":["createSurface"],"sources":["../../src/core/engine.ts"],"sourcesContent":["import type { Frame, Point, RendererName, Style, StyleDefinition } from './style';\nimport { createSurface, type Fade, type Surface } from './surface';\n\n/** Options shared by every style: how and when the background is rendered. */\nexport interface EngineOptions {\n /**\n * Rendering backend, chosen when the background is created (changing it later has no effect).\n * `'auto'` uses the style's renderers in their own order (WebGL2 first for the built-in styles)\n * and falls back to the next when the browser lacks one. Naming one tries it first.\n */\n renderer: 'auto' | RendererName;\n /** Fill colour painted behind the style. `null` or `'transparent'` keeps the canvas transparent. */\n backgroundColor: string | null;\n /**\n * Fade the pattern out towards the top edge over this many CSS pixels: from its own opacity\n * this far in from the edge to fully transparent at the edge. The background colour isn't\n * faded. `0` = no fade. Fades on several sides multiply where they overlap.\n */\n fadeOutTop: number;\n /** Like `fadeOutTop`, towards the right edge. */\n fadeOutRight: number;\n /** Like `fadeOutTop`, towards the bottom edge. */\n fadeOutBottom: number;\n /** Like `fadeOutTop`, towards the left edge. */\n fadeOutLeft: number;\n /** React to the pointer and to clicks / taps. */\n interactive: boolean;\n /**\n * Upper limit for the canvas pixel density. Canvas memory grows with its square:\n * a 1920×1080 canvas takes 8 MB at 1, 18 MB at 1.5 and 32 MB at 2.\n */\n maxDpr: number;\n /** Frame-rate cap while the pointer is moving the background. `0` = uncapped. */\n maxFps: number;\n /** Frame-rate cap while only the style's own animation runs. Lower saves CPU and battery. `0` = same as `maxFps`. */\n idleFps: number;\n /** Hold the style's own animation still when the user prefers reduced motion. */\n respectReducedMotion: boolean;\n}\n\nexport const engineDefaults: Readonly<EngineOptions> = Object.freeze({\n renderer: 'auto',\n backgroundColor: null,\n fadeOutTop: 0,\n fadeOutRight: 0,\n fadeOutBottom: 0,\n fadeOutLeft: 0,\n interactive: true,\n maxDpr: 1.5,\n maxFps: 60,\n idleFps: 30,\n respectReducedMotion: true,\n});\n\nexport interface Background<O extends object> {\n readonly canvas: HTMLCanvasElement;\n /** Name of the style, e.g. `'topo'`. */\n readonly style: string;\n /**\n * The rendering backend actually in use, or `null` when none of the style's renderers works in\n * this browser (e.g. a WebGL-only style without WebGL2): the background then stays blank.\n */\n readonly renderer: RendererName | null;\n readonly options: Readonly<O>;\n readonly engineOptions: Readonly<EngineOptions>;\n /** Update any of the style's options on the fly. */\n setOptions(options: Partial<O>): void;\n /** Update any engine options on the fly (except `renderer`). */\n setEngineOptions(options: Partial<EngineOptions>): void;\n pause(): void;\n resume(): void;\n /** Stop rendering, remove listeners and (if it was created by us) the canvas. */\n destroy(): void;\n}\n\n/**\n * Mount an interactive background.\n *\n * - `target` `null`/omitted: a fixed, full-viewport canvas is placed behind the page.\n * - `target` is a `<canvas>`: it is drawn into directly and sized by your CSS.\n * - `target` is any other element (or a selector): a canvas is appended that\n * fills it. The element gets `position: relative` if it was `static`.\n *\n * @example createBackground('#hero', topo({ levels: 14 }), { maxFps: 30 })\n */\nexport function createBackground<O extends object>(\n target: HTMLElement | string | null | undefined,\n style: Style<O>,\n engineOptions: Partial<EngineOptions> = {},\n): Background<O> {\n const host = typeof target === 'string' ? document.querySelector<HTMLElement>(target) : target;\n if (typeof target === 'string' && !host) {\n throw new Error(`interactive-bg: no element matches \"${target}\"`);\n }\n\n const definition = style.definition;\n let options: O = { ...style.options };\n let engine: EngineOptions = { ...engineDefaults, ...engineOptions };\n let backgroundColor = resolveColor(engine.backgroundColor);\n let fade = resolveFade(engine);\n\n const ownsCanvas = !(host instanceof HTMLCanvasElement);\n // We may switch the host to `position: relative`; remember what to restore on destroy().\n const hostPosition = ownsCanvas && host ? host.style.position : null;\n let canvas = ownsCanvas ? mountCanvas(host ?? null) : (host as HTMLCanvasElement);\n const surface =\n createSurface(\n () => canvas,\n definition.renderers,\n options,\n engine.renderer,\n () => {\n needsDraw = true;\n wake();\n },\n // A renderer that failed may have claimed the canvas's context; the next one needs a fresh canvas.\n ownsCanvas\n ? () => {\n const fresh = mountCanvas(host ?? null);\n canvas.remove();\n canvas = fresh;\n }\n : undefined,\n ) ?? blankSurface(definition);\n\n // Canvas size in CSS pixels, and the pixel density it is rendered at.\n let width = 0;\n let height = 0;\n let dpr = 1;\n\n // Pointer state: `target*` is where the pointer is, the rest eases toward it.\n let pointerX = 0;\n let pointerY = 0;\n let targetX = 0;\n let targetY = 0;\n let influence = 0;\n let targetInfluence = 0;\n /** Clicks since the last rendered frame, and the array handed to the style for the current one (swapped each frame). */\n let clicks: Point[] = [];\n let frameClicks: Point[] = [];\n // Reused for every frame: styles only read them during render().\n const pointer = { x: 0, y: 0, influence: 0 };\n const frameState: Frame = { width: 0, height: 0, dpr: 1, dt: 0, reducedMotion: false, pointer, clicks: frameClicks };\n /** The style reported a short-lived effect still playing in its last frame. */\n let styleBusy = false;\n\n let lastFrame = 0;\n let rafId = 0;\n let timerId = 0;\n let paused = false;\n let visible = true;\n let intersecting = true;\n let destroyed = false;\n let needsDraw = true;\n\n const reducedMotionQuery = window.matchMedia?.('(prefers-reduced-motion: reduce)');\n let reducedMotion = reducedMotionQuery?.matches ?? false;\n\n function holdStill(): boolean {\n return engine.respectReducedMotion && reducedMotion;\n }\n\n /** Is the style's own ambient animation running (as opposed to only reacting to the pointer)? */\n function animating(): boolean {\n return surface.name !== null && definition.animates(options) && !holdStill();\n }\n\n /** Does something need the full frame rate: the pointer's effect easing, or a style effect playing? */\n function busy(): boolean {\n return styleBusy || clicks.length > 0 || pointerEasing();\n }\n\n /** Frame rate for the ambient animation; `0` means uncapped for the engine options. */\n function effectiveIdleFps(): number {\n const configured =\n engine.maxFps <= 0\n ? engine.idleFps\n : engine.idleFps <= 0\n ? engine.maxFps\n : Math.min(engine.maxFps, engine.idleFps);\n const needed = definition.idleFps?.(options) ?? 0;\n if (needed <= 0) return configured;\n return configured <= 0 ? needed : Math.min(configured, needed);\n }\n\n function frameInterval(fps: number): number {\n return fps > 0 ? 1000 / fps : 0;\n }\n\n function canRun(): boolean {\n return !destroyed && !paused && visible;\n }\n\n /** Is the pointer's effect still catching up with the pointer? */\n function pointerEasing(): boolean {\n if (influence !== targetInfluence) return true;\n return influence > 0 && (Math.abs(targetX - pointerX) > 0.5 || Math.abs(targetY - pointerY) > 0.5);\n }\n\n function resize(): void {\n const rect = canvas.getBoundingClientRect();\n const nextWidth = Math.max(1, Math.round(rect.width));\n const nextHeight = Math.max(1, Math.round(rect.height));\n const nextDpr = Math.min(window.devicePixelRatio || 1, Math.max(0.5, engine.maxDpr));\n if (nextWidth === width && nextHeight === height && nextDpr === dpr) return;\n width = nextWidth;\n height = nextHeight;\n dpr = nextDpr;\n canvas.width = Math.round(width * dpr);\n canvas.height = Math.round(height * dpr);\n needsDraw = true;\n }\n\n function frame(now: number): void {\n rafId = 0;\n if (!canRun()) return;\n\n // Full frame rate only while reacting to the pointer or playing an effect.\n const minInterval = frameInterval(busy() ? engine.maxFps : effectiveIdleFps());\n // 15% tolerance so a 60 fps cap doesn't drop frames on 60 Hz displays.\n if (lastFrame && now - lastFrame < minInterval * 0.85) {\n schedule(minInterval - (now - lastFrame));\n return;\n }\n const dt = lastFrame ? Math.min((now - lastFrame) / 1000, 0.1) : 0;\n lastFrame = now;\n\n const wasBusy = busy();\n // Frame-rate independent easing.\n const ease = 1 - Math.exp(-dt * 8);\n pointerX += (targetX - pointerX) * ease;\n pointerY += (targetY - pointerY) * ease;\n influence += (targetInfluence - influence) * ease;\n if (Math.abs(targetInfluence - influence) < 0.001) influence = targetInfluence;\n\n const moving = animating();\n if (moving || wasBusy || needsDraw) {\n needsDraw = false;\n [frameClicks, clicks] = [clicks, frameClicks];\n clicks.length = 0;\n pointer.x = pointerX;\n pointer.y = pointerY;\n pointer.influence = influence;\n frameState.width = width;\n frameState.height = height;\n frameState.dpr = dpr;\n frameState.dt = dt;\n frameState.reducedMotion = holdStill();\n frameState.clicks = frameClicks;\n styleBusy = surface.render(frameState, backgroundColor, fade);\n }\n\n // Nothing left to animate: stop until a pointer move, click, resize or option change wakes us.\n if (busy()) schedule();\n else if (moving) schedule(frameInterval(effectiveIdleFps()));\n else lastFrame = 0;\n }\n\n /** Request a frame `delay` ms from now. */\n function schedule(delay = 0): void {\n if (rafId || timerId || !canRun()) return;\n // requestAnimationFrame alone wakes up on every display refresh (up to 120–165 Hz),\n // so sleep through longer gaps with a timer and only then wait for the next frame.\n // The timer wakes slightly early so the next frame lands within frame()'s tolerance.\n if (delay > 8) {\n timerId = window.setTimeout(onTimer, delay * 0.8);\n } else {\n rafId = requestAnimationFrame(frame);\n }\n }\n\n function onTimer(): void {\n timerId = 0;\n schedule();\n }\n\n /** Render as soon as possible, cutting short any idle sleep. */\n function wake(): void {\n if (timerId) clearTimeout(timerId);\n timerId = 0;\n schedule();\n }\n\n function cancel(): void {\n if (rafId) cancelAnimationFrame(rafId);\n if (timerId) clearTimeout(timerId);\n rafId = timerId = 0;\n }\n\n function onPointerMove(event: PointerEvent): void {\n if (!engine.interactive || !canRun()) return;\n const rect = canvas.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n const inside = x >= 0 && y >= 0 && x <= rect.width && y <= rect.height;\n if (inside && targetInfluence === 0) {\n // Entering: start where the pointer is instead of sliding in.\n pointerX = x;\n pointerY = y;\n }\n targetX = x;\n targetY = y;\n targetInfluence = inside ? 1 : 0;\n if (inside || influence > 0) wake();\n }\n\n function onPointerDown(event: PointerEvent): void {\n if (!engine.interactive || !canRun()) return;\n const rect = canvas.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n if (x < 0 || y < 0 || x > rect.width || y > rect.height) return;\n clicks.push({ x, y });\n wake();\n }\n\n function onPointerLeave(): void {\n targetInfluence = 0;\n wake();\n }\n\n function onVisibilityChange(): void {\n visible = document.visibilityState === 'visible' && intersecting;\n lastFrame = 0;\n if (visible) wake();\n else cancel();\n }\n\n function onReducedMotionChange(event: MediaQueryListEvent): void {\n reducedMotion = event.matches;\n needsDraw = true;\n wake();\n }\n\n // ResizeObserver doesn't fire when only the pixel density changes (e.g. the window moves to\n // another screen), so watch the current density with a media query, re-armed after each change.\n let dprQuery: MediaQueryList | null = null;\n function watchDpr(): void {\n dprQuery?.removeEventListener?.('change', onDprChange);\n dprQuery = window.matchMedia?.(`(resolution: ${window.devicePixelRatio || 1}dppx)`) ?? null;\n dprQuery?.addEventListener?.('change', onDprChange);\n }\n function onDprChange(): void {\n resize();\n wake();\n watchDpr();\n }\n watchDpr();\n\n const resizeObserver = new ResizeObserver(() => {\n resize();\n wake();\n });\n resizeObserver.observe(canvas);\n\n const intersectionObserver =\n typeof IntersectionObserver === 'function'\n ? new IntersectionObserver((entries) => {\n intersecting = entries[entries.length - 1].isIntersecting;\n onVisibilityChange();\n })\n : null;\n intersectionObserver?.observe(canvas);\n\n window.addEventListener('pointermove', onPointerMove, { passive: true });\n window.addEventListener('pointerdown', onPointerDown, { passive: true });\n document.documentElement.addEventListener('pointerleave', onPointerLeave);\n document.addEventListener('visibilitychange', onVisibilityChange);\n reducedMotionQuery?.addEventListener?.('change', onReducedMotionChange);\n\n resize();\n schedule();\n\n return {\n canvas,\n style: definition.name,\n renderer: surface.name,\n get options() {\n return options;\n },\n get engineOptions() {\n return engine;\n },\n setOptions(next) {\n options = { ...options, ...next };\n surface.update(options);\n needsDraw = true;\n wake();\n },\n setEngineOptions(next) {\n const previous = engine;\n engine = { ...engine, ...next };\n backgroundColor = resolveColor(engine.backgroundColor);\n fade = resolveFade(engine);\n if (engine.maxDpr !== previous.maxDpr) resize();\n if (!engine.interactive) {\n influence = targetInfluence = 0;\n clicks.length = 0;\n }\n needsDraw = true;\n wake();\n },\n pause() {\n paused = true;\n cancel();\n },\n resume() {\n paused = false;\n lastFrame = 0;\n wake();\n },\n destroy() {\n if (destroyed) return;\n destroyed = true;\n cancel();\n resizeObserver.disconnect();\n intersectionObserver?.disconnect();\n window.removeEventListener('pointermove', onPointerMove);\n window.removeEventListener('pointerdown', onPointerDown);\n document.documentElement.removeEventListener('pointerleave', onPointerLeave);\n document.removeEventListener('visibilitychange', onVisibilityChange);\n reducedMotionQuery?.removeEventListener?.('change', onReducedMotionChange);\n dprQuery?.removeEventListener?.('change', onDprChange);\n if (!ownsCanvas) surface.clear();\n surface.destroy();\n if (ownsCanvas) {\n // Free the GPU context now rather than at garbage collection: browsers cap live WebGL contexts.\n if (surface.name === 'webgl') canvas.getContext('webgl2')?.getExtension('WEBGL_lose_context')?.loseContext();\n canvas.remove();\n if (host && hostPosition !== null) host.style.position = hostPosition;\n }\n },\n };\n}\n\n/** Stands in when none of the style's renderers works here: draws nothing, and says so once. */\nfunction blankSurface<O extends object>(definition: StyleDefinition<O>): Omit<Surface<O>, 'name'> & { name: null } {\n const needs = definition.renderers.map((r) => (r.name === 'webgl' ? 'WebGL2' : 'Canvas 2D')).join(' or ');\n console.warn(`interactive-bg: style \"${definition.name}\" needs ${needs}, which this browser doesn't provide`);\n return { name: null, update() {}, render: () => false, clear() {}, destroy() {} };\n}\n\nfunction resolveFade(engine: EngineOptions): Fade {\n return {\n top: Math.max(0, engine.fadeOutTop),\n right: Math.max(0, engine.fadeOutRight),\n bottom: Math.max(0, engine.fadeOutBottom),\n left: Math.max(0, engine.fadeOutLeft),\n };\n}\n\nfunction resolveColor(color: string | null): string | null {\n return color && color !== 'transparent' ? color : null;\n}\n\n/** Create the background canvas inside `host`, or fixed behind the page when there is no host. */\nfunction mountCanvas(host: HTMLElement | null): HTMLCanvasElement {\n const canvas = document.createElement('canvas');\n canvas.setAttribute('aria-hidden', 'true');\n Object.assign(canvas.style, {\n position: host ? 'absolute' : 'fixed',\n inset: '0',\n width: '100%',\n height: '100%',\n display: 'block',\n pointerEvents: 'none',\n zIndex: host ? '0' : '-1',\n });\n if (host) {\n if (getComputedStyle(host).position === 'static') host.style.position = 'relative';\n host.prepend(canvas);\n } else {\n document.body.prepend(canvas);\n }\n return canvas;\n}\n"],"mappings":";;AAwCA,IAAa,iBAA0C,OAAO,OAAO;CACnE,UAAU;CACV,iBAAiB;CACjB,YAAY;CACZ,cAAc;CACd,eAAe;CACf,aAAa;CACb,aAAa;CACb,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,sBAAsB;AACxB,CAAC;;;;;;;;;;;AAiCD,SAAgB,iBACd,QACA,OACA,gBAAwC,CAAC,GAC1B;CACf,MAAM,OAAO,OAAO,WAAW,WAAW,SAAS,cAA2B,MAAM,IAAI;CACxF,IAAI,OAAO,WAAW,YAAY,CAAC,MACjC,MAAM,IAAI,MAAM,uCAAuC,OAAO,EAAE;CAGlE,MAAM,aAAa,MAAM;CACzB,IAAI,UAAa,EAAE,GAAG,MAAM,QAAQ;CACpC,IAAI,SAAwB;EAAE,GAAG;EAAgB,GAAG;CAAc;CAClE,IAAI,kBAAkB,aAAa,OAAO,eAAe;CACzD,IAAI,OAAO,YAAY,MAAM;CAE7B,MAAM,aAAa,EAAE,gBAAgB;CAErC,MAAM,eAAe,cAAc,OAAO,KAAK,MAAM,WAAW;CAChE,IAAI,SAAS,aAAa,YAAY,QAAQ,IAAI,IAAK;CACvD,MAAM,UACJA,gBAAAA,oBACQ,QACN,WAAW,WACX,SACA,OAAO,gBACD;EACJ,YAAY;EACZ,KAAK;CACP,GAEA,mBACU;EACJ,MAAM,QAAQ,YAAY,QAAQ,IAAI;EACtC,OAAO,OAAO;EACd,SAAS;CACX,IACA,KAAA,CACN,KAAK,aAAa,UAAU;CAG9B,IAAI,QAAQ;CACZ,IAAI,SAAS;CACb,IAAI,MAAM;CAGV,IAAI,WAAW;CACf,IAAI,WAAW;CACf,IAAI,UAAU;CACd,IAAI,UAAU;CACd,IAAI,YAAY;CAChB,IAAI,kBAAkB;;CAEtB,IAAI,SAAkB,CAAC;CACvB,IAAI,cAAuB,CAAC;CAE5B,MAAM,UAAU;EAAE,GAAG;EAAG,GAAG;EAAG,WAAW;CAAE;CAC3C,MAAM,aAAoB;EAAE,OAAO;EAAG,QAAQ;EAAG,KAAK;EAAG,IAAI;EAAG,eAAe;EAAO;EAAS,QAAQ;CAAY;;CAEnH,IAAI,YAAY;CAEhB,IAAI,YAAY;CAChB,IAAI,QAAQ;CACZ,IAAI,UAAU;CACd,IAAI,SAAS;CACb,IAAI,UAAU;CACd,IAAI,eAAe;CACnB,IAAI,YAAY;CAChB,IAAI,YAAY;CAEhB,MAAM,qBAAqB,OAAO,aAAa,kCAAkC;CACjF,IAAI,gBAAgB,oBAAoB,WAAW;CAEnD,SAAS,YAAqB;EAC5B,OAAO,OAAO,wBAAwB;CACxC;;CAGA,SAAS,YAAqB;EAC5B,OAAO,QAAQ,SAAS,QAAQ,WAAW,SAAS,OAAO,KAAK,CAAC,UAAU;CAC7E;;CAGA,SAAS,OAAgB;EACvB,OAAO,aAAa,OAAO,SAAS,KAAK,cAAc;CACzD;;CAGA,SAAS,mBAA2B;EAClC,MAAM,aACJ,OAAO,UAAU,IACb,OAAO,UACP,OAAO,WAAW,IAChB,OAAO,SACP,KAAK,IAAI,OAAO,QAAQ,OAAO,OAAO;EAC9C,MAAM,SAAS,WAAW,UAAU,OAAO,KAAK;EAChD,IAAI,UAAU,GAAG,OAAO;EACxB,OAAO,cAAc,IAAI,SAAS,KAAK,IAAI,YAAY,MAAM;CAC/D;CAEA,SAAS,cAAc,KAAqB;EAC1C,OAAO,MAAM,IAAI,MAAO,MAAM;CAChC;CAEA,SAAS,SAAkB;EACzB,OAAO,CAAC,aAAa,CAAC,UAAU;CAClC;;CAGA,SAAS,gBAAyB;EAChC,IAAI,cAAc,iBAAiB,OAAO;EAC1C,OAAO,YAAY,MAAM,KAAK,IAAI,UAAU,QAAQ,IAAI,MAAO,KAAK,IAAI,UAAU,QAAQ,IAAI;CAChG;CAEA,SAAS,SAAe;EACtB,MAAM,OAAO,OAAO,sBAAsB;EAC1C,MAAM,YAAY,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,KAAK,CAAC;EACpD,MAAM,aAAa,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,MAAM,CAAC;EACtD,MAAM,UAAU,KAAK,IAAI,OAAO,oBAAoB,GAAG,KAAK,IAAI,IAAK,OAAO,MAAM,CAAC;EACnF,IAAI,cAAc,SAAS,eAAe,UAAU,YAAY,KAAK;EACrE,QAAQ;EACR,SAAS;EACT,MAAM;EACN,OAAO,QAAQ,KAAK,MAAM,QAAQ,GAAG;EACrC,OAAO,SAAS,KAAK,MAAM,SAAS,GAAG;EACvC,YAAY;CACd;CAEA,SAAS,MAAM,KAAmB;EAChC,QAAQ;EACR,IAAI,CAAC,OAAO,GAAG;EAGf,MAAM,cAAc,cAAc,KAAK,IAAI,OAAO,SAAS,iBAAiB,CAAC;EAE7E,IAAI,aAAa,MAAM,YAAY,cAAc,KAAM;GACrD,SAAS,eAAe,MAAM,UAAU;GACxC;EACF;EACA,MAAM,KAAK,YAAY,KAAK,KAAK,MAAM,aAAa,KAAM,EAAG,IAAI;EACjE,YAAY;EAEZ,MAAM,UAAU,KAAK;EAErB,MAAM,OAAO,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC;EACjC,aAAa,UAAU,YAAY;EACnC,aAAa,UAAU,YAAY;EACnC,cAAc,kBAAkB,aAAa;EAC7C,IAAI,KAAK,IAAI,kBAAkB,SAAS,IAAI,MAAO,YAAY;EAE/D,MAAM,SAAS,UAAU;EACzB,IAAI,UAAU,WAAW,WAAW;GAClC,YAAY;GACZ,CAAC,aAAa,UAAU,CAAC,QAAQ,WAAW;GAC5C,OAAO,SAAS;GAChB,QAAQ,IAAI;GACZ,QAAQ,IAAI;GACZ,QAAQ,YAAY;GACpB,WAAW,QAAQ;GACnB,WAAW,SAAS;GACpB,WAAW,MAAM;GACjB,WAAW,KAAK;GAChB,WAAW,gBAAgB,UAAU;GACrC,WAAW,SAAS;GACpB,YAAY,QAAQ,OAAO,YAAY,iBAAiB,IAAI;EAC9D;EAGA,IAAI,KAAK,GAAG,SAAS;OAChB,IAAI,QAAQ,SAAS,cAAc,iBAAiB,CAAC,CAAC;OACtD,YAAY;CACnB;;CAGA,SAAS,SAAS,QAAQ,GAAS;EACjC,IAAI,SAAS,WAAW,CAAC,OAAO,GAAG;EAInC,IAAI,QAAQ,GACV,UAAU,OAAO,WAAW,SAAS,QAAQ,EAAG;OAEhD,QAAQ,sBAAsB,KAAK;CAEvC;CAEA,SAAS,UAAgB;EACvB,UAAU;EACV,SAAS;CACX;;CAGA,SAAS,OAAa;EACpB,IAAI,SAAS,aAAa,OAAO;EACjC,UAAU;EACV,SAAS;CACX;CAEA,SAAS,SAAe;EACtB,IAAI,OAAO,qBAAqB,KAAK;EACrC,IAAI,SAAS,aAAa,OAAO;EACjC,QAAQ,UAAU;CACpB;CAEA,SAAS,cAAc,OAA2B;EAChD,IAAI,CAAC,OAAO,eAAe,CAAC,OAAO,GAAG;EACtC,MAAM,OAAO,OAAO,sBAAsB;EAC1C,MAAM,IAAI,MAAM,UAAU,KAAK;EAC/B,MAAM,IAAI,MAAM,UAAU,KAAK;EAC/B,MAAM,SAAS,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,SAAS,KAAK,KAAK;EAChE,IAAI,UAAU,oBAAoB,GAAG;GAEnC,WAAW;GACX,WAAW;EACb;EACA,UAAU;EACV,UAAU;EACV,kBAAkB,SAAS,IAAI;EAC/B,IAAI,UAAU,YAAY,GAAG,KAAK;CACpC;CAEA,SAAS,cAAc,OAA2B;EAChD,IAAI,CAAC,OAAO,eAAe,CAAC,OAAO,GAAG;EACtC,MAAM,OAAO,OAAO,sBAAsB;EAC1C,MAAM,IAAI,MAAM,UAAU,KAAK;EAC/B,MAAM,IAAI,MAAM,UAAU,KAAK;EAC/B,IAAI,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,SAAS,IAAI,KAAK,QAAQ;EACzD,OAAO,KAAK;GAAE;GAAG;EAAE,CAAC;EACpB,KAAK;CACP;CAEA,SAAS,iBAAuB;EAC9B,kBAAkB;EAClB,KAAK;CACP;CAEA,SAAS,qBAA2B;EAClC,UAAU,SAAS,oBAAoB,aAAa;EACpD,YAAY;EACZ,IAAI,SAAS,KAAK;OACb,OAAO;CACd;CAEA,SAAS,sBAAsB,OAAkC;EAC/D,gBAAgB,MAAM;EACtB,YAAY;EACZ,KAAK;CACP;CAIA,IAAI,WAAkC;CACtC,SAAS,WAAiB;EACxB,UAAU,sBAAsB,UAAU,WAAW;EACrD,WAAW,OAAO,aAAa,gBAAgB,OAAO,oBAAoB,EAAE,MAAM,KAAK;EACvF,UAAU,mBAAmB,UAAU,WAAW;CACpD;CACA,SAAS,cAAoB;EAC3B,OAAO;EACP,KAAK;EACL,SAAS;CACX;CACA,SAAS;CAET,MAAM,iBAAiB,IAAI,qBAAqB;EAC9C,OAAO;EACP,KAAK;CACP,CAAC;CACD,eAAe,QAAQ,MAAM;CAE7B,MAAM,uBACJ,OAAO,yBAAyB,aAC5B,IAAI,sBAAsB,YAAY;EACpC,eAAe,QAAQ,QAAQ,SAAS,EAAE,CAAC;EAC3C,mBAAmB;CACrB,CAAC,IACD;CACN,sBAAsB,QAAQ,MAAM;CAEpC,OAAO,iBAAiB,eAAe,eAAe,EAAE,SAAS,KAAK,CAAC;CACvE,OAAO,iBAAiB,eAAe,eAAe,EAAE,SAAS,KAAK,CAAC;CACvE,SAAS,gBAAgB,iBAAiB,gBAAgB,cAAc;CACxE,SAAS,iBAAiB,oBAAoB,kBAAkB;CAChE,oBAAoB,mBAAmB,UAAU,qBAAqB;CAEtE,OAAO;CACP,SAAS;CAET,OAAO;EACL;EACA,OAAO,WAAW;EAClB,UAAU,QAAQ;EAClB,IAAI,UAAU;GACZ,OAAO;EACT;EACA,IAAI,gBAAgB;GAClB,OAAO;EACT;EACA,WAAW,MAAM;GACf,UAAU;IAAE,GAAG;IAAS,GAAG;GAAK;GAChC,QAAQ,OAAO,OAAO;GACtB,YAAY;GACZ,KAAK;EACP;EACA,iBAAiB,MAAM;GACrB,MAAM,WAAW;GACjB,SAAS;IAAE,GAAG;IAAQ,GAAG;GAAK;GAC9B,kBAAkB,aAAa,OAAO,eAAe;GACrD,OAAO,YAAY,MAAM;GACzB,IAAI,OAAO,WAAW,SAAS,QAAQ,OAAO;GAC9C,IAAI,CAAC,OAAO,aAAa;IACvB,YAAY,kBAAkB;IAC9B,OAAO,SAAS;GAClB;GACA,YAAY;GACZ,KAAK;EACP;EACA,QAAQ;GACN,SAAS;GACT,OAAO;EACT;EACA,SAAS;GACP,SAAS;GACT,YAAY;GACZ,KAAK;EACP;EACA,UAAU;GACR,IAAI,WAAW;GACf,YAAY;GACZ,OAAO;GACP,eAAe,WAAW;GAC1B,sBAAsB,WAAW;GACjC,OAAO,oBAAoB,eAAe,aAAa;GACvD,OAAO,oBAAoB,eAAe,aAAa;GACvD,SAAS,gBAAgB,oBAAoB,gBAAgB,cAAc;GAC3E,SAAS,oBAAoB,oBAAoB,kBAAkB;GACnE,oBAAoB,sBAAsB,UAAU,qBAAqB;GACzE,UAAU,sBAAsB,UAAU,WAAW;GACrD,IAAI,CAAC,YAAY,QAAQ,MAAM;GAC/B,QAAQ,QAAQ;GAChB,IAAI,YAAY;IAEd,IAAI,QAAQ,SAAS,SAAS,OAAO,WAAW,QAAQ,CAAC,EAAE,aAAa,oBAAoB,CAAC,EAAE,YAAY;IAC3G,OAAO,OAAO;IACd,IAAI,QAAQ,iBAAiB,MAAM,KAAK,MAAM,WAAW;GAC3D;EACF;CACF;AACF;;AAGA,SAAS,aAA+B,YAA2E;CACjH,MAAM,QAAQ,WAAW,UAAU,KAAK,MAAO,EAAE,SAAS,UAAU,WAAW,WAAY,CAAC,CAAC,KAAK,MAAM;CACxG,QAAQ,KAAK,0BAA0B,WAAW,KAAK,UAAU,MAAM,qCAAqC;CAC5G,OAAO;EAAE,MAAM;EAAM,SAAS,CAAC;EAAG,cAAc;EAAO,QAAQ,CAAC;EAAG,UAAU,CAAC;CAAE;AAClF;AAEA,SAAS,YAAY,QAA6B;CAChD,OAAO;EACL,KAAK,KAAK,IAAI,GAAG,OAAO,UAAU;EAClC,OAAO,KAAK,IAAI,GAAG,OAAO,YAAY;EACtC,QAAQ,KAAK,IAAI,GAAG,OAAO,aAAa;EACxC,MAAM,KAAK,IAAI,GAAG,OAAO,WAAW;CACtC;AACF;AAEA,SAAS,aAAa,OAAqC;CACzD,OAAO,SAAS,UAAU,gBAAgB,QAAQ;AACpD;;AAGA,SAAS,YAAY,MAA6C;CAChE,MAAM,SAAS,SAAS,cAAc,QAAQ;CAC9C,OAAO,aAAa,eAAe,MAAM;CACzC,OAAO,OAAO,OAAO,OAAO;EAC1B,UAAU,OAAO,aAAa;EAC9B,OAAO;EACP,OAAO;EACP,QAAQ;EACR,SAAS;EACT,eAAe;EACf,QAAQ,OAAO,MAAM;CACvB,CAAC;CACD,IAAI,MAAM;EACR,IAAI,iBAAiB,IAAI,CAAC,CAAC,aAAa,UAAU,KAAK,MAAM,WAAW;EACxE,KAAK,QAAQ,MAAM;CACrB,OACE,SAAS,KAAK,QAAQ,MAAM;CAE9B,OAAO;AACT"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { RendererName, Style } from './style.cjs';
|
|
2
|
+
/** Options shared by every style: how and when the background is rendered. */
|
|
3
|
+
export interface EngineOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Rendering backend, chosen when the background is created (changing it later has no effect).
|
|
6
|
+
* `'auto'` uses the style's renderers in their own order (WebGL2 first for the built-in styles)
|
|
7
|
+
* and falls back to the next when the browser lacks one. Naming one tries it first.
|
|
8
|
+
*/
|
|
9
|
+
renderer: 'auto' | RendererName;
|
|
10
|
+
/** Fill colour painted behind the style. `null` or `'transparent'` keeps the canvas transparent. */
|
|
11
|
+
backgroundColor: string | null;
|
|
12
|
+
/**
|
|
13
|
+
* Fade the pattern out towards the top edge over this many CSS pixels: from its own opacity
|
|
14
|
+
* this far in from the edge to fully transparent at the edge. The background colour isn't
|
|
15
|
+
* faded. `0` = no fade. Fades on several sides multiply where they overlap.
|
|
16
|
+
*/
|
|
17
|
+
fadeOutTop: number;
|
|
18
|
+
/** Like `fadeOutTop`, towards the right edge. */
|
|
19
|
+
fadeOutRight: number;
|
|
20
|
+
/** Like `fadeOutTop`, towards the bottom edge. */
|
|
21
|
+
fadeOutBottom: number;
|
|
22
|
+
/** Like `fadeOutTop`, towards the left edge. */
|
|
23
|
+
fadeOutLeft: number;
|
|
24
|
+
/** React to the pointer and to clicks / taps. */
|
|
25
|
+
interactive: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Upper limit for the canvas pixel density. Canvas memory grows with its square:
|
|
28
|
+
* a 1920×1080 canvas takes 8 MB at 1, 18 MB at 1.5 and 32 MB at 2.
|
|
29
|
+
*/
|
|
30
|
+
maxDpr: number;
|
|
31
|
+
/** Frame-rate cap while the pointer is moving the background. `0` = uncapped. */
|
|
32
|
+
maxFps: number;
|
|
33
|
+
/** Frame-rate cap while only the style's own animation runs. Lower saves CPU and battery. `0` = same as `maxFps`. */
|
|
34
|
+
idleFps: number;
|
|
35
|
+
/** Hold the style's own animation still when the user prefers reduced motion. */
|
|
36
|
+
respectReducedMotion: boolean;
|
|
37
|
+
}
|
|
38
|
+
export declare const engineDefaults: Readonly<EngineOptions>;
|
|
39
|
+
export interface Background<O extends object> {
|
|
40
|
+
readonly canvas: HTMLCanvasElement;
|
|
41
|
+
/** Name of the style, e.g. `'topo'`. */
|
|
42
|
+
readonly style: string;
|
|
43
|
+
/**
|
|
44
|
+
* The rendering backend actually in use, or `null` when none of the style's renderers works in
|
|
45
|
+
* this browser (e.g. a WebGL-only style without WebGL2): the background then stays blank.
|
|
46
|
+
*/
|
|
47
|
+
readonly renderer: RendererName | null;
|
|
48
|
+
readonly options: Readonly<O>;
|
|
49
|
+
readonly engineOptions: Readonly<EngineOptions>;
|
|
50
|
+
/** Update any of the style's options on the fly. */
|
|
51
|
+
setOptions(options: Partial<O>): void;
|
|
52
|
+
/** Update any engine options on the fly (except `renderer`). */
|
|
53
|
+
setEngineOptions(options: Partial<EngineOptions>): void;
|
|
54
|
+
pause(): void;
|
|
55
|
+
resume(): void;
|
|
56
|
+
/** Stop rendering, remove listeners and (if it was created by us) the canvas. */
|
|
57
|
+
destroy(): void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Mount an interactive background.
|
|
61
|
+
*
|
|
62
|
+
* - `target` `null`/omitted: a fixed, full-viewport canvas is placed behind the page.
|
|
63
|
+
* - `target` is a `<canvas>`: it is drawn into directly and sized by your CSS.
|
|
64
|
+
* - `target` is any other element (or a selector): a canvas is appended that
|
|
65
|
+
* fills it. The element gets `position: relative` if it was `static`.
|
|
66
|
+
*
|
|
67
|
+
* @example createBackground('#hero', topo({ levels: 14 }), { maxFps: 30 })
|
|
68
|
+
*/
|
|
69
|
+
export declare function createBackground<O extends object>(target: HTMLElement | string | null | undefined, style: Style<O>, engineOptions?: Partial<EngineOptions>): Background<O>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { RendererName, Style } from './style.js';
|
|
2
|
+
/** Options shared by every style: how and when the background is rendered. */
|
|
3
|
+
export interface EngineOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Rendering backend, chosen when the background is created (changing it later has no effect).
|
|
6
|
+
* `'auto'` uses the style's renderers in their own order (WebGL2 first for the built-in styles)
|
|
7
|
+
* and falls back to the next when the browser lacks one. Naming one tries it first.
|
|
8
|
+
*/
|
|
9
|
+
renderer: 'auto' | RendererName;
|
|
10
|
+
/** Fill colour painted behind the style. `null` or `'transparent'` keeps the canvas transparent. */
|
|
11
|
+
backgroundColor: string | null;
|
|
12
|
+
/**
|
|
13
|
+
* Fade the pattern out towards the top edge over this many CSS pixels: from its own opacity
|
|
14
|
+
* this far in from the edge to fully transparent at the edge. The background colour isn't
|
|
15
|
+
* faded. `0` = no fade. Fades on several sides multiply where they overlap.
|
|
16
|
+
*/
|
|
17
|
+
fadeOutTop: number;
|
|
18
|
+
/** Like `fadeOutTop`, towards the right edge. */
|
|
19
|
+
fadeOutRight: number;
|
|
20
|
+
/** Like `fadeOutTop`, towards the bottom edge. */
|
|
21
|
+
fadeOutBottom: number;
|
|
22
|
+
/** Like `fadeOutTop`, towards the left edge. */
|
|
23
|
+
fadeOutLeft: number;
|
|
24
|
+
/** React to the pointer and to clicks / taps. */
|
|
25
|
+
interactive: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Upper limit for the canvas pixel density. Canvas memory grows with its square:
|
|
28
|
+
* a 1920×1080 canvas takes 8 MB at 1, 18 MB at 1.5 and 32 MB at 2.
|
|
29
|
+
*/
|
|
30
|
+
maxDpr: number;
|
|
31
|
+
/** Frame-rate cap while the pointer is moving the background. `0` = uncapped. */
|
|
32
|
+
maxFps: number;
|
|
33
|
+
/** Frame-rate cap while only the style's own animation runs. Lower saves CPU and battery. `0` = same as `maxFps`. */
|
|
34
|
+
idleFps: number;
|
|
35
|
+
/** Hold the style's own animation still when the user prefers reduced motion. */
|
|
36
|
+
respectReducedMotion: boolean;
|
|
37
|
+
}
|
|
38
|
+
export declare const engineDefaults: Readonly<EngineOptions>;
|
|
39
|
+
export interface Background<O extends object> {
|
|
40
|
+
readonly canvas: HTMLCanvasElement;
|
|
41
|
+
/** Name of the style, e.g. `'topo'`. */
|
|
42
|
+
readonly style: string;
|
|
43
|
+
/**
|
|
44
|
+
* The rendering backend actually in use, or `null` when none of the style's renderers works in
|
|
45
|
+
* this browser (e.g. a WebGL-only style without WebGL2): the background then stays blank.
|
|
46
|
+
*/
|
|
47
|
+
readonly renderer: RendererName | null;
|
|
48
|
+
readonly options: Readonly<O>;
|
|
49
|
+
readonly engineOptions: Readonly<EngineOptions>;
|
|
50
|
+
/** Update any of the style's options on the fly. */
|
|
51
|
+
setOptions(options: Partial<O>): void;
|
|
52
|
+
/** Update any engine options on the fly (except `renderer`). */
|
|
53
|
+
setEngineOptions(options: Partial<EngineOptions>): void;
|
|
54
|
+
pause(): void;
|
|
55
|
+
resume(): void;
|
|
56
|
+
/** Stop rendering, remove listeners and (if it was created by us) the canvas. */
|
|
57
|
+
destroy(): void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Mount an interactive background.
|
|
61
|
+
*
|
|
62
|
+
* - `target` `null`/omitted: a fixed, full-viewport canvas is placed behind the page.
|
|
63
|
+
* - `target` is a `<canvas>`: it is drawn into directly and sized by your CSS.
|
|
64
|
+
* - `target` is any other element (or a selector): a canvas is appended that
|
|
65
|
+
* fills it. The element gets `position: relative` if it was `static`.
|
|
66
|
+
*
|
|
67
|
+
* @example createBackground('#hero', topo({ levels: 14 }), { maxFps: 30 })
|
|
68
|
+
*/
|
|
69
|
+
export declare function createBackground<O extends object>(target: HTMLElement | string | null | undefined, style: Style<O>, engineOptions?: Partial<EngineOptions>): Background<O>;
|