ngwr 15.3.0 → 15.4.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/bits/border-glow/styles/_rules.scss +60 -49
- package/bits/click-spark/click-spark.scss +5 -1
- package/bits/click-spark/styles/_index.scss +10 -15
- package/bits/click-spark/styles/_rules.scss +20 -0
- package/fesm2022/ngwr-bits-aurora.mjs +108 -52
- package/fesm2022/ngwr-bits-aurora.mjs.map +1 -1
- package/fesm2022/ngwr-bits-border-glow-testing.mjs +8 -8
- package/fesm2022/ngwr-bits-border-glow-testing.mjs.map +1 -1
- package/fesm2022/ngwr-bits-border-glow.mjs +29 -26
- package/fesm2022/ngwr-bits-border-glow.mjs.map +1 -1
- package/fesm2022/ngwr-bits-dot-grid.mjs +11 -14
- package/fesm2022/ngwr-bits-dot-grid.mjs.map +1 -1
- package/fesm2022/ngwr-bits-fuzzy-text.mjs +18 -4
- package/fesm2022/ngwr-bits-fuzzy-text.mjs.map +1 -1
- package/fesm2022/ngwr-bits-ripple-grid.mjs +14 -13
- package/fesm2022/ngwr-bits-ripple-grid.mjs.map +1 -1
- package/fesm2022/ngwr-bits-splash-cursor.mjs +21 -10
- package/fesm2022/ngwr-bits-splash-cursor.mjs.map +1 -1
- package/fesm2022/ngwr-bits-waves.mjs +10 -15
- package/fesm2022/ngwr-bits-waves.mjs.map +1 -1
- package/fesm2022/ngwr-pipes.mjs +162 -51
- package/fesm2022/ngwr-pipes.mjs.map +1 -1
- package/fesm2022/ngwr-statistic.mjs +11 -16
- package/fesm2022/ngwr-statistic.mjs.map +1 -1
- package/fesm2022/ngwr-version.mjs +1 -1
- package/fesm2022/ngwr-version.mjs.map +1 -1
- package/llms-full.txt +2 -2
- package/package.json +1 -1
- package/schematics/use/symbol-map.json +1 -0
- package/skills/ngwr/SKILL.md +3 -1
- package/types/ngwr-bits-aurora.d.ts +24 -2
- package/types/ngwr-bits-border-glow-testing.d.ts +9 -5
- package/types/ngwr-bits-border-glow.d.ts +10 -4
- package/types/ngwr-bits-fuzzy-text.d.ts +2 -1
- package/types/ngwr-bits-ripple-grid.d.ts +7 -2
- package/types/ngwr-bits-splash-cursor.d.ts +2 -1
- package/types/ngwr-pipes.d.ts +142 -32
- package/types/ngwr-version.d.ts +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-bits-dot-grid.mjs","names":[],"sources":["../../../projects/lib/bits/dot-grid/dot-grid.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n *\n * After the Dot Grid background on reactbits.dev\n * (https://www.reactbits.dev/backgrounds/dot-grid). The effect is the same\n * idea; the code is ngwr's own: a 2D canvas, one spring per dot, and a frame\n * loop that sleeps whenever nothing moves.\n */\n\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n ElementRef,\n NgZone,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n computed,\n effect,\n inject,\n input,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { numAttr, resolveCssColor, watchColorScheme } from 'ngwr/utils';\n\n/** One dot: where the grid puts it, how far it has been pushed, and how lit it is. */\ninterface Dot {\n readonly x: number;\n readonly y: number;\n /** Offset from rest, in CSS pixels. */\n ox: number;\n oy: number;\n /** Velocity of that offset, in CSS pixels per 60 Hz frame. */\n vx: number;\n vy: number;\n /** How far toward the active colour, 0..1. */\n glow: number;\n}\n\n/**\n * The most bitmap pixels the canvas is backed with, the bound `wr-waves`\n * uses: a hero keeps the full 2x, and a very wide one steps down toward 1x\n * rather than growing a bitmap every frame has to clear and fill.\n */\nconst MAX_CANVAS_PIXELS = 2_500_000;\n\n/**\n * The smallest pitch the grid is laid out at. A 1px gap on a full-page host\n * is two million dots, which no frame can draw; 4px is already a fine mesh.\n */\nconst MIN_GAP = 4;\n\n/** A 60 Hz frame, in ms — the unit the spring constants below are tuned in. */\nconst FRAME_MS = 1000 / 60;\n\n/** The longest step one frame may take, so a tab that wakes after a pause does not fling the dots. */\nconst MAX_STEP = 3;\n\n/** Spring pull back toward the target offset, per frame. */\nconst SPRING = 0.1;\n\n/** Velocity a dot keeps from one frame to the next; under 1 is what lets it settle. */\nconst DAMPING = 0.8;\n\n/** Share of the gap to its target a dot's glow closes per frame while it brightens. */\nconst GLOW_RISE = 0.3;\n\n/** The same while it fades — slower, so the pointer leaves a short trail behind it. */\nconst GLOW_FALL = 0.06;\n\n/** Below this, in pixels or pixels per frame, a dot counts as at rest. */\nconst REST = 0.01;\n\n/** Below this a glow counts as settled: under one step of an 8-bit alpha. */\nconst GLOW_REST = 1 / 256;\n\n/** Alpha steps the lit dots are batched into, one path and one fill each. */\nconst GLOW_LEVELS = 16;\n\n/** What the dots fall back to when the colour cannot be read: `--wr-border-strong` in light. */\nconst DOT_FALLBACK = 'rgb(156, 163, 175)';\n\n/** What the lit dots fall back to: the shipped primary. */\nconst ACTIVE_FALLBACK = 'rgb(57, 105, 226)';\n\nconst TAU = Math.PI * 2;\n\n/**\n * Dot-grid background — evenly spaced dots on a 2D canvas that light up in the\n * active colour near the pointer and are pushed away from it, both by the same\n * falloff, then spring back. Fills its nearest positioned ancestor; put it\n * behind hero copy or section content.\n *\n * The pointer is followed on the host's PARENT, with passive listeners, so the\n * dots answer a pointer that is over the content in front of them rather than\n * over the canvas — the host itself takes no pointer events and never steals a\n * click. Placed directly in a shadow root, where it has no parent element, it\n * follows the pointer on the shadow host; with neither it listens nowhere and\n * the grid stays unlit. Content placed after it needs `position: relative` (or\n * any z-index) to paint above it, as with any absolutely positioned layer.\n *\n * The grid is laid out in the host's own box, before transforms, so one that\n * boots inside a scaling-in panel (a dialog opening) is not stretched when the\n * animation ends, and the pointer is mapped back through the transform.\n *\n * Decorative: the canvas is `aria-hidden`, the host has no role and nothing in\n * it takes focus. The frame loop runs only while something moves — a still\n * grid asks for no frames at all — and never while the host is off screen.\n *\n * Under `prefers-reduced-motion` one still grid is drawn, in the resting\n * colour, and nothing follows the pointer; it is drawn again when the colours\n * or the page's colour scheme change. The preference is read live.\n *\n * Colours are theme tokens by default — the dots `--wr-border-strong`, the lit\n * ones `--wr-color-primary` — through the `--wr-dot-grid-dot-color` and\n * `--wr-dot-grid-active-color` hooks, and resolved before they reach the\n * canvas, which reads neither a `var()` nor a `light-dark()`.\n *\n * @example\n * ```html\n * <section style=\"position: relative; min-height: 24rem; overflow: hidden\">\n * <wr-dot-grid />\n * <h1 style=\"position: relative\">Hero copy above the dots</h1>\n * </section>\n * ```\n *\n * @see https://ngwr.dev/bits/dot-grid\n */\n@Component({\n selector: 'wr-dot-grid',\n template: '<canvas #canvas class=\"wr-dot-grid__canvas\" aria-hidden=\"true\"></canvas>',\n styleUrl: './dot-grid.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-dot-grid',\n // Feed the CSS stand-in grid (see `styles/_rules.scss`) so it draws at the\n // real pitch, size and colour, and switch it off once the canvas has painted.\n '[style.--wr-dot-grid-gap.px]': 'pitch()',\n '[style.--wr-dot-grid-dot-size.px]': 'size()',\n '[style.--wr-dot-grid-input-dot-color]': 'standInColor()',\n '[class.wr-dot-grid--painted]': 'painted()',\n },\n})\nexport class WrDotGrid {\n /** Dot diameter in px. @default 3 */\n readonly dotSize = input(3, { transform: numAttr(3) });\n\n /** Distance between dot centres in px, never under 4. @default 24 */\n readonly gap = input(24, { transform: numAttr(24) });\n\n /** Resting dot colour, any CSS colour. When unset, empty or blank, `--wr-dot-grid-dot-color` decides. */\n readonly dotColor = input<string | null>(null);\n\n /** Colour a dot takes near the pointer, any CSS colour. When unset, empty or blank, `--wr-dot-grid-active-color` decides. */\n readonly activeColor = input<string | null>(null);\n\n /** How far from the pointer a dot still reacts, in px. @default 120 */\n readonly radius = input(120, { transform: numAttr(120) });\n\n /**\n * How far a dot right by the pointer is pushed, in px; the push fades out\n * toward `radius`. `0` keeps the dots in place and only lights them, and a\n * negative value pulls them in. @default 12\n */\n readonly strength = input(12, { transform: numAttr(12) });\n\n /** The pitch the grid is laid out at: `gap`, floored. */\n protected readonly pitch = computed(() => Math.max(MIN_GAP, this.gap()));\n\n /** The dot diameter drawn: `dotSize`, never negative. */\n protected readonly size = computed(() => Math.max(0, this.dotSize()));\n\n /** A colour the author chose, published so the stand-in draws in it too; `null` while the hook decides. */\n protected readonly standInColor = computed(() => (this.dotColor() ?? '').trim() || null);\n\n /** The colours the canvas resolves: an input that is unset, empty or blank leaves the hook in charge. */\n private readonly dotExpression = computed(() => (this.dotColor() ?? '').trim() || 'var(--wr-dot-grid-dot-color)');\n private readonly activeExpression = computed(\n () => (this.activeColor() ?? '').trim() || 'var(--wr-dot-grid-active-color)'\n );\n\n /**\n * Flips once the canvas has drawn, which retires the CSS stand-in grid.\n *\n * A canvas cannot be prerendered, so on an SSR'd page it is blank until the\n * bundle has loaded and hydrated. The stylesheet paints the same dots at the\n * same pitch from the prerendered HTML in the meantime, and this hands over.\n */\n protected readonly painted = signal(false);\n\n private readonly canvasRef = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly zone = inject(NgZone);\n private readonly destroyRef = inject(DestroyRef);\n private readonly platform = inject(WrPlatform);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n private ctx: CanvasRenderingContext2D | null = null;\n /** The host's layout box, in CSS pixels before any transform: what the grid is laid out in. */\n private box = { width: 0, height: 0 };\n /** Bitmap pixels per CSS pixel the canvas is backed at; 0 until the first fit. */\n private ratio = 0;\n private dots: Dot[] = [];\n /** Lit dots by alpha step, reused frame to frame. */\n private readonly lit: Dot[][] = Array.from({ length: GLOW_LEVELS }, () => []);\n /** The pointer, in viewport coordinates, and whether it is over the parent at all. */\n private readonly pointer = { clientX: 0, clientY: 0, inside: false };\n private rafId = 0;\n private lastTime = -1;\n /** Schedules a frame while motion is allowed; `null` under reduced motion. */\n private wake: (() => void) | null = null;\n\n /** Flips once `boot()` has a context and a grid — everything after waits for it. */\n private readonly booted = signal(false);\n\n constructor() {\n if (!this.isBrowser) return;\n\n afterNextRender(() => this.zone.runOutsideAngular(() => this.boot()));\n\n // A new pitch is a new grid. It leaves the bitmap alone, so the frame it\n // asks for repaints over the old grid rather than over a blank canvas.\n effect(() => {\n this.pitch();\n untracked(() => {\n if (!this.ctx) return;\n this.placeDots();\n this.requestDraw();\n });\n });\n\n // Everything else only changes what the next frame draws. A sleeping loop\n // or a still grid has no next frame, so ask for one.\n effect(() => {\n this.size();\n this.dotColor();\n this.activeColor();\n this.radius();\n this.strength();\n untracked(() => this.requestDraw());\n });\n\n // A drawn grid keeps the colours it was drawn in, and the loop sleeps when\n // nothing moves, so a scheme switch asks for a frame to re-resolve them.\n watchColorScheme(() => this.requestDraw());\n\n // Motion or a still grid, decided live.\n effect(onCleanup => {\n if (!this.booted()) return;\n if (this.platform.prefersReducedMotion()) {\n untracked(() => {\n this.rest();\n this.draw();\n });\n return;\n }\n onCleanup(untracked(() => this.zone.runOutsideAngular(() => this.interact())));\n });\n }\n\n // Setup\n\n private boot(): void {\n const canvas = this.canvasRef().nativeElement;\n this.ctx = canvas.getContext('2d');\n if (!this.ctx) return;\n\n // `clientWidth` and not `getBoundingClientRect()`: the rect is the box after\n // transforms, so a grid booted inside a dialog's scale-in would be laid out\n // for 96% of its box and stretched by CSS once the animation ended. The\n // observer's first entry follows with the exact, sub-pixel size.\n const hostEl = this.host.nativeElement;\n this.fit(hostEl.clientWidth, hostEl.clientHeight);\n this.placeDots();\n\n // The box is the observer's to follow, transforms excluded. A window resize\n // is for the pixel ratio, which browser zoom and a move to another screen\n // change with no box change at all.\n const onResize = (): void => this.resize(this.box.width, this.box.height);\n window.addEventListener('resize', onResize);\n const ro = new ResizeObserver(entries => {\n const rect = entries[entries.length - 1]?.contentRect;\n if (rect) this.resize(rect.width, rect.height);\n });\n ro.observe(hostEl);\n this.destroyRef.onDestroy(() => {\n window.removeEventListener('resize', onResize);\n ro.disconnect();\n });\n this.booted.set(true);\n }\n\n /** Follow the pointer and draw on demand; returns what stops both. */\n private interact(): () => void {\n const hostEl = this.host.nativeElement;\n // The parent, not the host: content in front of the canvas receives the\n // pointer, and its events bubble to the parent, never to a sibling. Placed\n // directly in a shadow root it has none, and the shadow host is where those\n // composed events land. Never the host itself, which takes no pointer\n // events and so could never hear one.\n const root = hostEl.getRootNode();\n const target =\n hostEl.parentElement ?? (root instanceof ShadowRoot && root.host instanceof HTMLElement ? root.host : null);\n\n // Off screen the grid asks for no frames. Without an observer (jsdom) it\n // counts as visible.\n let visible = typeof IntersectionObserver === 'undefined';\n const wake = (): void => {\n if (visible && this.rafId === 0) this.rafId = requestAnimationFrame(time => this.tick(time));\n };\n const halt = (): void => {\n cancelAnimationFrame(this.rafId);\n this.rafId = 0;\n this.lastTime = -1;\n };\n\n const onMove = (event: PointerEvent): void => {\n this.pointer.clientX = event.clientX;\n this.pointer.clientY = event.clientY;\n this.pointer.inside = true;\n wake();\n };\n const onLeave = (): void => {\n this.pointer.inside = false;\n wake();\n };\n target?.addEventListener('pointermove', onMove, { passive: true });\n target?.addEventListener('pointerdown', onMove, { passive: true });\n target?.addEventListener('pointerleave', onLeave, { passive: true });\n target?.addEventListener('pointercancel', onLeave, { passive: true });\n\n const io =\n typeof IntersectionObserver === 'undefined'\n ? null\n : new IntersectionObserver(entries => {\n visible = entries[entries.length - 1].isIntersecting;\n if (visible) wake();\n else halt();\n });\n io?.observe(hostEl);\n\n this.wake = wake;\n wake();\n\n return () => {\n target?.removeEventListener('pointermove', onMove);\n target?.removeEventListener('pointerdown', onMove);\n target?.removeEventListener('pointerleave', onLeave);\n target?.removeEventListener('pointercancel', onLeave);\n io?.disconnect();\n halt();\n this.wake = null;\n this.pointer.inside = false;\n };\n }\n\n /** Draw the grid as it should look now: a frame while motion is allowed, at once under reduced motion. */\n private requestDraw(): void {\n if (!this.booted()) return;\n if (this.wake) this.wake();\n else if (this.platform.prefersReducedMotion()) this.draw();\n }\n\n /** Fit the canvas to a new box or pixel ratio and, when that cleared it, repaint at once. */\n private resize(width: number, height: number): void {\n const moved = width !== this.box.width || height !== this.box.height;\n if (!this.fit(width, height)) return;\n if (moved) this.placeDots();\n // Observer entries arrive after the frame's own callbacks and before its\n // paint, so a frame asked for here would leave this one blank — with the\n // stand-in already retired, a resize drag would flicker every frame.\n this.draw();\n // Re-placed dots are at rest and unlit, which is right unless the pointer\n // is over them; a running loop simply carries on.\n if (this.pointer.inside) this.wake?.();\n }\n\n /**\n * Back the canvas for a `width` × `height` box, in CSS pixels, at the screen's\n * pixel ratio. Returns whether anything changed: writing `canvas.width` clears\n * the bitmap even to the value it already has, so an unchanged box is left alone.\n */\n private fit(width: number, height: number): boolean {\n // Capped at 2 and by the pixel budget, never below 1 — a bitmap the size of\n // the CSS box blurs every dot on a 2x screen.\n const area = width * height;\n const budget = area > 0 ? Math.sqrt(MAX_CANVAS_PIXELS / area) : 2;\n const ratio = Math.max(1, Math.min(window.devicePixelRatio || 1, 2, budget));\n if (width === this.box.width && height === this.box.height && ratio === this.ratio) return false;\n\n this.box = { width, height };\n this.ratio = ratio;\n const canvas = this.canvasRef().nativeElement;\n canvas.width = Math.round(width * ratio);\n canvas.height = Math.round(height * ratio);\n // Resizing the bitmap resets the transform, so the scale goes on after it.\n // Every coordinate the draw uses is in CSS pixels.\n this.ctx?.setTransform(ratio, 0, 0, ratio, 0, 0);\n return true;\n }\n\n /** Lay the dots out over the box, every one at rest. */\n private placeDots(): void {\n // One dot sits on the centre of the box and the rest step out from it by\n // the pitch — exactly where the stand-in's centred `background-position`\n // puts them, so the handover does not shift a single dot. One ring past\n // each edge, for the dots a push brings into view.\n const gap = this.pitch();\n const xs = this.axis(this.box.width, gap);\n const ys = this.axis(this.box.height, gap);\n const dots: Dot[] = [];\n for (const y of ys) {\n for (const x of xs) dots.push({ x, y, ox: 0, oy: 0, vx: 0, vy: 0, glow: 0 });\n }\n this.dots = dots;\n }\n\n /** Positions along one side of `length`: the centre, then every `gap` out to one step past each edge. */\n private axis(length: number, gap: number): number[] {\n const centre = length / 2;\n const steps = Math.ceil(centre / gap) + 1;\n const positions: number[] = [];\n for (let i = -steps; i <= steps; i++) positions.push(centre + i * gap);\n return positions;\n }\n\n /** Every dot back at rest and unlit, and the pointer gone. */\n private rest(): void {\n this.pointer.inside = false;\n for (const dot of this.dots) {\n dot.ox = dot.oy = dot.vx = dot.vy = dot.glow = 0;\n }\n }\n\n // Frame loop\n\n private tick(time: number): void {\n this.rafId = 0;\n // The springs are tuned per 60 Hz frame and stepped by the time that really\n // passed, so a 120 Hz screen does not run them twice as fast. A timestamp\n // that did not advance counts as one frame.\n const elapsed = time - this.lastTime;\n const step = this.lastTime < 0 || elapsed <= 0 ? 1 : Math.min(MAX_STEP, elapsed / FRAME_MS);\n this.lastTime = time;\n\n const moving = this.move(step);\n this.draw();\n\n // Nothing left to move: sleep until the pointer, a resize or a new input\n // asks for another frame.\n if (moving && this.wake) this.rafId = requestAnimationFrame(next => this.tick(next));\n else this.lastTime = -1;\n }\n\n /** Advance every dot by `step` frames; returns whether any is still moving. */\n private move(step: number): boolean {\n const reach = Math.max(0, this.radius());\n const push = this.strength();\n const active = this.pointer.inside && reach > 0;\n\n let px = 0;\n let py = 0;\n if (active) {\n // Read once a frame rather than once a move: a page that scrolls under a\n // still pointer moves the grid under it too. The rect is the box as\n // drawn, after any transform on it or an ancestor, and the grid is laid\n // out in the box before them, so the pointer is scaled back into it.\n const rect = this.host.nativeElement.getBoundingClientRect();\n px = (this.pointer.clientX - rect.left) * (rect.width > 0 ? this.box.width / rect.width : 1);\n py = (this.pointer.clientY - rect.top) * (rect.height > 0 ? this.box.height / rect.height : 1);\n }\n\n const reach2 = reach * reach;\n const pull = SPRING * step;\n const keep = Math.pow(DAMPING, step);\n const rise = 1 - Math.pow(1 - GLOW_RISE, step);\n const fall = 1 - Math.pow(1 - GLOW_FALL, step);\n\n let moving = false;\n for (const dot of this.dots) {\n let tx = 0;\n let ty = 0;\n let glow = 0;\n if (active) {\n const dx = dot.x - px;\n const dy = dot.y - py;\n const d2 = dx * dx + dy * dy;\n if (d2 < reach2) {\n const d = Math.sqrt(d2);\n const u = 1 - d / reach;\n // Smoothstep: no edge where the radius ends, and a flat top under the pointer.\n glow = u * u * (3 - 2 * u);\n if (d > 0) {\n tx = (dx / d) * push * glow;\n ty = (dy / d) * push * glow;\n }\n }\n }\n\n dot.vx = (dot.vx + (tx - dot.ox) * pull) * keep;\n dot.vy = (dot.vy + (ty - dot.oy) * pull) * keep;\n dot.ox += dot.vx * step;\n dot.oy += dot.vy * step;\n dot.glow += (glow - dot.glow) * (glow > dot.glow ? rise : fall);\n\n if (\n !moving &&\n (Math.abs(tx - dot.ox) > REST ||\n Math.abs(ty - dot.oy) > REST ||\n Math.abs(dot.vx) > REST ||\n Math.abs(dot.vy) > REST ||\n Math.abs(glow - dot.glow) > GLOW_REST)\n ) {\n moving = true;\n }\n }\n return moving;\n }\n\n private draw(): void {\n const ctx = this.ctx;\n if (!ctx) return;\n // Retire the stand-in only once something is really painted. Runs outside\n // Angular, so the `set` schedules the one host-binding update it needs.\n if (!this.painted()) this.painted.set(true);\n\n const canvas = this.canvasRef().nativeElement;\n const r = this.size() / 2;\n ctx.clearRect(0, 0, this.box.width, this.box.height);\n ctx.globalAlpha = 1;\n ctx.fillStyle = resolveCssColor(canvas, this.dotExpression(), DOT_FALLBACK);\n this.fillDots(ctx, this.dots, r);\n\n // Lit dots go over their resting selves in the active colour, at their\n // glow as alpha: the blend of the two colours without parsing either, so\n // an `oklch()` or a `display-p3` colour works as well as an `rgb()`.\n for (const level of this.lit) level.length = 0;\n let anyLit = false;\n for (const dot of this.dots) {\n const level = Math.round(dot.glow * GLOW_LEVELS);\n if (level > 0) {\n this.lit[Math.min(level, GLOW_LEVELS) - 1].push(dot);\n anyLit = true;\n }\n }\n if (!anyLit) return;\n\n ctx.fillStyle = resolveCssColor(canvas, this.activeExpression(), ACTIVE_FALLBACK);\n this.lit.forEach((dots, i) => {\n if (dots.length === 0) return;\n ctx.globalAlpha = (i + 1) / GLOW_LEVELS;\n this.fillDots(ctx, dots, r);\n });\n ctx.globalAlpha = 1;\n }\n\n /** One path for every dot in `dots`, filled once. */\n private fillDots(ctx: CanvasRenderingContext2D, dots: readonly Dot[], r: number): void {\n ctx.beginPath();\n for (const dot of dots) {\n const x = dot.x + dot.ox;\n const y = dot.y + dot.oy;\n ctx.moveTo(x + r, y);\n ctx.arc(x, y, r, 0, TAU);\n }\n ctx.fill();\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-bits-dot-grid.mjs","names":[],"sources":["../../../projects/lib/bits/dot-grid/dot-grid.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n *\n * After the Dot Grid background on reactbits.dev\n * (https://www.reactbits.dev/backgrounds/dot-grid). The effect is the same\n * idea; the code is ngwr's own: a 2D canvas, one spring per dot, and a frame\n * loop that sleeps whenever nothing moves.\n */\n\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n ElementRef,\n NgZone,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n afterRenderEffect,\n computed,\n effect,\n inject,\n input,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { numAttr, resolveCssColor, watchColorScheme } from 'ngwr/utils';\n\n/** One dot: where the grid puts it, how far it has been pushed, and how lit it is. */\ninterface Dot {\n readonly x: number;\n readonly y: number;\n /** Offset from rest, in CSS pixels. */\n ox: number;\n oy: number;\n /** Velocity of that offset, in CSS pixels per 60 Hz frame. */\n vx: number;\n vy: number;\n /** How far toward the active colour, 0..1. */\n glow: number;\n}\n\n/**\n * The most bitmap pixels the canvas is backed with, the bound `wr-waves`\n * uses: a hero keeps the full 2x, and a very wide one steps down toward 1x\n * rather than growing a bitmap every frame has to clear and fill.\n */\nconst MAX_CANVAS_PIXELS = 2_500_000;\n\n/**\n * The smallest pitch the grid is laid out at. A 1px gap on a full-page host\n * is two million dots, which no frame can draw; 4px is already a fine mesh.\n */\nconst MIN_GAP = 4;\n\n/** A 60 Hz frame, in ms — the unit the spring constants below are tuned in. */\nconst FRAME_MS = 1000 / 60;\n\n/** The longest step one frame may take, so a tab that wakes after a pause does not fling the dots. */\nconst MAX_STEP = 3;\n\n/** Spring pull back toward the target offset, per frame. */\nconst SPRING = 0.1;\n\n/** Velocity a dot keeps from one frame to the next; under 1 is what lets it settle. */\nconst DAMPING = 0.8;\n\n/** Share of the gap to its target a dot's glow closes per frame while it brightens. */\nconst GLOW_RISE = 0.3;\n\n/** The same while it fades — slower, so the pointer leaves a short trail behind it. */\nconst GLOW_FALL = 0.06;\n\n/** Below this, in pixels or pixels per frame, a dot counts as at rest. */\nconst REST = 0.01;\n\n/** Below this a glow counts as settled: under one step of an 8-bit alpha. */\nconst GLOW_REST = 1 / 256;\n\n/** Alpha steps the lit dots are batched into, one path and one fill each. */\nconst GLOW_LEVELS = 16;\n\n/** What the dots fall back to when the colour cannot be read: `--wr-border-strong` in light. */\nconst DOT_FALLBACK = 'rgb(156, 163, 175)';\n\n/** What the lit dots fall back to: the shipped primary. */\nconst ACTIVE_FALLBACK = 'rgb(57, 105, 226)';\n\nconst TAU = Math.PI * 2;\n\n/**\n * Dot-grid background — evenly spaced dots on a 2D canvas that light up in the\n * active colour near the pointer and are pushed away from it, both by the same\n * falloff, then spring back. Fills its nearest positioned ancestor; put it\n * behind hero copy or section content.\n *\n * The pointer is followed on the host's PARENT, with passive listeners, so the\n * dots answer a pointer that is over the content in front of them rather than\n * over the canvas — the host itself takes no pointer events and never steals a\n * click. Placed directly in a shadow root, where it has no parent element, it\n * follows the pointer on the shadow host; with neither it listens nowhere and\n * the grid stays unlit. Content placed after it needs `position: relative` (or\n * any z-index) to paint above it, as with any absolutely positioned layer.\n *\n * The grid is laid out in the host's own box, before transforms, so one that\n * boots inside a scaling-in panel (a dialog opening) is not stretched when the\n * animation ends, and the pointer is mapped back through the transform.\n *\n * Decorative: the canvas is `aria-hidden`, the host has no role and nothing in\n * it takes focus. The frame loop runs only while something moves — a still\n * grid asks for no frames at all — and never while the host is off screen.\n *\n * Under `prefers-reduced-motion` one still grid is drawn, in the resting\n * colour, and nothing follows the pointer; it is drawn again when the colours\n * or the page's colour scheme change. The preference is read live.\n *\n * Colours are theme tokens by default — the dots `--wr-border-strong`, the lit\n * ones `--wr-color-primary` — through the `--wr-dot-grid-dot-color` and\n * `--wr-dot-grid-active-color` hooks, and resolved before they reach the\n * canvas, which reads neither a `var()` nor a `light-dark()`.\n *\n * @example\n * ```html\n * <section style=\"position: relative; min-height: 24rem; overflow: hidden\">\n * <wr-dot-grid />\n * <h1 style=\"position: relative\">Hero copy above the dots</h1>\n * </section>\n * ```\n *\n * @see https://ngwr.dev/bits/dot-grid\n */\n@Component({\n selector: 'wr-dot-grid',\n template: '<canvas #canvas class=\"wr-dot-grid__canvas\" aria-hidden=\"true\"></canvas>',\n styleUrl: './dot-grid.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-dot-grid',\n // Feed the CSS stand-in grid (see `styles/_rules.scss`) so it draws at the\n // real pitch, size and colour, and switch it off once the canvas has painted.\n '[style.--wr-dot-grid-gap.px]': 'pitch()',\n '[style.--wr-dot-grid-dot-size.px]': 'size()',\n '[style.--wr-dot-grid-input-dot-color]': 'standInColor()',\n '[class.wr-dot-grid--painted]': 'painted()',\n },\n})\nexport class WrDotGrid {\n /** Dot diameter in px. @default 3 */\n readonly dotSize = input(3, { transform: numAttr(3) });\n\n /** Distance between dot centres in px, never under 4. @default 24 */\n readonly gap = input(24, { transform: numAttr(24) });\n\n /** Resting dot colour, any CSS colour. When unset, empty or blank, `--wr-dot-grid-dot-color` decides. */\n readonly dotColor = input<string | null>(null);\n\n /** Colour a dot takes near the pointer, any CSS colour. When unset, empty or blank, `--wr-dot-grid-active-color` decides. */\n readonly activeColor = input<string | null>(null);\n\n /** How far from the pointer a dot still reacts, in px. @default 120 */\n readonly radius = input(120, { transform: numAttr(120) });\n\n /**\n * How far a dot right by the pointer is pushed, in px; the push fades out\n * toward `radius`. `0` keeps the dots in place and only lights them, and a\n * negative value pulls them in. @default 12\n */\n readonly strength = input(12, { transform: numAttr(12) });\n\n /** The pitch the grid is laid out at: `gap`, floored. */\n protected readonly pitch = computed(() => Math.max(MIN_GAP, this.gap()));\n\n /** The dot diameter drawn: `dotSize`, never negative. */\n protected readonly size = computed(() => Math.max(0, this.dotSize()));\n\n /** A colour the author chose, published so the stand-in draws in it too; `null` while the hook decides. */\n protected readonly standInColor = computed(() => (this.dotColor() ?? '').trim() || null);\n\n /** The colours the canvas resolves: an input that is unset, empty or blank leaves the hook in charge. */\n private readonly dotExpression = computed(() => (this.dotColor() ?? '').trim() || 'var(--wr-dot-grid-dot-color)');\n private readonly activeExpression = computed(\n () => (this.activeColor() ?? '').trim() || 'var(--wr-dot-grid-active-color)'\n );\n\n /**\n * Flips once the canvas has drawn, which retires the CSS stand-in grid.\n *\n * A canvas cannot be prerendered, so on an SSR'd page it is blank until the\n * bundle has loaded and hydrated. The stylesheet paints the same dots at the\n * same pitch from the prerendered HTML in the meantime, and this hands over.\n */\n protected readonly painted = signal(false);\n\n private readonly canvasRef = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly zone = inject(NgZone);\n private readonly destroyRef = inject(DestroyRef);\n private readonly platform = inject(WrPlatform);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n private ctx: CanvasRenderingContext2D | null = null;\n /** The host's layout box, in CSS pixels before any transform: what the grid is laid out in. */\n private box = { width: 0, height: 0 };\n /** Bitmap pixels per CSS pixel the canvas is backed at; 0 until the first fit. */\n private ratio = 0;\n private dots: Dot[] = [];\n /** Lit dots by alpha step, reused frame to frame. */\n private readonly lit: Dot[][] = Array.from({ length: GLOW_LEVELS }, () => []);\n /** The pointer, in viewport coordinates, and whether it is over the parent at all. */\n private readonly pointer = { clientX: 0, clientY: 0, inside: false };\n private rafId = 0;\n private lastTime = -1;\n /** Schedules a frame while motion is allowed; `null` under reduced motion. */\n private wake: (() => void) | null = null;\n\n /** Flips once `boot()` has a context and a grid — everything after waits for it. */\n private readonly booted = signal(false);\n\n constructor() {\n if (!this.isBrowser) return;\n\n afterNextRender(() => this.zone.runOutsideAngular(() => this.boot()));\n\n // A new pitch is a new grid. It leaves the bitmap alone, so the frame asked\n // for below repaints over the old grid rather than over a blank canvas.\n effect(() => {\n this.pitch();\n untracked(() => {\n if (this.ctx) this.placeDots();\n });\n });\n\n // Everything that changes the picture only changes what the next frame\n // draws. A sleeping loop has no next frame, so ask for one; a still grid has\n // none either, so it is drawn at once, every dot at rest and unlit — and so\n // it is the moment motion stops. After render rather than an `effect`, which\n // runs before a directive's host binding: a still grid drawn then would miss\n // a hook it changes in the same pass.\n afterRenderEffect(() => {\n if (!this.booted()) return;\n const still = this.platform.prefersReducedMotion();\n this.pitch();\n this.size();\n this.dotColor();\n this.activeColor();\n this.radius();\n this.strength();\n untracked(() => {\n if (still) this.rest();\n this.requestDraw();\n });\n });\n\n // A drawn grid keeps the colours it was drawn in, and the loop sleeps when\n // nothing moves, so a scheme switch asks for a frame to re-resolve them.\n watchColorScheme(() => this.requestDraw());\n\n // Motion, decided live: never under `prefers-reduced-motion`, which the\n // still grid above answers.\n effect(onCleanup => {\n if (!this.booted() || this.platform.prefersReducedMotion()) return;\n onCleanup(untracked(() => this.zone.runOutsideAngular(() => this.interact())));\n });\n }\n\n // Setup\n\n private boot(): void {\n const canvas = this.canvasRef().nativeElement;\n this.ctx = canvas.getContext('2d');\n if (!this.ctx) return;\n\n // `clientWidth` and not `getBoundingClientRect()`: the rect is the box after\n // transforms, so a grid booted inside a dialog's scale-in would be laid out\n // for 96% of its box and stretched by CSS once the animation ended. The\n // observer's first entry follows with the exact, sub-pixel size.\n const hostEl = this.host.nativeElement;\n this.fit(hostEl.clientWidth, hostEl.clientHeight);\n this.placeDots();\n\n // The box is the observer's to follow, transforms excluded. A window resize\n // is for the pixel ratio, which browser zoom and a move to another screen\n // change with no box change at all.\n const onResize = (): void => this.resize(this.box.width, this.box.height);\n window.addEventListener('resize', onResize);\n const ro = new ResizeObserver(entries => {\n const rect = entries[entries.length - 1]?.contentRect;\n if (rect) this.resize(rect.width, rect.height);\n });\n ro.observe(hostEl);\n this.destroyRef.onDestroy(() => {\n window.removeEventListener('resize', onResize);\n ro.disconnect();\n });\n this.booted.set(true);\n }\n\n /** Follow the pointer and draw on demand; returns what stops both. */\n private interact(): () => void {\n const hostEl = this.host.nativeElement;\n // The parent, not the host: content in front of the canvas receives the\n // pointer, and its events bubble to the parent, never to a sibling. Placed\n // directly in a shadow root it has none, and the shadow host is where those\n // composed events land. Never the host itself, which takes no pointer\n // events and so could never hear one.\n const root = hostEl.getRootNode();\n const target =\n hostEl.parentElement ?? (root instanceof ShadowRoot && root.host instanceof HTMLElement ? root.host : null);\n\n // Off screen the grid asks for no frames. Without an observer (jsdom) it\n // counts as visible.\n let visible = typeof IntersectionObserver === 'undefined';\n const wake = (): void => {\n if (visible && this.rafId === 0) this.rafId = requestAnimationFrame(time => this.tick(time));\n };\n const halt = (): void => {\n cancelAnimationFrame(this.rafId);\n this.rafId = 0;\n this.lastTime = -1;\n };\n\n const onMove = (event: PointerEvent): void => {\n this.pointer.clientX = event.clientX;\n this.pointer.clientY = event.clientY;\n this.pointer.inside = true;\n wake();\n };\n const onLeave = (): void => {\n this.pointer.inside = false;\n wake();\n };\n target?.addEventListener('pointermove', onMove, { passive: true });\n target?.addEventListener('pointerdown', onMove, { passive: true });\n target?.addEventListener('pointerleave', onLeave, { passive: true });\n target?.addEventListener('pointercancel', onLeave, { passive: true });\n\n const io =\n typeof IntersectionObserver === 'undefined'\n ? null\n : new IntersectionObserver(entries => {\n visible = entries[entries.length - 1].isIntersecting;\n if (visible) wake();\n else halt();\n });\n io?.observe(hostEl);\n\n this.wake = wake;\n wake();\n\n return () => {\n target?.removeEventListener('pointermove', onMove);\n target?.removeEventListener('pointerdown', onMove);\n target?.removeEventListener('pointerleave', onLeave);\n target?.removeEventListener('pointercancel', onLeave);\n io?.disconnect();\n halt();\n this.wake = null;\n this.pointer.inside = false;\n };\n }\n\n /** Draw the grid as it should look now: a frame while motion is allowed, at once under reduced motion. */\n private requestDraw(): void {\n if (!this.booted()) return;\n if (this.wake) this.wake();\n else if (this.platform.prefersReducedMotion()) this.draw();\n }\n\n /** Fit the canvas to a new box or pixel ratio and, when that cleared it, repaint at once. */\n private resize(width: number, height: number): void {\n const moved = width !== this.box.width || height !== this.box.height;\n if (!this.fit(width, height)) return;\n if (moved) this.placeDots();\n // Observer entries arrive after the frame's own callbacks and before its\n // paint, so a frame asked for here would leave this one blank — with the\n // stand-in already retired, a resize drag would flicker every frame.\n this.draw();\n // Re-placed dots are at rest and unlit, which is right unless the pointer\n // is over them; a running loop simply carries on.\n if (this.pointer.inside) this.wake?.();\n }\n\n /**\n * Back the canvas for a `width` × `height` box, in CSS pixels, at the screen's\n * pixel ratio. Returns whether anything changed: writing `canvas.width` clears\n * the bitmap even to the value it already has, so an unchanged box is left alone.\n */\n private fit(width: number, height: number): boolean {\n // Capped at 2 and by the pixel budget, never below 1 — a bitmap the size of\n // the CSS box blurs every dot on a 2x screen.\n const area = width * height;\n const budget = area > 0 ? Math.sqrt(MAX_CANVAS_PIXELS / area) : 2;\n const ratio = Math.max(1, Math.min(window.devicePixelRatio || 1, 2, budget));\n if (width === this.box.width && height === this.box.height && ratio === this.ratio) return false;\n\n this.box = { width, height };\n this.ratio = ratio;\n const canvas = this.canvasRef().nativeElement;\n canvas.width = Math.round(width * ratio);\n canvas.height = Math.round(height * ratio);\n // Resizing the bitmap resets the transform, so the scale goes on after it.\n // Every coordinate the draw uses is in CSS pixels.\n this.ctx?.setTransform(ratio, 0, 0, ratio, 0, 0);\n return true;\n }\n\n /** Lay the dots out over the box, every one at rest. */\n private placeDots(): void {\n // One dot sits on the centre of the box and the rest step out from it by\n // the pitch — exactly where the stand-in's centred `background-position`\n // puts them, so the handover does not shift a single dot. One ring past\n // each edge, for the dots a push brings into view.\n const gap = this.pitch();\n const xs = this.axis(this.box.width, gap);\n const ys = this.axis(this.box.height, gap);\n const dots: Dot[] = [];\n for (const y of ys) {\n for (const x of xs) dots.push({ x, y, ox: 0, oy: 0, vx: 0, vy: 0, glow: 0 });\n }\n this.dots = dots;\n }\n\n /** Positions along one side of `length`: the centre, then every `gap` out to one step past each edge. */\n private axis(length: number, gap: number): number[] {\n const centre = length / 2;\n const steps = Math.ceil(centre / gap) + 1;\n const positions: number[] = [];\n for (let i = -steps; i <= steps; i++) positions.push(centre + i * gap);\n return positions;\n }\n\n /** Every dot back at rest and unlit, and the pointer gone. */\n private rest(): void {\n this.pointer.inside = false;\n for (const dot of this.dots) {\n dot.ox = dot.oy = dot.vx = dot.vy = dot.glow = 0;\n }\n }\n\n // Frame loop\n\n private tick(time: number): void {\n this.rafId = 0;\n // The springs are tuned per 60 Hz frame and stepped by the time that really\n // passed, so a 120 Hz screen does not run them twice as fast. A timestamp\n // that did not advance counts as one frame.\n const elapsed = time - this.lastTime;\n const step = this.lastTime < 0 || elapsed <= 0 ? 1 : Math.min(MAX_STEP, elapsed / FRAME_MS);\n this.lastTime = time;\n\n const moving = this.move(step);\n this.draw();\n\n // Nothing left to move: sleep until the pointer, a resize or a new input\n // asks for another frame.\n if (moving && this.wake) this.rafId = requestAnimationFrame(next => this.tick(next));\n else this.lastTime = -1;\n }\n\n /** Advance every dot by `step` frames; returns whether any is still moving. */\n private move(step: number): boolean {\n const reach = Math.max(0, this.radius());\n const push = this.strength();\n const active = this.pointer.inside && reach > 0;\n\n let px = 0;\n let py = 0;\n if (active) {\n // Read once a frame rather than once a move: a page that scrolls under a\n // still pointer moves the grid under it too. The rect is the box as\n // drawn, after any transform on it or an ancestor, and the grid is laid\n // out in the box before them, so the pointer is scaled back into it.\n const rect = this.host.nativeElement.getBoundingClientRect();\n px = (this.pointer.clientX - rect.left) * (rect.width > 0 ? this.box.width / rect.width : 1);\n py = (this.pointer.clientY - rect.top) * (rect.height > 0 ? this.box.height / rect.height : 1);\n }\n\n const reach2 = reach * reach;\n const pull = SPRING * step;\n const keep = Math.pow(DAMPING, step);\n const rise = 1 - Math.pow(1 - GLOW_RISE, step);\n const fall = 1 - Math.pow(1 - GLOW_FALL, step);\n\n let moving = false;\n for (const dot of this.dots) {\n let tx = 0;\n let ty = 0;\n let glow = 0;\n if (active) {\n const dx = dot.x - px;\n const dy = dot.y - py;\n const d2 = dx * dx + dy * dy;\n if (d2 < reach2) {\n const d = Math.sqrt(d2);\n const u = 1 - d / reach;\n // Smoothstep: no edge where the radius ends, and a flat top under the pointer.\n glow = u * u * (3 - 2 * u);\n if (d > 0) {\n tx = (dx / d) * push * glow;\n ty = (dy / d) * push * glow;\n }\n }\n }\n\n dot.vx = (dot.vx + (tx - dot.ox) * pull) * keep;\n dot.vy = (dot.vy + (ty - dot.oy) * pull) * keep;\n dot.ox += dot.vx * step;\n dot.oy += dot.vy * step;\n dot.glow += (glow - dot.glow) * (glow > dot.glow ? rise : fall);\n\n if (\n !moving &&\n (Math.abs(tx - dot.ox) > REST ||\n Math.abs(ty - dot.oy) > REST ||\n Math.abs(dot.vx) > REST ||\n Math.abs(dot.vy) > REST ||\n Math.abs(glow - dot.glow) > GLOW_REST)\n ) {\n moving = true;\n }\n }\n return moving;\n }\n\n private draw(): void {\n const ctx = this.ctx;\n if (!ctx) return;\n // Retire the stand-in only once something is really painted. Runs outside\n // Angular, so the `set` schedules the one host-binding update it needs.\n if (!this.painted()) this.painted.set(true);\n\n const canvas = this.canvasRef().nativeElement;\n const r = this.size() / 2;\n ctx.clearRect(0, 0, this.box.width, this.box.height);\n ctx.globalAlpha = 1;\n ctx.fillStyle = resolveCssColor(canvas, this.dotExpression(), DOT_FALLBACK);\n this.fillDots(ctx, this.dots, r);\n\n // Lit dots go over their resting selves in the active colour, at their\n // glow as alpha: the blend of the two colours without parsing either, so\n // an `oklch()` or a `display-p3` colour works as well as an `rgb()`.\n for (const level of this.lit) level.length = 0;\n let anyLit = false;\n for (const dot of this.dots) {\n const level = Math.round(dot.glow * GLOW_LEVELS);\n if (level > 0) {\n this.lit[Math.min(level, GLOW_LEVELS) - 1].push(dot);\n anyLit = true;\n }\n }\n if (!anyLit) return;\n\n ctx.fillStyle = resolveCssColor(canvas, this.activeExpression(), ACTIVE_FALLBACK);\n this.lit.forEach((dots, i) => {\n if (dots.length === 0) return;\n ctx.globalAlpha = (i + 1) / GLOW_LEVELS;\n this.fillDots(ctx, dots, r);\n });\n ctx.globalAlpha = 1;\n }\n\n /** One path for every dot in `dots`, filled once. */\n private fillDots(ctx: CanvasRenderingContext2D, dots: readonly Dot[], r: number): void {\n ctx.beginPath();\n for (const dot of dots) {\n const x = dot.x + dot.ox;\n const y = dot.y + dot.oy;\n ctx.moveTo(x + r, y);\n ctx.arc(x, y, r, 0, TAU);\n }\n ctx.fill();\n }\n}\n"],"mappings":"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"}
|
|
@@ -310,16 +310,29 @@ var WrFuzzyText = class WrFuzzyText {
|
|
|
310
310
|
}
|
|
311
311
|
rafId = requestAnimationFrame(run);
|
|
312
312
|
};
|
|
313
|
+
let visible = typeof IntersectionObserver === "undefined";
|
|
314
|
+
let hasBox = canvas.clientWidth > 0 && canvas.clientHeight > 0;
|
|
313
315
|
const resume = () => {
|
|
314
|
-
if (rafId === 0) rafId = requestAnimationFrame(run);
|
|
316
|
+
if (visible && hasBox && rafId === 0) rafId = requestAnimationFrame(run);
|
|
315
317
|
};
|
|
316
318
|
const park = () => {
|
|
317
319
|
cancelAnimationFrame(rafId);
|
|
318
320
|
rafId = 0;
|
|
319
321
|
};
|
|
320
|
-
const io = typeof IntersectionObserver === "undefined" ? null : new IntersectionObserver((entries) =>
|
|
321
|
-
|
|
322
|
-
|
|
322
|
+
const io = typeof IntersectionObserver === "undefined" ? null : new IntersectionObserver((entries) => {
|
|
323
|
+
const { isIntersecting, intersectionRatio } = entries[entries.length - 1];
|
|
324
|
+
visible = isIntersecting && intersectionRatio > 0;
|
|
325
|
+
if (visible) resume();
|
|
326
|
+
else park();
|
|
327
|
+
}, { threshold: [0, 1e-9] });
|
|
328
|
+
io?.observe(canvas);
|
|
329
|
+
const ro = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => {
|
|
330
|
+
hasBox = canvas.clientWidth > 0 && canvas.clientHeight > 0;
|
|
331
|
+
if (hasBox) resume();
|
|
332
|
+
else park();
|
|
333
|
+
});
|
|
334
|
+
ro?.observe(canvas);
|
|
335
|
+
resume();
|
|
323
336
|
const isInsideText = (x, y) => x >= interactiveLeft && x <= interactiveRight && y >= interactiveTop && y <= interactiveBottom;
|
|
324
337
|
const isOverText = (clientX, clientY) => {
|
|
325
338
|
const rect = canvas.getBoundingClientRect();
|
|
@@ -357,6 +370,7 @@ var WrFuzzyText = class WrFuzzyText {
|
|
|
357
370
|
this.teardown = () => {
|
|
358
371
|
park();
|
|
359
372
|
io?.disconnect();
|
|
373
|
+
ro?.disconnect();
|
|
360
374
|
window.removeEventListener("resize", onResize);
|
|
361
375
|
clearTimeout(glitchOnTimer);
|
|
362
376
|
clearTimeout(glitchOffTimer);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-bits-fuzzy-text.mjs","names":[],"sources":["../../../projects/lib/bits/fuzzy-text/fuzzy-text.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n *\n * Angular adaptation of the FuzzyText effect by David Haz / reactbits.dev.\n * Original: https://www.reactbits.dev/text-animations/fuzzy-text\n *\n * Renders text to an offscreen 2D canvas, then redraws it onto the\n * visible canvas one row (or column) at a time with random horizontal /\n * vertical offsets. Intensity reacts to hover / click / glitch state.\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { isPlatformBrowser } from '@angular/common';\nimport type { ElementRef } from '@angular/core';\nimport {\n Component,\n DestroyRef,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n effect,\n inject,\n input,\n viewChild,\n} from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { numAttr, resolveCssColor, watchColorScheme } from 'ngwr/utils';\n\nimport type { WrFuzzyTextDirection } from './types';\n\n/**\n * Canvas-rendered \"fuzzy\" text — each row/column of glyphs is offset by\n * a random amount per frame, giving the text a TV-static or motion-blur\n * appearance. Intensity ramps up on hover, click, and during periodic\n * glitch bursts (configurable).\n *\n * @example\n * ```html\n * <wr-fuzzy-text text=\"404\" />\n * <wr-fuzzy-text\n * text=\"HELLO\"\n * color=\"#0ea5e9\"\n * [fuzzRange]=\"50\"\n * [hoverIntensity]=\"0.8\"\n * [glitchMode]=\"true\"\n * />\n * ```\n *\n * @see https://www.reactbits.dev/text-animations/fuzzy-text\n */\n@Component({\n selector: 'wr-fuzzy-text',\n // The text is drawn into the canvas as PIXELS, so without the first span there is\n // nothing for a screen reader to read at all — the same layer `wr-rotating-text`\n // and `wr-decrypt-text` carry. Hidden inline rather than through a class: this\n // entry point ships no stylesheet, and a consumer who imported none would\n // otherwise see the text twice.\n //\n // BOUND, not a static `style=\"…\"` attribute. A real attribute is refused under\n // `style-src 'self'`, and what is lost there is not a colour: the span drops\n // every visual-hiding declaration and the word renders on screen beside the\n // canvas that already draws it. A binding goes through `style.setProperty`,\n // which is CSSOM and outside CSP's reach.\n template:\n '<span class=\"wr-fuzzy-text__sr-only\" [style]=\"srOnly\">{{ text() }}</span>' +\n '<canvas #canvas aria-hidden=\"true\" [style]=\"fit\"></canvas>',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-fuzzy-text', '[style.color]': \"'var(--wr-color-on-surface)'\" },\n})\nexport class WrFuzzyText {\n /**\n * Visual-hiding declarations for the screen-reader copy of the text. A stable\n * object literal, so the binding is written once rather than on every pass.\n */\n protected readonly srOnly: Readonly<Record<string, string>> = {\n position: 'absolute',\n width: '1px',\n height: '1px',\n overflow: 'hidden',\n 'clip-path': 'inset(50%)',\n 'white-space': 'nowrap',\n };\n\n /**\n * The canvas is as wide as the text it draws, and text cannot wrap, so a long\n * string or a narrow screen pushed it past its container and clipped it. Scaled\n * down to fit instead, keeping its aspect ratio; the pointer maths below reads\n * the drawn size back. Bound for the reason `srOnly` is.\n */\n protected readonly fit: Readonly<Record<string, string>> = { 'max-width': '100%', height: 'auto' };\n\n /** Text to render. */\n readonly text = input.required<string>();\n\n /** Font size as CSS length (`'4rem'`, `'72px'`, …) or px number. @default 'clamp(2rem, 10vw, 10rem)' */\n readonly fontSize = input<string | number>('clamp(2rem, 10vw, 10rem)');\n\n /** Font weight. @default 900 */\n readonly fontWeight = input<number | string>(900);\n\n /** Font family. `'inherit'` reads the canvas's computed style. @default 'inherit' */\n readonly fontFamily = input('inherit');\n\n /**\n * Text colour, any CSS colour — `var()` and `light-dark()` included, resolved\n * against the canvas. `'inherit'` follows the canvas's computed colour. Both are\n * re-read while the text animates, so a colour-scheme change lands on the next\n * frame, and the still copy drawn under reduced motion is redrawn when the\n * page's scheme switches. Ignored if `[gradient]` is set. @default 'inherit'\n */\n readonly color = input('inherit');\n\n /** Optional horizontal gradient stops, each resolved the way `color` is. */\n readonly gradient = input<readonly string[] | null>(null);\n\n /** Enable hover / touch reactivity. @default true */\n readonly enableHover = input(true, { transform: coerceBooleanProperty });\n\n /** Base intensity (0..1) when idle. @default 0.18 */\n readonly baseIntensity = input(0.18, { transform: numAttr(0.18) });\n\n /** Intensity (0..1) while the pointer hovers the text. @default 0.5 */\n readonly hoverIntensity = input(0.5, { transform: numAttr(0.5) });\n\n /** Max pixel displacement per row/column at intensity 1. @default 30 */\n readonly fuzzRange = input(30, { transform: numAttr(30) });\n\n /** Frame rate cap. @default 60 */\n readonly fps = input(60, { transform: numAttr(60) });\n\n /** Fuzz direction. @default 'horizontal' */\n readonly direction = input<WrFuzzyTextDirection>('horizontal');\n\n /** Ms to ease intensity toward target. `0` snaps. @default 0 */\n readonly transitionDuration = input(0, { transform: numAttr(0) });\n\n /** Spike to full intensity briefly on click. @default false */\n readonly clickEffect = input(false, { transform: coerceBooleanProperty });\n\n /** Periodic \"glitch\" bursts at full intensity. @default false */\n readonly glitchMode = input(false, { transform: coerceBooleanProperty });\n\n /** Ms between glitch bursts. @default 2000 */\n readonly glitchInterval = input(2000, { transform: numAttr(2000) });\n\n /** Ms a glitch burst lasts. @default 200 */\n readonly glitchDuration = input(200, { transform: numAttr(200) });\n\n /** Extra pixels between glyphs. @default 0 */\n readonly letterSpacing = input(0, { transform: numAttr(0) });\n\n private readonly canvasRef = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');\n\n private readonly destroyRef = inject(DestroyRef);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n private readonly platform = inject(WrPlatform);\n\n private teardown: (() => void) | undefined;\n\n /**\n * Re-bakes and redraws the still copy, set only while one is on the canvas.\n * The fuzz loop needs none: every frame it draws re-resolves the fill.\n */\n private repaint: (() => void) | undefined;\n\n /**\n * `init()` suspends on font loading, so the component can be destroyed while\n * it is awaiting. At that moment `teardown` is still undefined, so the\n * destroy hook has nothing to call — and everything the rest of `init()`\n * starts (the rAF loop, the glitch timers, canvas listeners) would run for\n * the life of the page. This flag lets the continuation bail out.\n */\n private destroyed = false;\n\n constructor() {\n if (!this.isBrowser) return;\n\n afterNextRender(() => {\n void this.init();\n });\n\n // Re-init whenever any input that affects the offscreen rendering changes.\n effect(() => {\n this.text();\n this.fontSize();\n this.fontWeight();\n this.fontFamily();\n this.color();\n this.gradient();\n this.letterSpacing();\n this.direction();\n this.fuzzRange();\n this.fps();\n this.glitchMode();\n this.glitchInterval();\n this.glitchDuration();\n // Read live: `init()` chooses between the still copy and the loop, so a\n // preference switched while the page is open needs a fresh one.\n this.platform.prefersReducedMotion();\n this.restart();\n });\n\n // The still copy keeps the colours it was baked in, so a colour-scheme\n // switch under reduced motion has to ask for it again.\n watchColorScheme(() => this.repaint?.());\n\n this.destroyRef.onDestroy(() => {\n this.destroyed = true;\n this.teardown?.();\n });\n }\n\n // Init / loop\n\n /** Tear the running instance down and boot a fresh one. A no-op before the first boot. */\n private restart(): void {\n if (!this.teardown) return;\n this.teardown();\n this.teardown = undefined;\n queueMicrotask(() => {\n void this.init();\n });\n }\n\n /**\n * The fill, as computed colours: one entry for `color`, or one per gradient stop.\n * A canvas reads neither a custom property nor `light-dark()` — `fillStyle` keeps\n * its previous value and `addColorStop` throws — so each goes through the cascade\n * first, falling back to the canvas's own computed colour.\n */\n private resolveFill(canvas: HTMLCanvasElement): string[] {\n const inherited = window.getComputedStyle(canvas).color || '#fff';\n const gradient = this.gradient();\n if (gradient && gradient.length >= 2) return gradient.map(stop => resolveCssColor(canvas, stop, inherited));\n const color = this.color();\n return [color === 'inherit' ? inherited : resolveCssColor(canvas, color, inherited)];\n }\n\n /**\n * CSS font size in pixels, resolved where the canvas would resolve it: inside the\n * canvas, whose fallback content is never painted, so `em` and `%` follow the\n * host's own font size.\n */\n private measureFontSize(fontSize: string | number, canvas: HTMLCanvasElement): number {\n if (typeof fontSize === 'number') return fontSize;\n const tmp = document.createElement('span');\n tmp.style.fontSize = fontSize;\n canvas.appendChild(tmp);\n const px = Number.parseFloat(window.getComputedStyle(tmp).fontSize);\n tmp.remove();\n return px;\n }\n\n private async init(): Promise<void> {\n const canvas = this.canvasRef().nativeElement;\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n const fontFamily =\n this.fontFamily() === 'inherit' ? window.getComputedStyle(canvas).fontFamily || 'sans-serif' : this.fontFamily();\n const fontSize = this.fontSize();\n // Numeric font size for buffer maths — and for the font string itself. The canvas\n // was handed the raw CSS, and Chromium caches a parsed font by its string, so a\n // `clamp(2rem, 10vw, 10rem)` kept the pixel size of the viewport it was first\n // parsed at however often the canvas was rebuilt after a resize.\n const numericFontSize = this.measureFontSize(fontSize, canvas);\n const fontSizeStr =\n typeof fontSize === 'number'\n ? `${fontSize}px`\n : Number.isFinite(numericFontSize)\n ? `${numericFontSize}px`\n : fontSize;\n const fontWeight = this.fontWeight();\n const fontString = `${fontWeight} ${fontSizeStr} ${fontFamily}`;\n\n try {\n await document.fonts.load(fontString);\n } catch {\n await document.fonts.ready;\n }\n\n // Destroyed while the fonts were loading — nothing below may start.\n if (this.destroyed) return;\n\n const text = this.text();\n const letterSpacing = this.letterSpacing();\n const fuzzRange = this.fuzzRange();\n const direction = this.direction();\n\n // Offscreen canvas with the laid-out text.\n const offscreen = document.createElement('canvas');\n const offCtx = offscreen.getContext('2d');\n if (!offCtx) return;\n offCtx.font = fontString;\n offCtx.textBaseline = 'alphabetic';\n\n let totalWidth = 0;\n if (letterSpacing !== 0) {\n for (const ch of text) totalWidth += offCtx.measureText(ch).width + letterSpacing;\n totalWidth -= letterSpacing;\n } else {\n totalWidth = offCtx.measureText(text).width;\n }\n const m = offCtx.measureText(text);\n const actualLeft = m.actualBoundingBoxLeft ?? 0;\n const actualRight = letterSpacing !== 0 ? totalWidth : (m.actualBoundingBoxRight ?? m.width);\n const actualAscent = m.actualBoundingBoxAscent ?? numericFontSize;\n const actualDescent = m.actualBoundingBoxDescent ?? numericFontSize * 0.2;\n const textBoundingWidth = Math.ceil(letterSpacing !== 0 ? totalWidth : actualLeft + actualRight);\n const tightHeight = Math.ceil(actualAscent + actualDescent);\n\n const extraWidthBuffer = 10;\n const offscreenWidth = textBoundingWidth + extraWidthBuffer;\n offscreen.width = offscreenWidth;\n offscreen.height = tightHeight;\n const xOffset = extraWidthBuffer / 2;\n\n // The text is baked into the offscreen canvas in the colours resolved now, and\n // re-baked whenever they resolve differently (see `refill` in the loop).\n const bake = (fills: readonly string[]): void => {\n offCtx.font = fontString;\n offCtx.textBaseline = 'alphabetic';\n if (fills.length >= 2) {\n const g = offCtx.createLinearGradient(0, 0, offscreenWidth, 0);\n fills.forEach((c, i) => g.addColorStop(i / (fills.length - 1), c));\n offCtx.fillStyle = g;\n } else {\n offCtx.fillStyle = fills[0];\n }\n\n if (letterSpacing !== 0) {\n let xPos = xOffset;\n for (const ch of text) {\n offCtx.fillText(ch, xPos, actualAscent);\n xPos += offCtx.measureText(ch).width + letterSpacing;\n }\n } else {\n offCtx.fillText(text, xOffset - actualLeft, actualAscent);\n }\n };\n let fillKey = '';\n /** Re-resolve the fill; re-bake and report `true` when it changed. */\n const refill = (): boolean => {\n const fills = this.resolveFill(canvas);\n const key = fills.join('|');\n if (key === fillKey) return false;\n if (fillKey) offCtx.clearRect(0, 0, offscreenWidth, tightHeight);\n fillKey = key;\n bake(fills);\n return true;\n };\n refill();\n\n // Size the visible canvas, leaving room for the max horizontal fuzz.\n const horizontalMargin = fuzzRange + 20;\n const verticalMargin = 0;\n canvas.width = offscreenWidth + horizontalMargin * 2;\n canvas.height = tightHeight + verticalMargin * 2;\n ctx.translate(horizontalMargin, verticalMargin);\n\n const interactiveLeft = horizontalMargin + xOffset;\n const interactiveTop = verticalMargin;\n const interactiveRight = interactiveLeft + textBoundingWidth;\n const interactiveBottom = interactiveTop + tightHeight;\n\n // A viewport-relative size (the default is `clamp(2rem, 10vw, 10rem)`) is\n // measured into the bitmap once, so a rotated phone or a resized window kept\n // the old size until a reload. Re-measured on resize, and rebuilt only when\n // the pixel size actually moved.\n const onResize = (): void => {\n if (!Object.is(this.measureFontSize(this.fontSize(), canvas), numericFontSize)) this.restart();\n };\n window.addEventListener('resize', onResize);\n\n // Reduced motion: paint the text once, clean — no fuzz loop, no\n // glitch bursts, no pointer reactivity. Teardown must still be set\n // so the re-init effect keeps redrawing on input changes.\n if (this.platform.prefersReducedMotion()) {\n ctx.drawImage(offscreen, 0, 0);\n // A scheme switch re-bakes the text in whatever its colours now resolve\n // to and draws it again; a switch that leaves them alone draws nothing.\n // Cleared in device space, the way the loop's second pass clears.\n this.repaint = (): void => {\n if (!refill()) return;\n ctx.save();\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.restore();\n ctx.drawImage(offscreen, 0, 0);\n };\n this.teardown = (): void => {\n this.repaint = undefined;\n window.removeEventListener('resize', onResize);\n };\n return;\n }\n\n let isHovering = false;\n let isClicking = false;\n let isGlitching = false;\n let currentIntensity = this.baseIntensity();\n let targetIntensity = currentIntensity;\n let lastFrame = 0;\n const frameDuration = 1000 / this.fps();\n\n let rafId = 0;\n let glitchOnTimer: ReturnType<typeof setTimeout> | undefined;\n let glitchOffTimer: ReturnType<typeof setTimeout> | undefined;\n let clickTimer: ReturnType<typeof setTimeout> | undefined;\n\n const startGlitchLoop = (): void => {\n if (!this.glitchMode()) return;\n glitchOnTimer = setTimeout(() => {\n isGlitching = true;\n glitchOffTimer = setTimeout(() => {\n isGlitching = false;\n startGlitchLoop();\n }, this.glitchDuration());\n }, this.glitchInterval());\n };\n if (this.glitchMode()) startGlitchLoop();\n\n const run = (ts: number): void => {\n if (ts - lastFrame < frameDuration) {\n rafId = requestAnimationFrame(run);\n return;\n }\n lastFrame = ts;\n\n // A colour scheme switched under a running loop lands here: the text is\n // re-baked in whatever its colours now resolve to.\n refill();\n\n ctx.clearRect(\n -fuzzRange - 20,\n -fuzzRange - 10,\n offscreenWidth + 2 * (fuzzRange + 20),\n tightHeight + 2 * (fuzzRange + 10)\n );\n\n if (isClicking) targetIntensity = 1;\n else if (isGlitching) targetIntensity = 1;\n else if (isHovering) targetIntensity = this.hoverIntensity();\n else targetIntensity = this.baseIntensity();\n\n const td = this.transitionDuration();\n if (td > 0) {\n const step = 1 / (td / frameDuration);\n if (currentIntensity < targetIntensity) currentIntensity = Math.min(currentIntensity + step, targetIntensity);\n else if (currentIntensity > targetIntensity)\n currentIntensity = Math.max(currentIntensity - step, targetIntensity);\n } else {\n currentIntensity = targetIntensity;\n }\n\n if (direction === 'horizontal') {\n for (let j = 0; j < tightHeight; j++) {\n const dx = Math.floor(currentIntensity * (Math.random() - 0.5) * fuzzRange);\n ctx.drawImage(offscreen, 0, j, offscreenWidth, 1, dx, j, offscreenWidth, 1);\n }\n } else if (direction === 'vertical') {\n for (let i = 0; i < offscreenWidth; i++) {\n const dy = Math.floor(currentIntensity * (Math.random() - 0.5) * fuzzRange);\n ctx.drawImage(offscreen, i, 0, 1, tightHeight, i, dy, 1, tightHeight);\n }\n } else {\n // both: horizontal pass, then a vertical pass over what it drew.\n for (let j = 0; j < tightHeight; j++) {\n const dx = Math.floor(currentIntensity * (Math.random() - 0.5) * fuzzRange);\n ctx.drawImage(offscreen, 0, j, offscreenWidth, 1, dx, j, offscreenWidth, 1);\n }\n\n // The second pass runs in DEVICE space, with the transform off.\n //\n // `getImageData` and `putImageData` are specified to ignore the\n // current transformation matrix; `drawImage` and `clearRect` honour\n // it. The context carries `translate(horizontalMargin, …)`, so the\n // two halves of this branch were addressing pixels two different\n // ways: the read started at device x=0, a whole margin to the LEFT of\n // what the horizontal pass had drawn, and the write put it back\n // there — shifting the text off its own box every frame. Resetting\n // the matrix for the duration is what makes the four calls agree.\n ctx.save();\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n\n // Wide enough for the fuzz the first pass may have thrown either way,\n // and clamped to the canvas so no read runs off the edge.\n const readX = Math.max(0, horizontalMargin - fuzzRange);\n const readW = Math.min(canvas.width - readX, offscreenWidth + fuzzRange * 2);\n const readY = verticalMargin;\n const readH = Math.min(canvas.height - readY, tightHeight);\n\n const data = ctx.getImageData(readX, readY, readW, readH);\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.putImageData(data, readX, readY);\n\n for (let i = 0; i < readW; i++) {\n const dy = Math.floor(currentIntensity * (Math.random() - 0.5) * fuzzRange * 0.5);\n if (dy === 0) continue;\n const col = ctx.getImageData(readX + i, readY, 1, readH);\n // `putImageData` REPLACES rather than composites, so the column has\n // to be cleared before it is written back at its offset.\n ctx.clearRect(readX + i, 0, 1, canvas.height);\n ctx.putImageData(col, readX + i, readY + dy);\n }\n\n ctx.restore();\n }\n\n rafId = requestAnimationFrame(run);\n };\n\n // Off screen the loop stops asking for frames, the way `wr-waves` does: it is\n // decoration, and every frame out of sight still redrew one slice per row.\n // Without an observer (jsdom) it simply runs.\n const resume = (): void => {\n if (rafId === 0) rafId = requestAnimationFrame(run);\n };\n const park = (): void => {\n cancelAnimationFrame(rafId);\n rafId = 0;\n };\n const io =\n typeof IntersectionObserver === 'undefined'\n ? null\n : new IntersectionObserver(entries => (entries[entries.length - 1].isIntersecting ? resume() : park()));\n if (io) io.observe(canvas);\n else resume();\n\n const isInsideText = (x: number, y: number): boolean =>\n x >= interactiveLeft && x <= interactiveRight && y >= interactiveTop && y <= interactiveBottom;\n\n /**\n * A client point in canvas pixels. The canvas may be drawn smaller than its\n * bitmap (`fit`), so the offset is scaled back up; a box with no size (jsdom)\n * is taken as unscaled.\n */\n const isOverText = (clientX: number, clientY: number): boolean => {\n const rect = canvas.getBoundingClientRect();\n const scale = rect.width > 0 ? canvas.width / rect.width : 1;\n return isInsideText((clientX - rect.left) * scale, (clientY - rect.top) * scale);\n };\n\n const onMouseMove = (e: MouseEvent): void => {\n if (!this.enableHover()) return;\n isHovering = isOverText(e.clientX, e.clientY);\n };\n const onMouseLeave = (): void => {\n isHovering = false;\n };\n // Passive, and it never cancels: the text is decoration, and a swipe that\n // starts on it has to scroll the page like a swipe anywhere else. It used to\n // `preventDefault()`, which turned the whole canvas into a scroll dead zone on\n // every touch screen.\n const onTouchMove = (e: TouchEvent): void => {\n if (!this.enableHover()) return;\n const t = e.touches[0];\n isHovering = isOverText(t.clientX, t.clientY);\n };\n const onTouchEnd = (): void => {\n isHovering = false;\n };\n const onClick = (): void => {\n if (!this.clickEffect()) return;\n isClicking = true;\n clearTimeout(clickTimer);\n clickTimer = setTimeout(() => {\n isClicking = false;\n }, 150);\n };\n\n // Attached unconditionally, and gated inside the three handlers that can SET a\n // reactive state instead. `enableHover` and `clickEffect` are not in the re-init\n // effect's dependency list, so an attach conditional on them read the value at\n // boot and never again: a `[clickEffect]` bound to a signal that starts `false` —\n // the default, so the ordinary case — stayed dead after it flipped true. Adding\n // them to that list would restart the rAF loop and the glitch timers just to\n // toggle a listener, which is visible; the guards are not.\n //\n // `onMouseLeave` / `onTouchEnd` stay ungated deliberately: they only ever CLEAR\n // toward the idle state, and gating them would strand `isHovering` on for good\n // when hover is switched off mid-hover.\n canvas.addEventListener('mousemove', onMouseMove);\n canvas.addEventListener('mouseleave', onMouseLeave);\n canvas.addEventListener('touchmove', onTouchMove, { passive: true });\n canvas.addEventListener('touchend', onTouchEnd);\n canvas.addEventListener('click', onClick);\n\n this.teardown = (): void => {\n park();\n io?.disconnect();\n window.removeEventListener('resize', onResize);\n clearTimeout(glitchOnTimer);\n clearTimeout(glitchOffTimer);\n clearTimeout(clickTimer);\n canvas.removeEventListener('mousemove', onMouseMove);\n canvas.removeEventListener('mouseleave', onMouseLeave);\n canvas.removeEventListener('touchmove', onTouchMove);\n canvas.removeEventListener('touchend', onTouchEnd);\n canvas.removeEventListener('click', onClick);\n };\n }\n}\n\nexport type { WrFuzzyTextDirection } from './types';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-bits-fuzzy-text.mjs","names":[],"sources":["../../../projects/lib/bits/fuzzy-text/fuzzy-text.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n *\n * Angular adaptation of the FuzzyText effect by David Haz / reactbits.dev.\n * Original: https://www.reactbits.dev/text-animations/fuzzy-text\n *\n * Renders text to an offscreen 2D canvas, then redraws it onto the\n * visible canvas one row (or column) at a time with random horizontal /\n * vertical offsets. Intensity reacts to hover / click / glitch state.\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { isPlatformBrowser } from '@angular/common';\nimport type { ElementRef } from '@angular/core';\nimport {\n Component,\n DestroyRef,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n effect,\n inject,\n input,\n viewChild,\n} from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { numAttr, resolveCssColor, watchColorScheme } from 'ngwr/utils';\n\nimport type { WrFuzzyTextDirection } from './types';\n\n/**\n * Canvas-rendered \"fuzzy\" text — each row/column of glyphs is offset by\n * a random amount per frame, giving the text a TV-static or motion-blur\n * appearance. Intensity ramps up on hover, click, and during periodic\n * glitch bursts (configurable). Off screen, or drawn at no size, the loop\n * stops asking for frames and resumes where it left off.\n *\n * @example\n * ```html\n * <wr-fuzzy-text text=\"404\" />\n * <wr-fuzzy-text\n * text=\"HELLO\"\n * color=\"#0ea5e9\"\n * [fuzzRange]=\"50\"\n * [hoverIntensity]=\"0.8\"\n * [glitchMode]=\"true\"\n * />\n * ```\n *\n * @see https://www.reactbits.dev/text-animations/fuzzy-text\n */\n@Component({\n selector: 'wr-fuzzy-text',\n // The text is drawn into the canvas as PIXELS, so without the first span there is\n // nothing for a screen reader to read at all — the same layer `wr-rotating-text`\n // and `wr-decrypt-text` carry. Hidden inline rather than through a class: this\n // entry point ships no stylesheet, and a consumer who imported none would\n // otherwise see the text twice.\n //\n // BOUND, not a static `style=\"…\"` attribute. A real attribute is refused under\n // `style-src 'self'`, and what is lost there is not a colour: the span drops\n // every visual-hiding declaration and the word renders on screen beside the\n // canvas that already draws it. A binding goes through `style.setProperty`,\n // which is CSSOM and outside CSP's reach.\n template:\n '<span class=\"wr-fuzzy-text__sr-only\" [style]=\"srOnly\">{{ text() }}</span>' +\n '<canvas #canvas aria-hidden=\"true\" [style]=\"fit\"></canvas>',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-fuzzy-text', '[style.color]': \"'var(--wr-color-on-surface)'\" },\n})\nexport class WrFuzzyText {\n /**\n * Visual-hiding declarations for the screen-reader copy of the text. A stable\n * object literal, so the binding is written once rather than on every pass.\n */\n protected readonly srOnly: Readonly<Record<string, string>> = {\n position: 'absolute',\n width: '1px',\n height: '1px',\n overflow: 'hidden',\n 'clip-path': 'inset(50%)',\n 'white-space': 'nowrap',\n };\n\n /**\n * The canvas is as wide as the text it draws, and text cannot wrap, so a long\n * string or a narrow screen pushed it past its container and clipped it. Scaled\n * down to fit instead, keeping its aspect ratio; the pointer maths below reads\n * the drawn size back. Bound for the reason `srOnly` is.\n */\n protected readonly fit: Readonly<Record<string, string>> = { 'max-width': '100%', height: 'auto' };\n\n /** Text to render. */\n readonly text = input.required<string>();\n\n /** Font size as CSS length (`'4rem'`, `'72px'`, …) or px number. @default 'clamp(2rem, 10vw, 10rem)' */\n readonly fontSize = input<string | number>('clamp(2rem, 10vw, 10rem)');\n\n /** Font weight. @default 900 */\n readonly fontWeight = input<number | string>(900);\n\n /** Font family. `'inherit'` reads the canvas's computed style. @default 'inherit' */\n readonly fontFamily = input('inherit');\n\n /**\n * Text colour, any CSS colour — `var()` and `light-dark()` included, resolved\n * against the canvas. `'inherit'` follows the canvas's computed colour. Both are\n * re-read while the text animates, so a colour-scheme change lands on the next\n * frame, and the still copy drawn under reduced motion is redrawn when the\n * page's scheme switches. Ignored if `[gradient]` is set. @default 'inherit'\n */\n readonly color = input('inherit');\n\n /** Optional horizontal gradient stops, each resolved the way `color` is. */\n readonly gradient = input<readonly string[] | null>(null);\n\n /** Enable hover / touch reactivity. @default true */\n readonly enableHover = input(true, { transform: coerceBooleanProperty });\n\n /** Base intensity (0..1) when idle. @default 0.18 */\n readonly baseIntensity = input(0.18, { transform: numAttr(0.18) });\n\n /** Intensity (0..1) while the pointer hovers the text. @default 0.5 */\n readonly hoverIntensity = input(0.5, { transform: numAttr(0.5) });\n\n /** Max pixel displacement per row/column at intensity 1. @default 30 */\n readonly fuzzRange = input(30, { transform: numAttr(30) });\n\n /** Frame rate cap. @default 60 */\n readonly fps = input(60, { transform: numAttr(60) });\n\n /** Fuzz direction. @default 'horizontal' */\n readonly direction = input<WrFuzzyTextDirection>('horizontal');\n\n /** Ms to ease intensity toward target. `0` snaps. @default 0 */\n readonly transitionDuration = input(0, { transform: numAttr(0) });\n\n /** Spike to full intensity briefly on click. @default false */\n readonly clickEffect = input(false, { transform: coerceBooleanProperty });\n\n /** Periodic \"glitch\" bursts at full intensity. @default false */\n readonly glitchMode = input(false, { transform: coerceBooleanProperty });\n\n /** Ms between glitch bursts. @default 2000 */\n readonly glitchInterval = input(2000, { transform: numAttr(2000) });\n\n /** Ms a glitch burst lasts. @default 200 */\n readonly glitchDuration = input(200, { transform: numAttr(200) });\n\n /** Extra pixels between glyphs. @default 0 */\n readonly letterSpacing = input(0, { transform: numAttr(0) });\n\n private readonly canvasRef = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');\n\n private readonly destroyRef = inject(DestroyRef);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n private readonly platform = inject(WrPlatform);\n\n private teardown: (() => void) | undefined;\n\n /**\n * Re-bakes and redraws the still copy, set only while one is on the canvas.\n * The fuzz loop needs none: every frame it draws re-resolves the fill.\n */\n private repaint: (() => void) | undefined;\n\n /**\n * `init()` suspends on font loading, so the component can be destroyed while\n * it is awaiting. At that moment `teardown` is still undefined, so the\n * destroy hook has nothing to call — and everything the rest of `init()`\n * starts (the rAF loop, the glitch timers, canvas listeners) would run for\n * the life of the page. This flag lets the continuation bail out.\n */\n private destroyed = false;\n\n constructor() {\n if (!this.isBrowser) return;\n\n afterNextRender(() => {\n void this.init();\n });\n\n // Re-init whenever any input that affects the offscreen rendering changes.\n effect(() => {\n this.text();\n this.fontSize();\n this.fontWeight();\n this.fontFamily();\n this.color();\n this.gradient();\n this.letterSpacing();\n this.direction();\n this.fuzzRange();\n this.fps();\n this.glitchMode();\n this.glitchInterval();\n this.glitchDuration();\n // Read live: `init()` chooses between the still copy and the loop, so a\n // preference switched while the page is open needs a fresh one.\n this.platform.prefersReducedMotion();\n this.restart();\n });\n\n // The still copy keeps the colours it was baked in, so a colour-scheme\n // switch under reduced motion has to ask for it again.\n watchColorScheme(() => this.repaint?.());\n\n this.destroyRef.onDestroy(() => {\n this.destroyed = true;\n this.teardown?.();\n });\n }\n\n // Init / loop\n\n /** Tear the running instance down and boot a fresh one. A no-op before the first boot. */\n private restart(): void {\n if (!this.teardown) return;\n this.teardown();\n this.teardown = undefined;\n queueMicrotask(() => {\n void this.init();\n });\n }\n\n /**\n * The fill, as computed colours: one entry for `color`, or one per gradient stop.\n * A canvas reads neither a custom property nor `light-dark()` — `fillStyle` keeps\n * its previous value and `addColorStop` throws — so each goes through the cascade\n * first, falling back to the canvas's own computed colour.\n */\n private resolveFill(canvas: HTMLCanvasElement): string[] {\n const inherited = window.getComputedStyle(canvas).color || '#fff';\n const gradient = this.gradient();\n if (gradient && gradient.length >= 2) return gradient.map(stop => resolveCssColor(canvas, stop, inherited));\n const color = this.color();\n return [color === 'inherit' ? inherited : resolveCssColor(canvas, color, inherited)];\n }\n\n /**\n * CSS font size in pixels, resolved where the canvas would resolve it: inside the\n * canvas, whose fallback content is never painted, so `em` and `%` follow the\n * host's own font size.\n */\n private measureFontSize(fontSize: string | number, canvas: HTMLCanvasElement): number {\n if (typeof fontSize === 'number') return fontSize;\n const tmp = document.createElement('span');\n tmp.style.fontSize = fontSize;\n canvas.appendChild(tmp);\n const px = Number.parseFloat(window.getComputedStyle(tmp).fontSize);\n tmp.remove();\n return px;\n }\n\n private async init(): Promise<void> {\n const canvas = this.canvasRef().nativeElement;\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n const fontFamily =\n this.fontFamily() === 'inherit' ? window.getComputedStyle(canvas).fontFamily || 'sans-serif' : this.fontFamily();\n const fontSize = this.fontSize();\n // Numeric font size for buffer maths — and for the font string itself. The canvas\n // was handed the raw CSS, and Chromium caches a parsed font by its string, so a\n // `clamp(2rem, 10vw, 10rem)` kept the pixel size of the viewport it was first\n // parsed at however often the canvas was rebuilt after a resize.\n const numericFontSize = this.measureFontSize(fontSize, canvas);\n const fontSizeStr =\n typeof fontSize === 'number'\n ? `${fontSize}px`\n : Number.isFinite(numericFontSize)\n ? `${numericFontSize}px`\n : fontSize;\n const fontWeight = this.fontWeight();\n const fontString = `${fontWeight} ${fontSizeStr} ${fontFamily}`;\n\n try {\n await document.fonts.load(fontString);\n } catch {\n await document.fonts.ready;\n }\n\n // Destroyed while the fonts were loading — nothing below may start.\n if (this.destroyed) return;\n\n const text = this.text();\n const letterSpacing = this.letterSpacing();\n const fuzzRange = this.fuzzRange();\n const direction = this.direction();\n\n // Offscreen canvas with the laid-out text.\n const offscreen = document.createElement('canvas');\n const offCtx = offscreen.getContext('2d');\n if (!offCtx) return;\n offCtx.font = fontString;\n offCtx.textBaseline = 'alphabetic';\n\n let totalWidth = 0;\n if (letterSpacing !== 0) {\n for (const ch of text) totalWidth += offCtx.measureText(ch).width + letterSpacing;\n totalWidth -= letterSpacing;\n } else {\n totalWidth = offCtx.measureText(text).width;\n }\n const m = offCtx.measureText(text);\n const actualLeft = m.actualBoundingBoxLeft ?? 0;\n const actualRight = letterSpacing !== 0 ? totalWidth : (m.actualBoundingBoxRight ?? m.width);\n const actualAscent = m.actualBoundingBoxAscent ?? numericFontSize;\n const actualDescent = m.actualBoundingBoxDescent ?? numericFontSize * 0.2;\n const textBoundingWidth = Math.ceil(letterSpacing !== 0 ? totalWidth : actualLeft + actualRight);\n const tightHeight = Math.ceil(actualAscent + actualDescent);\n\n const extraWidthBuffer = 10;\n const offscreenWidth = textBoundingWidth + extraWidthBuffer;\n offscreen.width = offscreenWidth;\n offscreen.height = tightHeight;\n const xOffset = extraWidthBuffer / 2;\n\n // The text is baked into the offscreen canvas in the colours resolved now, and\n // re-baked whenever they resolve differently (see `refill` in the loop).\n const bake = (fills: readonly string[]): void => {\n offCtx.font = fontString;\n offCtx.textBaseline = 'alphabetic';\n if (fills.length >= 2) {\n const g = offCtx.createLinearGradient(0, 0, offscreenWidth, 0);\n fills.forEach((c, i) => g.addColorStop(i / (fills.length - 1), c));\n offCtx.fillStyle = g;\n } else {\n offCtx.fillStyle = fills[0];\n }\n\n if (letterSpacing !== 0) {\n let xPos = xOffset;\n for (const ch of text) {\n offCtx.fillText(ch, xPos, actualAscent);\n xPos += offCtx.measureText(ch).width + letterSpacing;\n }\n } else {\n offCtx.fillText(text, xOffset - actualLeft, actualAscent);\n }\n };\n let fillKey = '';\n /** Re-resolve the fill; re-bake and report `true` when it changed. */\n const refill = (): boolean => {\n const fills = this.resolveFill(canvas);\n const key = fills.join('|');\n if (key === fillKey) return false;\n if (fillKey) offCtx.clearRect(0, 0, offscreenWidth, tightHeight);\n fillKey = key;\n bake(fills);\n return true;\n };\n refill();\n\n // Size the visible canvas, leaving room for the max horizontal fuzz.\n const horizontalMargin = fuzzRange + 20;\n const verticalMargin = 0;\n canvas.width = offscreenWidth + horizontalMargin * 2;\n canvas.height = tightHeight + verticalMargin * 2;\n ctx.translate(horizontalMargin, verticalMargin);\n\n const interactiveLeft = horizontalMargin + xOffset;\n const interactiveTop = verticalMargin;\n const interactiveRight = interactiveLeft + textBoundingWidth;\n const interactiveBottom = interactiveTop + tightHeight;\n\n // A viewport-relative size (the default is `clamp(2rem, 10vw, 10rem)`) is\n // measured into the bitmap once, so a rotated phone or a resized window kept\n // the old size until a reload. Re-measured on resize, and rebuilt only when\n // the pixel size actually moved.\n const onResize = (): void => {\n if (!Object.is(this.measureFontSize(this.fontSize(), canvas), numericFontSize)) this.restart();\n };\n window.addEventListener('resize', onResize);\n\n // Reduced motion: paint the text once, clean — no fuzz loop, no\n // glitch bursts, no pointer reactivity. Teardown must still be set\n // so the re-init effect keeps redrawing on input changes.\n if (this.platform.prefersReducedMotion()) {\n ctx.drawImage(offscreen, 0, 0);\n // A scheme switch re-bakes the text in whatever its colours now resolve\n // to and draws it again; a switch that leaves them alone draws nothing.\n // Cleared in device space, the way the loop's second pass clears.\n this.repaint = (): void => {\n if (!refill()) return;\n ctx.save();\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.restore();\n ctx.drawImage(offscreen, 0, 0);\n };\n this.teardown = (): void => {\n this.repaint = undefined;\n window.removeEventListener('resize', onResize);\n };\n return;\n }\n\n let isHovering = false;\n let isClicking = false;\n let isGlitching = false;\n let currentIntensity = this.baseIntensity();\n let targetIntensity = currentIntensity;\n let lastFrame = 0;\n const frameDuration = 1000 / this.fps();\n\n let rafId = 0;\n let glitchOnTimer: ReturnType<typeof setTimeout> | undefined;\n let glitchOffTimer: ReturnType<typeof setTimeout> | undefined;\n let clickTimer: ReturnType<typeof setTimeout> | undefined;\n\n const startGlitchLoop = (): void => {\n if (!this.glitchMode()) return;\n glitchOnTimer = setTimeout(() => {\n isGlitching = true;\n glitchOffTimer = setTimeout(() => {\n isGlitching = false;\n startGlitchLoop();\n }, this.glitchDuration());\n }, this.glitchInterval());\n };\n if (this.glitchMode()) startGlitchLoop();\n\n const run = (ts: number): void => {\n if (ts - lastFrame < frameDuration) {\n rafId = requestAnimationFrame(run);\n return;\n }\n lastFrame = ts;\n\n // A colour scheme switched under a running loop lands here: the text is\n // re-baked in whatever its colours now resolve to.\n refill();\n\n ctx.clearRect(\n -fuzzRange - 20,\n -fuzzRange - 10,\n offscreenWidth + 2 * (fuzzRange + 20),\n tightHeight + 2 * (fuzzRange + 10)\n );\n\n if (isClicking) targetIntensity = 1;\n else if (isGlitching) targetIntensity = 1;\n else if (isHovering) targetIntensity = this.hoverIntensity();\n else targetIntensity = this.baseIntensity();\n\n const td = this.transitionDuration();\n if (td > 0) {\n const step = 1 / (td / frameDuration);\n if (currentIntensity < targetIntensity) currentIntensity = Math.min(currentIntensity + step, targetIntensity);\n else if (currentIntensity > targetIntensity)\n currentIntensity = Math.max(currentIntensity - step, targetIntensity);\n } else {\n currentIntensity = targetIntensity;\n }\n\n if (direction === 'horizontal') {\n for (let j = 0; j < tightHeight; j++) {\n const dx = Math.floor(currentIntensity * (Math.random() - 0.5) * fuzzRange);\n ctx.drawImage(offscreen, 0, j, offscreenWidth, 1, dx, j, offscreenWidth, 1);\n }\n } else if (direction === 'vertical') {\n for (let i = 0; i < offscreenWidth; i++) {\n const dy = Math.floor(currentIntensity * (Math.random() - 0.5) * fuzzRange);\n ctx.drawImage(offscreen, i, 0, 1, tightHeight, i, dy, 1, tightHeight);\n }\n } else {\n // both: horizontal pass, then a vertical pass over what it drew.\n for (let j = 0; j < tightHeight; j++) {\n const dx = Math.floor(currentIntensity * (Math.random() - 0.5) * fuzzRange);\n ctx.drawImage(offscreen, 0, j, offscreenWidth, 1, dx, j, offscreenWidth, 1);\n }\n\n // The second pass runs in DEVICE space, with the transform off.\n //\n // `getImageData` and `putImageData` are specified to ignore the\n // current transformation matrix; `drawImage` and `clearRect` honour\n // it. The context carries `translate(horizontalMargin, …)`, so the\n // two halves of this branch were addressing pixels two different\n // ways: the read started at device x=0, a whole margin to the LEFT of\n // what the horizontal pass had drawn, and the write put it back\n // there — shifting the text off its own box every frame. Resetting\n // the matrix for the duration is what makes the four calls agree.\n ctx.save();\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n\n // Wide enough for the fuzz the first pass may have thrown either way,\n // and clamped to the canvas so no read runs off the edge.\n const readX = Math.max(0, horizontalMargin - fuzzRange);\n const readW = Math.min(canvas.width - readX, offscreenWidth + fuzzRange * 2);\n const readY = verticalMargin;\n const readH = Math.min(canvas.height - readY, tightHeight);\n\n const data = ctx.getImageData(readX, readY, readW, readH);\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.putImageData(data, readX, readY);\n\n for (let i = 0; i < readW; i++) {\n const dy = Math.floor(currentIntensity * (Math.random() - 0.5) * fuzzRange * 0.5);\n if (dy === 0) continue;\n const col = ctx.getImageData(readX + i, readY, 1, readH);\n // `putImageData` REPLACES rather than composites, so the column has\n // to be cleared before it is written back at its offset.\n ctx.clearRect(readX + i, 0, 1, canvas.height);\n ctx.putImageData(col, readX + i, readY + dy);\n }\n\n ctx.restore();\n }\n\n rafId = requestAnimationFrame(run);\n };\n\n // Off screen, or drawn at no size, the loop stops asking for frames, the way\n // `wr-waves` does: it is decoration, and every frame out of sight still redrew\n // one slice per row. The box is the canvas as DRAWN — `max-width: 100%` shrinks\n // it to nothing in a parent with no width — not its bitmap, which is the text's\n // size. Without the observers (jsdom) nothing reports a change, so what is\n // measured here stands: a box runs the loop, no box parks it. A box flush\n // against a zero-height clip intersects with nothing showing, and only a second\n // threshold reports the clip moving. It sits below any real ratio, or a clip\n // closing on a transition steps from under it to 0 without crossing anything.\n let visible = typeof IntersectionObserver === 'undefined';\n let hasBox = canvas.clientWidth > 0 && canvas.clientHeight > 0;\n const resume = (): void => {\n if (visible && hasBox && rafId === 0) rafId = requestAnimationFrame(run);\n };\n const park = (): void => {\n cancelAnimationFrame(rafId);\n rafId = 0;\n };\n const io =\n typeof IntersectionObserver === 'undefined'\n ? null\n : new IntersectionObserver(\n entries => {\n const { isIntersecting, intersectionRatio } = entries[entries.length - 1];\n visible = isIntersecting && intersectionRatio > 0;\n if (visible) resume();\n else park();\n },\n { threshold: [0, 1e-9] }\n );\n io?.observe(canvas);\n const ro =\n typeof ResizeObserver === 'undefined'\n ? null\n : new ResizeObserver(() => {\n hasBox = canvas.clientWidth > 0 && canvas.clientHeight > 0;\n if (hasBox) resume();\n else park();\n });\n ro?.observe(canvas);\n resume();\n\n const isInsideText = (x: number, y: number): boolean =>\n x >= interactiveLeft && x <= interactiveRight && y >= interactiveTop && y <= interactiveBottom;\n\n /**\n * A client point in canvas pixels. The canvas may be drawn smaller than its\n * bitmap (`fit`), so the offset is scaled back up; a box with no size (jsdom)\n * is taken as unscaled.\n */\n const isOverText = (clientX: number, clientY: number): boolean => {\n const rect = canvas.getBoundingClientRect();\n const scale = rect.width > 0 ? canvas.width / rect.width : 1;\n return isInsideText((clientX - rect.left) * scale, (clientY - rect.top) * scale);\n };\n\n const onMouseMove = (e: MouseEvent): void => {\n if (!this.enableHover()) return;\n isHovering = isOverText(e.clientX, e.clientY);\n };\n const onMouseLeave = (): void => {\n isHovering = false;\n };\n // Passive, and it never cancels: the text is decoration, and a swipe that\n // starts on it has to scroll the page like a swipe anywhere else. It used to\n // `preventDefault()`, which turned the whole canvas into a scroll dead zone on\n // every touch screen.\n const onTouchMove = (e: TouchEvent): void => {\n if (!this.enableHover()) return;\n const t = e.touches[0];\n isHovering = isOverText(t.clientX, t.clientY);\n };\n const onTouchEnd = (): void => {\n isHovering = false;\n };\n const onClick = (): void => {\n if (!this.clickEffect()) return;\n isClicking = true;\n clearTimeout(clickTimer);\n clickTimer = setTimeout(() => {\n isClicking = false;\n }, 150);\n };\n\n // Attached unconditionally, and gated inside the three handlers that can SET a\n // reactive state instead. `enableHover` and `clickEffect` are not in the re-init\n // effect's dependency list, so an attach conditional on them read the value at\n // boot and never again: a `[clickEffect]` bound to a signal that starts `false` —\n // the default, so the ordinary case — stayed dead after it flipped true. Adding\n // them to that list would restart the rAF loop and the glitch timers just to\n // toggle a listener, which is visible; the guards are not.\n //\n // `onMouseLeave` / `onTouchEnd` stay ungated deliberately: they only ever CLEAR\n // toward the idle state, and gating them would strand `isHovering` on for good\n // when hover is switched off mid-hover.\n canvas.addEventListener('mousemove', onMouseMove);\n canvas.addEventListener('mouseleave', onMouseLeave);\n canvas.addEventListener('touchmove', onTouchMove, { passive: true });\n canvas.addEventListener('touchend', onTouchEnd);\n canvas.addEventListener('click', onClick);\n\n this.teardown = (): void => {\n park();\n io?.disconnect();\n ro?.disconnect();\n window.removeEventListener('resize', onResize);\n clearTimeout(glitchOnTimer);\n clearTimeout(glitchOffTimer);\n clearTimeout(clickTimer);\n canvas.removeEventListener('mousemove', onMouseMove);\n canvas.removeEventListener('mouseleave', onMouseLeave);\n canvas.removeEventListener('touchmove', onTouchMove);\n canvas.removeEventListener('touchend', onTouchEnd);\n canvas.removeEventListener('click', onClick);\n };\n }\n}\n\nexport type { WrFuzzyTextDirection } from './types';\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from "@angular/core";
|
|
2
|
-
import { Component, DestroyRef, ElementRef, NgZone, PLATFORM_ID, ViewEncapsulation, afterNextRender, computed, effect, inject, input, signal, untracked, viewChild } from "@angular/core";
|
|
2
|
+
import { Component, DestroyRef, ElementRef, NgZone, PLATFORM_ID, ViewEncapsulation, afterNextRender, afterRenderEffect, computed, effect, inject, input, signal, untracked, viewChild } from "@angular/core";
|
|
3
3
|
import { coerceBooleanProperty } from "@angular/cdk/coercion";
|
|
4
4
|
import { isPlatformBrowser } from "@angular/common";
|
|
5
5
|
import { clamp, numAttr, resolveCssColor, watchColorScheme } from "ngwr/utils";
|
|
@@ -156,27 +156,26 @@ var WrRippleGrid = class WrRippleGrid {
|
|
|
156
156
|
wake = null;
|
|
157
157
|
armIdle = null;
|
|
158
158
|
booted = signal(false, ...ngDevMode ? [{ debugName: "booted" }] : /* istanbul ignore next */ []);
|
|
159
|
+
hasBox = signal(false, ...ngDevMode ? [{ debugName: "hasBox" }] : /* istanbul ignore next */ []);
|
|
159
160
|
constructor() {
|
|
160
161
|
if (!this.isBrowser) return;
|
|
161
162
|
afterNextRender(() => this.zone.runOutsideAngular(() => this.boot()));
|
|
162
|
-
|
|
163
|
+
afterRenderEffect(() => {
|
|
164
|
+
if (!this.booted()) return;
|
|
165
|
+
const still = this.platform.prefersReducedMotion();
|
|
163
166
|
this.cell();
|
|
164
167
|
this.stroke();
|
|
165
168
|
this.lineColor();
|
|
166
169
|
this.rippleColor();
|
|
167
170
|
this.strength();
|
|
168
|
-
untracked(() =>
|
|
171
|
+
untracked(() => {
|
|
172
|
+
if (still) this.rest();
|
|
173
|
+
this.requestDraw();
|
|
174
|
+
});
|
|
169
175
|
});
|
|
170
176
|
watchColorScheme(() => this.requestDraw());
|
|
171
177
|
effect((onCleanup) => {
|
|
172
|
-
if (!this.booted()) return;
|
|
173
|
-
if (this.platform.prefersReducedMotion()) {
|
|
174
|
-
untracked(() => {
|
|
175
|
-
this.rest();
|
|
176
|
-
this.draw();
|
|
177
|
-
});
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
178
|
+
if (!this.booted() || !this.hasBox() || this.platform.prefersReducedMotion()) return;
|
|
180
179
|
const trail = this.rippleOnMove();
|
|
181
180
|
const idle = this.idle();
|
|
182
181
|
onCleanup(untracked(() => this.zone.runOutsideAngular(() => this.interact(trail, idle))));
|
|
@@ -254,12 +253,13 @@ var WrRippleGrid = class WrRippleGrid {
|
|
|
254
253
|
target?.addEventListener("pointercancel", onLeave, { passive: true });
|
|
255
254
|
}
|
|
256
255
|
const io = typeof IntersectionObserver === "undefined" ? null : new IntersectionObserver((entries) => {
|
|
257
|
-
|
|
256
|
+
const { isIntersecting, intersectionRatio } = entries[entries.length - 1];
|
|
257
|
+
visible = isIntersecting && intersectionRatio > 0;
|
|
258
258
|
if (visible) {
|
|
259
259
|
wake();
|
|
260
260
|
armIdle();
|
|
261
261
|
} else halt();
|
|
262
|
-
});
|
|
262
|
+
}, { threshold: [0, 1e-9] });
|
|
263
263
|
io?.observe(hostEl);
|
|
264
264
|
this.wake = wake;
|
|
265
265
|
this.armIdle = armIdle;
|
|
@@ -308,6 +308,7 @@ var WrRippleGrid = class WrRippleGrid {
|
|
|
308
308
|
height
|
|
309
309
|
};
|
|
310
310
|
this.ratio = ratio;
|
|
311
|
+
this.hasBox.set(width > 0 && height > 0);
|
|
311
312
|
const canvas = this.canvasRef().nativeElement;
|
|
312
313
|
canvas.width = Math.round(width * ratio);
|
|
313
314
|
canvas.height = Math.round(height * ratio);
|