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

Starry Sea bg

Thousands of little fish swimming upward in winding formations, with perspective depth and atmospheric fog.

Usage Examples

Basic Usage

View example source code
vue
<template>
  <div class="">
    <div class="grid grid-cols-2 gap-x-6 gap-y-2">
      <base-checkbox
        v-model="enable"
        :label="t('enable')"
        class="col-span-2 border rounded p-4"
      />

      <base-input
        v-model="options.fishSize"
        :label="`${t('fishSize')}: ${options.fishSize}`"
        type="range"
        :min="5"
        :max="60"
        :step="1"
      />

      <base-input
        v-model="options.spread"
        :label="`${t('spread')}: ${options.spread}`"
        type="range"
        :min="0.02"
        :max="0.5"
        :step="0.01"
      />

      <base-input
        v-model="options.riseSpeed"
        :label="`${t('riseSpeed')}: ${options.riseSpeed}`"
        type="range"
        :min="0"
        :max="0.002"
        :step="0.0001"
      />

      <base-input
        v-model="options.meanderStrength"
        :label="`${t('meanderStrength')}: ${options.meanderStrength}`"
        type="range"
        :min="0"
        :max="0.01"
        :step="0.0005"
      />

      <base-input
        v-model="options.followSpeed"
        :label="`${t('followSpeed')}: ${options.followSpeed}`"
        type="range"
        :min="0.005"
        :max="0.15"
        :step="0.005"
      />

      <base-input
        v-model="options.turnRate"
        :label="`${t('turnRate')}: ${options.turnRate}`"
        type="range"
        :min="0.01"
        :max="0.2"
        :step="0.01"
      />

      <base-input
        v-model="options.spreadCurve"
        :label="`${t('spreadCurve')}: ${options.spreadCurve}`"
        type="range"
        :min="0.5"
        :max="5"
        :step="0.5"
      />
    </div>

    <bg-starry-sea
      v-if="enable"
      class="pointer-events-none inset-0 z-[99999] !fixed"
      :fish-size="Number(options.fishSize)"
      :spread="Number(options.spread)"
      :rise-speed="Number(options.riseSpeed)"
      :meander-strength="Number(options.meanderStrength)"
      :follow-speed="Number(options.followSpeed)"
      :turn-rate="Number(options.turnRate)"
      :spread-curve="Number(options.spreadCurve)"
    />
  </div>
</template>

<script setup lang="ts">
import { useData } from 'vitepress'
import { onUnmounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import BaseCheckbox from '../../base-checkbox.vue'
import BaseInput from '../../base-input.vue'
import BgStarrySea from '../bg-starry-sea.vue'

const { t } = useI18n()

const enable = ref(false)

const isMobile = window.matchMedia('(max-width: 768px)').matches

const options = ref({
  fishSize: isMobile ? 10 : 18,
  spread: 0.18,
  riseSpeed: 0.0005,
  meanderStrength: 0.003,
  followSpeed: 0.03,
  turnRate: 0.04,
  spreadCurve: 2,
})

const { isDark } = useData()

const oriValue = isDark.value

watch(enable, (value) => {
  isDark.value = value
})

onUnmounted(() => {
  isDark.value = oriValue
})
</script>

How it Works

  • Trail Path System: Multiple winding paths driven by Simplex Noise. Fish follow the path history in sequence, forming long dragon-like formations
  • WebGL2 Instanced Rendering: All fish share the same vertex data. Position, angle, color and depth are passed via instance attributes, completing the render in a single draw call -- even 10,000 fish at once without breaking a sweat! (ゝ∀・)b
  • SDF Fish Shape: An ellipse and rounded triangle SDF are combined in the fragment shader to form the fish silhouette, with a matching color glow effect
  • Perspective Depth: Paths carry Z-axis depth variation. Distant fish shrink, compress spacing, slow down, and fade into the background color for an atmospheric fog effect
  • Depth Sorting: Instance data is sorted by depth each frame, ensuring closer fish are drawn on top of distant ones

Source Code

API

Props

interface Props {
  /** 魚的數量。@default 10000 */
  fishCount?: number;

  /** 魚的大小(px)。@default 18 */
  fishSize?: number;

  /** 長龍路徑數量。@default 6 */
  trailCount?: number;

  /** 路徑歷史步數,越長龍身越長。@default 2000 */
  trailLength?: number;

  /** 魚群散佈寬度(正規化空間)。@default 0.18 */
  spread?: number;

  /** 上升速度。@default 0.0005 */
  riseSpeed?: number;

  /** 路徑蜿蜒幅度。@default 0.003 */
  meanderStrength?: number;

  /** 魚跟隨路徑的平滑速度,0~1,越大越緊跟。@default 0.03 */
  followSpeed?: number;

  /** 路徑轉彎頻率,越小轉彎越平緩。@default 0.04 */
  turnRate?: number;

  /** 散佈集中度,越大魚群越集中在路徑中心,偶有離群魚。@default 2 */
  spreadCurve?: number;
}

v0.62.0