ImageGeneration
WebGL pixel-mosaic placeholder for generated or slow-loading images, with a dissolve reveal and an in-place regenerate churn
Usage
TxImageGeneration wraps one element and paints a churning pixel-mosaic shader inside it while an image is being made or loaded, then dissolves into the real image. It requires three as a peer dependency.
Loading demo...
Loading flag with a known image
When the card stays mounted after the job ends, pause it while waiting and reveal through the exposed handle.
<script setup lang="ts">
import { ref, watch } from 'vue'
const props = defineProps<{ generating: boolean, src: string }>()
const cell = ref()
watch(() => props.generating, (generating) => {
// Generating: fade any shown image back to the shader.
// Done: reveal the image and hold it until the next run.
if (generating)
cell.value?.triggerHide()
else
cell.value?.triggerReveal({ hold: 'manual' })
})
</script>
<template>
<TxImageGeneration ref="cell" :images="[props.src]" :paused="props.generating" role="img" :aria-label="props.generating ? 'Generating image' : 'Result'" :aria-busy="props.generating">
<div style="width: 320px; height: 200px; border-radius: 12px" />
</TxImageGeneration>
</template>
Regenerate and variants
triggerRegenerate() only runs while an image is showing: the image breaks into the cell grid, churns, and the next image from the pool dissolves in.
<script setup lang="ts">
import { ref } from 'vue'
const cell = ref()
const variants = ['/a.jpg', '/b.jpg', '/c.jpg']
</script>
<template>
<TxImageGeneration ref="cell" preset="pixels-mechanic" :images="variants">
<div style="width: 320px; height: 320px; border-radius: 20px" />
</TxImageGeneration>
<TxButton @click="cell?.triggerReveal({ hold: 'manual' })">Show</TxButton>
<TxButton @click="cell?.triggerRegenerate({ durationMs: 3000 })">Regenerate</TxButton>
</template>
Best Practices
- Give the wrapped child an explicit width and height; the wrapper is
inline-blockand sizes itself to the child. - Pass a pool (
images) before triggering a reveal — with an empty pool every trigger is a silent no-op. - Only animate while working:
:paused="!isGenerating". Never pause for the card's whole lifetime, or the image never appears. prefers-reduced-motionis not handled by the library: gate it yourself (:paused="reduced && generating") and keep the paused state scoped to the waiting window.- Serve reveal images same-origin or with CORS headers; cross-origin images still reveal, but
triggerRegenerate()cannot sample their colours and falls back to the preset palette. - Use
sweep-gradientwith a lowerstrengthfor quiet grid tiles; keep the pixel mosaics for the hero or result surface.
API Reference
Props
| Prop | Type | Default | Description |
|---|---|---|---|
preset | 'pixels-organic' | 'pixels-mechanic' | 'sweep-gradient' | 'pixels-organic' | The bundled effect: soft mosaic, grid-locked mosaic, or a diagonal sweep. |
theme | 'auto' | 'dark' | 'light' | 'auto' | Theme resolution; auto follows the OS live. |
strength | number | 1 | 0-1 scales the canvas opacity; above 1 the palette intensity is boosted instead. |
speed | number | 1 | Scales the effect's whole clock (drift, churn, flicker). |
pixelScale | number | 1 | On-screen pixel-cell size multiplier; the reveal dissolve stays in lockstep. |
cardBg | string | preset default | Card background; also parsed to an opaque RGB for the shader's contrast logic. |
colors | (string | null | undefined)[] | preset palette | Palette override, one entry per shader slot. |
images | string | string[] | [] | Reveal pool; a random pick that never repeats the previous one. |
autoReveal | boolean | false | Runs shader → reveal → hold → hide on its own. |
revealDelayRange | [number, number] | [2, 4] | Random shader-only delay in seconds between reveals. |
revealInitialDelay | number | [number, number] | jitter | One-time delay before the very first reveal. |
revealHoldMs | number | [number, number] | 2000 | Time the image stays visible before the hide fade. |
revealFadeOutMs | number | 300 | Cross-fade back to the shader. |
borderRadius | number | auto-detected | Card corner radius in CSS px. |
paused | boolean | false | Freezes the shader and the reveal scheduler. |
fragmentShader | string | bundled | Replacement GLSL 1.00 fragment shader; shared page-wide while set. |
excludeSrcs | () => string[] | Set<string> | null | none | Sources this pick must avoid, for coordinating instances that share a pool. |
Slots
| Slot | Props | Description |
|---|---|---|
default | - | The card element the effect sizes itself to. |
Events
| Event | Payload | Description |
|---|---|---|
cycle | ImageGenerationCycleEvent | Auto-reveal phase transitions (idle → reveal → visible → hide). |
Exposed Methods
| Method | Description |
|---|---|
triggerReveal({ hold?: 'auto' | 'manual' }) | One reveal pass; no-op while a pass is running or images is empty. |
triggerHide() | Fades a revealed image back to the shader; no-op when nothing is showing. |
triggerRegenerate({ durationMs?, tintFromImage?, autoReveal? }) | Breaks the shown image into cells and churns before the next one dissolves in; no-op unless an image is showing. |
isImageActive() | true while an image is revealing, visible or hiding. |
CSS Variables
None. The effect paints on its own canvases.
Overview
- One shared
THREE.WebGLRendererand one WebGL context serve the whole page; each card copies its frame into its own 2D canvas. - Frame rate is capped at 10 fps, the GL canvas at a device-pixel-ratio cap of 1.25, the visible canvas at 2.
- Cards pause offscreen (
IntersectionObserver, 64 px margin) and the animation loop stops when no card is active; WebGL context loss is handled. - Decoded images are cached by URL across cards and drawn like
object-fit: cover, centre-cropped. - The wrapper takes its corner radius from the child's computed
border-top-left-radiusand applies it to all four corners.
Technologies
- Manually verified against
index.ts,TxImageGeneration.vue,types.tsandimage-generation.test.tsunderpackages/tuffex/packages/components/src/image-generation/. - The
engine/**andpresets/**trees are verbatim ports of upstreamimg-fx(MIT © Jakub Antalik) with strict-TS index hardening only. - Requires
three(peer dependency,>=0.149.0), used exactly as upstream does. - The Vue shell mirrors the upstream wrapper's reveal cycle, imperative handle semantics and resize behaviour.
- Component source:
packages/tuffex/packages/components/src/image-generation/src/TxImageGeneration.vue. - Types:
packages/tuffex/packages/components/src/image-generation/src/types.ts. - Upstream: Jakubantalik/Libraries · img-fx (MIT).
- Coverage:
packages/tuffex/packages/components/src/image-generation/__tests__/image-generation.test.tsverifies preset resolution, image-pool normalisation, the strength branches and the handle's no-op guards.
查看源码
packages/tuffex/packages/components/src/image-generation/index.ts