Skip to content
Welcome to vote for your favorite component! You can also tell me anything you want to say! (*´∀`)~♥

Flock bg

A school of little fish swimming around ◝( •ω• )◟

This component was inspired by the Okinawa Churaumi Aquarium ヾ(◍'౪`◍)ノ゙

2025-07-26 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
vue
<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

Flock
View example source code
vue
<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
vue
<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[]>;
}

v0.59.0