Skip to content

雨痕 bg

冷雨在玻璃上靜靜滑落

技術關鍵字

名稱 描述
Canvas Shader使用 GLSL 開發,直接在 GPU 上執行,比 Canvas 2D API 更快,但也更難
DOM to Image將 DOM 元素轉換為圖片的技術,基於 SVG foreignObject 實現
CSS Anchor PositioningCSS 新功能,允許元素相對於其他元素(錨點)進行定位
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,
})

v0.63.0