Skip to content

Every little fish swimming around is a visitor on this page right now, and one of them is you ( ´ ▽ ` )ノ If they get in the way of reading, feel free to turn them off.

Snow

bg

A snowy background effect for your webpage -- it even accumulates snow! ( ´ ▽ ` )ノ

NameDescription
Babylon.js3D engine
Particle SystemSpawns large numbers of small objects, commonly used to simulate smoke, fire, rain, and snow
OffscreenCanvasOffloads canvas rendering to a separate thread without blocking the main thread
Web WorkerRuns scripts on a background thread, keeping UI interactions on the main thread unblocked
Vue DirectiveCustom Vue directives that encapsulate DOM manipulation logic and repeated behaviors
Physics SimulationSimulates real-world physics such as gravity, collisions, and velocity

Use the v-bg-static directive to make designated elements accumulate snow.

You can also adjust the snowflake image and size.

<template>
<div class="w-full flex items-center justify-center py-10">
<div class="grid grid-cols-4 gap-10">
<base-checkbox
v-model="enable"
v-bg-static
:label="t('startSnow')"
class="col-span-4 border border-gray-200 rounded-full p-4 px-6 sm:col-span-3"
/>
<base-btn
v-bg-static
:label="t('sweep')"
class="col-span-4 border-gray-200 sm:col-span-1 !rounded-full"
@click="sweep()"
/>
<base-input
:label="t('snowflakeImage')"
class="col-span-2"
>
<div
v-bg-static
class="border rounded"
>
<input
type="file"
accept="image/*"
class="px-2 py-1"
@change="handleImageChange"
>
</div>
</base-input>
<base-input
v-model="options.size"
:label="`${t('particleSize')}: ${options.size}`"
type="range"
:min="1"
:max="20"
class="col-span-2"
/>
</div>
<bg-snow
ref="bgRef"
class="fixed left-0 top-0 z-[100] w-full h-lvh"
:class="{ 'opacity-0': !enable }"
:image-src="imgUrl"
:size="options.size"
/>
</div>
</template>
<script setup lang="ts">
import { useObjectUrl } from '@vueuse/core'
import { onUnmounted, ref, useTemplateRef, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useSiteDark } from '../../../composables/use-site-dark'
import BaseBtn from '../../base-btn.vue'
import BaseCheckbox from '../../base-checkbox.vue'
import BaseInput from '../../base-input.vue'
import BgSnow from '../bg-snow.vue'
import { vBgStatic } from '../v-bg-static'
const { t } = useI18n()
const enable = ref(false)
const imgFile = ref<File>()
function handleImageChange(event: Event) {
const file = (event.target as HTMLInputElement).files?.[0]
imgFile.value = file
}
const imgUrl = useObjectUrl(imgFile)
const bgRef = useTemplateRef('bgRef')
const options = ref({
size: 8,
})
function sweep() {
bgRef.value?.sweep()
}
const isDark = useSiteDark()
const oriValue = isDark.value
watch(enable, (value) => {
isDark.value = value
})
onUnmounted(() => {
isDark.value = oriValue
})
</script>

The snow animation is based on Babylon.js's particle system. The snow accumulation effect performs collision detection on DOM elements marked with v-bg-static.

Here's a small challenge: since the number of snowflakes can be very large, the computational cost can be quite significant. If we calculate directly on the main thread, the page would become laggy and janky.

This is where a neat trick comes in: Offscreen Canvas! ( •̀ ω •́ )✧

This technology allows the entire Canvas rendering to be handed off to a Web Worker. This means all computations, including the expensive calculation tasks, no longer run on the main thread, so you don't have to worry about UI jank. (/≧▽≦)/

When you look at the source code, you might notice I used a package called Comlink. This package greatly simplifies the way messages are passed between Web Workers and the main thread. ( ´ ▽ ` )ノ

Otherwise, using postMessage to pass messages is really cumbersome. ⎝(・ω´・⎝)

interface Expose {
/** 清除積雪 */
sweep: () => void;
}