ImageGeneration
A WebGL pixel-mosaic placeholder for generated or slow-loading images.
Usage
Wraps one element, paints a shader inside it, and dissolves into the image once it is ready. Requires the three peer dependency (>=0.149.0).
Presets
preset picks the effect; images is the pool to reveal from.
Loading demo...
Loading Flag with a Known Image
When the card stays mounted after the job ends, reveal or hide the image through the ref. Trigger the reveal after the new images reach the component, and leave paused unset.
<script setup lang="ts">
import { ref, watch } from 'vue'
const props = defineProps<{ generating: boolean, src: string }>()
const cell = ref()
// flush: 'post' fires after the component has the new images.
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' })
}, { flush: 'post' })
</script>
<template>
<TxImageGeneration ref="cell" :images="[props.src]" 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
triggerRegenerate() works only while an image is showing: the image breaks into cells, 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
imagesbefore triggering a reveal; with an empty pool every trigger is a silent no-op. - As a placeholder that never reveals an image, animate only while working (
:paused="!isGenerating"). When it does reveal one, leavepausedunset: a paused card silently ignores reveals. - Serve reveal images same-origin or with CORS headers; cross-origin images still reveal, but
triggerRegenerate()can't sample their colors and falls back to the preset palette. - Use
sweep-gradientwith a lowerstrengthfor quiet grid tiles; keep the pixel mosaics for the main result.
API Reference
Props
| Prop | Type | Default | Description |
|---|---|---|---|
preset | 'pixels-organic' | 'pixels-mechanic' | 'sweep-gradient' | 'pixels-organic' | Bundled effect: soft mosaic, grid-locked mosaic, or diagonal sweep. |
theme | 'auto' | 'dark' | 'light' | 'auto' | Theme; auto follows the page or OS theme live. |
strength | number | 1 | 0–1 scales canvas opacity; above 1 it boosts the palette instead. |
speed | number | 1 | Scales the whole effect clock (drift, churn, flicker). |
pixelScale | number | 1 | Pixel-cell size multiplier; the reveal dissolve stays in step. |
cardBg | string | preset default | Card background, also used for the shader's contrast logic. |
colors | (string | null | undefined)[] | preset palette | Palette override, one entry per shader slot. |
images | string | string[] | [] | Reveal pool; picks at random, never repeating the previous image. |
autoReveal | boolean | false | Loops shader → reveal → hold → hide on its own. |
revealDelayRange | [number, number] | [2, 4] | Random delay between reveals, in seconds. |
revealInitialDelay | number | [number, number] | jitter | One-time delay before the first reveal. |
revealHoldMs | number | [number, number] | 2000 | How long the image stays fully visible. |
revealFadeOutMs | number | 300 | Fade back to the shader. |
borderRadius | number | auto-detected | Card corner radius in CSS px. |
paused | boolean | false | Freezes the shader and auto-reveal; reveal, hide, and regenerate do nothing while paused. |
fragmentShader | string | bundled | Replacement GLSL 1.00 fragment shader; applies page-wide while set. |
excludeSrcs | () => string[] | Set<string> | null | none | Sources this pick must avoid, for instances sharing a pool. |
Events
| Event | Payload | Description |
|---|---|---|
cycle | ImageGenerationCycleEvent | Auto-reveal phase changes (idle → reveal → visible → hide). |
Slots
| Slot | Props | Description |
|---|---|---|
default | - | The card element the effect sizes itself to. |
Exposed Methods
| Method | Description |
|---|---|
element | The root wrapper <div> (HTMLDivElement); null while unmounted. |
triggerReveal({ hold?: 'auto' | 'manual' }) | Runs one reveal, held until triggerHide() with manual; no-op mid-pass or with empty images. |
triggerHide() | Fades the image back to the shader; no-op when nothing is showing. |
triggerRegenerate({ durationMs?, tintFromImage?, autoReveal? }) | Breaks the shown image into churning cells, then dissolves in the next; no-op unless an image is showing. |
isImageActive() | true while an image is revealing, visible, or hiding. |
Overview
- One shared
THREE.WebGLRendererand 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 of 1.25, and the visible canvas at 2.
- Cards pause offscreen (
IntersectionObserver, 64 px margin), the loop stops when no card is active, and WebGL context loss is handled. - Decoded images are cached by URL across cards and drawn like
object-fit: cover, center-cropped. - The corner radius comes from the child's computed
border-top-left-radiusand applies to all four corners. prefers-reduced-motionis not handled; wire it yourself when needed, such as:paused="reduced && generating".
Technologies
engine/**andpresets/**are ported from Jakubantalik/Libraries · img-fx (MIT © Jakub Antalik).- Source:
packages/tuffex/packages/components/src/image-generation/.
查看源码
packages/tuffex/packages/components/src/image-generation/index.ts