Flock bg
A school of little fish swimming around ◝( •ω• )◟
This component was inspired by the Okinawa Churaumi Aquarium ヾ(◍'౪`◍)ノ゙
I recently went to the Okinawa Churaumi Aquarium to see the whale sharks, but it happened to be summer vacation --
the crowd was bigger than the fish in the tank.
The flow of people was tangled like a knotted fishing net,
and I was like a trapped little fish, helplessly wriggling forward.
Pushing through the heat of noise and body warmth,
I finally reached that corner I had been longing for.
Before I could even process it, a gentle azure had already caressed my face.
Above me, the canopy was shattered into countless fragments by dancing light, and fish shimmered within.
Time was gently lifted by the waves,
and the afternoon quietly sank into the ocean blue.
The boundary between above and below dissolved, all things fell silent,
leaving only a racing heartbeat and leisurely swimming fish.
Yet one can never stay forever,
so I could only turn back in the fading light,
like a little fish that must leave its school.
Slowly retreating toward the exit,
letting the noise and crowd close in once more.
Usage Examples
Basic Usage
The fish swim toward the mouse cursor.
View example source code
<template>
<div class="w-full flex flex-col gap-4 border border-gray-200 rounded-xl">
<bg-flock />
</div>
</template>
<script setup lang="ts">
import BgFlock from '../bg-flock.vue'
</script>Custom Boid
Don't like fish? Assemble your own creature! (・∀・)9
View example source code
<template>
<div class="w-full flex flex-col gap-4 border border-gray-200 rounded-xl">
<bg-flock
ref="bgRef"
:count="50"
:target-shell="{
radius: 150,
band: 20,
swirlSpeed: -0.5,
}"
>
<div class="scene h-full w-full">
<div class="camera">
<div
v-for="(boid, i) in viewBoids"
:key="i"
class="boid flex preserve-3d"
:style="boid.style"
>
<div class="body flex-center text-[8px] text-white">
{{ i }}
</div>
</div>
<div class="label font-liu-jian-mao-cao z-0 flex-center">
{{ t('flock') }}
</div>
</div>
</div>
</bg-flock>
</div>
</template>
<script setup lang="ts">
import type { CSSProperties } from 'vue'
import { computed, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'
import BgFlock from '../bg-flock.vue'
const { t } = useI18n()
const bgRef = useTemplateRef('bgRef')
const viewBoids = computed<Array<{ style: CSSProperties }>>(() => {
const result: Array<{ style: CSSProperties }> = []
const boidList = bgRef.value?.boidList ?? []
for (const boid of boidList) {
const { yaw, pitch, position } = boid
const { x, y, z } = position
const transform
= `translate3d(${x}px, ${y}px, ${z}px)`
+ ` rotateY(${yaw}rad)`
+ ` rotateZ(${pitch}rad)`
result.push({
style: {
transform,
zIndex: `${Math.round(z)}`,
},
})
}
return result
})
</script>
<style scoped lang="sass">
.scene
position: relative
overflow: hidden
/* 透視感 */
perspective: 700px
isolation: isolate
.camera
position: absolute
inset: 0
transform-style: preserve-3d
/* 可調整角度,可以製造俯視、仰視等等視角 */
// transform: rotateX(30deg)
.boid
position: absolute
left: 0
top: 0
pointer-events: none
will-change: transform
transform-origin: 0 50% 0
/* 基本箭頭(朝 +X 方向),用 clip-path 畫圖形 */
.body
width: 30px
height: 20px
background: linear-gradient(90deg, light-dark(#2ecc71, #3e5c47), light-dark(#a6f7c5, #b6e3c5))
clip-path: polygon(75% 0%, 100% 50%, 75% 100%, 0% 100%, 25% 50%, 0% 0%)
.label
width: 100%
height: 100%
font-size: 20vh
color: light-dark(#333, #DDD)
</style>Fish Tank
Turn the entire webpage into an ocean! ヾ(◍'౪`◍)ノ゙
View example source code
<template>
<div class="w-full">
<base-checkbox
v-model="enable"
:label="t('enable')"
class="border rounded p-4"
/>
<bg-flock
v-if="enable"
class="pointer-events-none left-0 top-0 z-50 h-full w-full !fixed"
:count="300"
:size="10"
:target-shell="{
radius: 150,
band: 50,
swirlSpeed: 0.5,
}"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import BaseCheckbox from '../../base-checkbox.vue'
import BgFlock from '../bg-flock.vue'
const { t } = useI18n()
const enable = ref(false)
</script>How It Works
Based on the Boids algorithm. The default fish are drawn using zdog.
This video explains it really well. I also highly recommend checking out The Nature of Code -- it was one of my foundational reads. (*´∀`)~♥
Source Code
API
Props
interface Props {
/** 初始 boid 數量 */
count?: number;
/** 小魚尺寸 */
size?: number;
/** 速度倍率 */
playbackRate?: number;
boidOptions?: Partial<Pick<
BoidOptions,
'maxForce' | 'maxSpeed' | 'angSmooth'
>>;
/** 行為權重 */
behaviorWeights?: BehaviorWeights;
/** 行為半徑,決定特定行為的考量範圍 */
behaviorRadii?: BehaviorRadii;
/** Shell 模式,用於模擬魚環繞特定目標成球的樣子
*
* 無則維持原本單點 target 的行為
*/
targetShell?: {
radius: number;
band: number;
swirlSpeed: number;
};
}Methods
interface Expose {
boidList: ShallowRef<Boid[]>;
}