Components/ImageGeneration

ImageGeneration

WebGL pixel-mosaic placeholder for generated or slow-loading images, with a dissolve reveal and an in-place regenerate churn

VerifiedSince 0.6.2

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-block and 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-motion is 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-gradient with a lower strength for quiet grid tiles; keep the pixel mosaics for the hero or result surface.

API Reference

Props

PropTypeDefaultDescription
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.
strengthnumber10-1 scales the canvas opacity; above 1 the palette intensity is boosted instead.
speednumber1Scales the effect's whole clock (drift, churn, flicker).
pixelScalenumber1On-screen pixel-cell size multiplier; the reveal dissolve stays in lockstep.
cardBgstringpreset defaultCard background; also parsed to an opaque RGB for the shader's contrast logic.
colors(string | null | undefined)[]preset palettePalette override, one entry per shader slot.
imagesstring | string[][]Reveal pool; a random pick that never repeats the previous one.
autoRevealbooleanfalseRuns shader → reveal → hold → hide on its own.
revealDelayRange[number, number][2, 4]Random shader-only delay in seconds between reveals.
revealInitialDelaynumber | [number, number]jitterOne-time delay before the very first reveal.
revealHoldMsnumber | [number, number]2000Time the image stays visible before the hide fade.
revealFadeOutMsnumber300Cross-fade back to the shader.
borderRadiusnumberauto-detectedCard corner radius in CSS px.
pausedbooleanfalseFreezes the shader and the reveal scheduler.
fragmentShaderstringbundledReplacement GLSL 1.00 fragment shader; shared page-wide while set.
excludeSrcs() => string[] | Set<string> | nullnoneSources this pick must avoid, for coordinating instances that share a pool.

Slots

SlotPropsDescription
default-The card element the effect sizes itself to.

Events

EventPayloadDescription
cycleImageGenerationCycleEventAuto-reveal phase transitions (idle → reveal → visible → hide).

Exposed Methods

MethodDescription
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.WebGLRenderer and 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-radius and applies it to all four corners.

Technologies

  • Manually verified against index.ts, TxImageGeneration.vue, types.ts and image-generation.test.ts under packages/tuffex/packages/components/src/image-generation/.
  • The engine/** and presets/** trees are verbatim ports of upstream img-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.ts verifies preset resolution, image-pool normalisation, the strength branches and the handle's no-op guards.
查看源码
packages/tuffex/packages/components/src/image-generation/index.ts