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"
      />
    </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 rounded-2xl 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.88.1