Rain Trail bg
Cold rain quietly trickling down the glass.
Usage Examples
Basic Usage
Place any DOM element (image, text, etc.) inside the bg-rain-trail slot to add a layer of rain-soaked glass over the content.
You can tune drop density, size, refraction strength, and fall speed.

View example source code
<template>
<div class="w-full flex flex-col gap-4 py-2">
<div class="example-ctrl grid grid-cols-2 gap-4 p-4 lg:grid-cols-4">
<base-input
v-model.number="options.dropDensity"
type="range"
:label="`${t('dropDensity')}: ${options.dropDensity}`"
:min="4"
:max="30"
:step="1"
/>
<base-input
v-model.number="options.dropSize"
type="range"
:label="`${t('dropSize')}: ${options.dropSize}`"
:min="0.5"
:max="2"
:step="0.01"
/>
<!-- <base-input
v-model.number="options.refraction"
type="range"
:label="`${t('refraction')}: ${options.refraction}`"
:min="0"
:max="0.2"
:step="0.001"
/> -->
<base-input
v-model.number="options.fallSpeed"
type="range"
:label="`${t('fallSpeed')}: ${options.fallSpeed}`"
:min="0"
:max="1"
:step="0.01"
/>
<base-input
v-model.number="options.blurAmount"
type="range"
:label="`${t('blurAmount')}: ${options.blurAmount}`"
:min="0"
:max="0.01"
:step="0.0001"
class="col-span-2 lg:col-span-4"
/>
</div>
<bg-rain-trail
class="aspect-video w-full overflow-hidden rounded-lg"
:drop-density="options.dropDensity"
:drop-size="options.dropSize"
:refraction="options.refraction"
:fall-speed="options.fallSpeed"
:blur-amount="options.blurAmount"
>
<div class="relative h-full w-full">
<img
src="/painting-codfish-rain.webp"
:alt="t('sceneAlt')"
class="h-full w-full object-cover"
>
<div
class="absolute inset-0 flex items-end justify-end p-4 text-white"
>
<div class="text-right text-xs leading-[2] tracking-widest md:text-base">
<span class="block">{{ t('verse1') }}</span>
<span class="block">{{ t('verse2') }}</span>
</div>
</div>
</div>
</bg-rain-trail>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import BaseInput from '../../base-input.vue'
import BgRainTrail from '../bg-rain-trail.vue'
const { t } = useI18n()
const options = ref({
dropDensity: 30,
dropSize: 1.5,
refraction: 0.005,
fallSpeed: 0.25,
blurAmount: 0.0005,
})
</script>How it Works
The algorithm is ported from Martijn Steinrucken (BigWings)' 2017 "Heartfelt" shader (CC BY-NC-SA 3.0).
HAS_HEART and USE_POST_PROCESSING (lightning / vignette / heart-shaped decorations) are stripped out, keeping only the core rain logic.
How the canvas aligns to the target
The canvas is moved under <body> via <Teleport>, then bound to the target's position and size with CSS Anchor Positioning:
.bg-rain-trail-canvas
top: anchor(top)
left: anchor(left)
width: anchor-size(width)
height: anchor-size(height)When the target scrolls or resizes, the browser natively recalculates the canvas position without going through JS, so there's zero latency. Older browsers without anchor positioning support fall back to JS-driven transform: translate(), which has roughly one frame of position lag (a physical limit of compositor scroll).
Source Code
API
Props
interface Props {
/**
* CSS selector,指定後雨滴效果會套用到頁面上對應的 DOM,並忽略 slot 內容。
* 若同時提供 selector 與 slot,優先採用 selector。
*/
selector?: string;
/** 雨珠在畫面上的密度,數值越大水珠越密集。@default 12 */
dropDensity?: number;
/** 雨珠尺寸係數,1 為基準大小。@default 1 */
dropSize?: number;
/** 折射強度,越高扭曲越明顯。@default 0.01 */
refraction?: number;
/** 滑落速度倍率。@default 0.25 */
fallSpeed?: number;
/** 玻璃整體霧化程度,0 為清澈、值越大越朦朧。@default 0.0000001 */
blurAmount?: number;
/**
* canvas 的 z-index
* @default 1
*/
zIndex?: number;
/**
* snapdom 擷取倍率,越低紋理越糊但擷取越快;雨層霧化採樣後通常看不出差異。
* 未指定時用 `min(devicePixelRatio, 2)`。
*/
captureScale?: number;
/**
* snapdom 快取策略;高頻 `recapture()` 場景用 `'full'` 可省下重複的樣式 / 字型運算。
* @default 'full'
*/
captureCache?: CaptureCachePolicy;
}Slots
<!-- @slot 玻璃後方的場景內容;指定 `selector` 後此插槽不會渲染 -->
<slot />Methods
defineExpose({
/** 手動觸發重新擷取 DOM 紋理,用於 slot 內容變動或外部 DOM 改變後同步畫面 */
recapture: renderer.recapture,
})