雨痕 bg
冷雨在玻璃上靜靜滑落
技術關鍵字
| 名稱 | 描述 |
|---|---|
| Canvas Shader | 使用 GLSL 開發,直接在 GPU 上執行,比 Canvas 2D API 更快,但也更難 |
| DOM to Image | 將 DOM 元素轉換為圖片的技術,基於 SVG foreignObject 實現 |
| CSS Anchor Positioning | CSS 新功能,允許元素相對於其他元素(錨點)進行定位 |
| Post-processing | 增強渲染效果,例如:鏡頭模糊、顏色分割等等 |
使用範例
基本用法
把任何 DOM 元素(圖片、文字等等)放進 bg-rain-trail 的 slot,就能讓內容多上一層下雨的玻璃。
可以調整水珠密度、尺寸、折射強度與滑落速度。

陌生的冷雨捎來遠方氣漩的涼意又是個美麗的雨天
查看範例原始碼
vue
<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>原理
此演算法移植自 Martijn Steinrucken (BigWings) 2017 年的 "Heartfelt" shader (CC BY-NC-SA 3.0)
刪除 HAS_HEART 與 USE_POST_PROCESSING(雷電 / 漸暈 / 心形造型)等示範用裝飾,只保留下雨核心邏輯
canvas 怎麼貼齊 target
canvas 透過 <Teleport> 移到 <body> 之下, 再用 CSS Anchor Positioning 把位置與尺寸綁定到 target:
css
.bg-rain-trail-canvas
top: anchor(top)
left: anchor(left)
width: anchor-size(width)
height: anchor-size(height)target 滾動或尺寸變化時,瀏覽器會原生重算 canvas 位置,不經過 JS,所以零延遲。(≖‿ゝ≖)✧
不支援 anchor positioning 的舊瀏覽器會 fallback 到 JS 的 transform: translate(), 會有約一個 frame 的位置延遲(這是 compositor scroll 的物理限制)。(。-`ω´-)
原始碼
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,
})