每條游來游去的小魚,都是正在瀏覽這一頁的訪客,有一條是你自己 ( ´ ▽ ` )ノ 覺得干擾閱讀的話,可以把小魚關掉。
空間懸浮輪播
carousel所有圖片會漂浮在空間中!ヾ(◍'౪`◍)ノ゙
| 名稱 | 描述 |
|---|---|
| Babylon.js | 3D 引擎 |
| Post-processing | 增強渲染效果,例如:鏡頭模糊、顏色分割等等 |
| JS 動畫 | 基於 JavaScript 實現的動畫,達成更複雜、精準的動畫控制,常見套件有 GSAP、anime.js 等 |
| Anime.js | 輕量級 JavaScript 動畫函式庫 |
所有圖片會分布在空間中,切換圖片時,鏡頭會移動至目標圖片前方。
0
<template> <div class="w-full flex flex-col gap-4"> <carousel-space-floating ref="carouselRef" class="h-full example-wrap" :images="images" fps-visible />
<div class="flex gap-4"> <base-btn :label="t('prev')" class="flex-1" @click="prev()" /> <base-btn :label="t('next')" class="flex-1" @click="next()" /> </div> </div></template>
<script setup lang="ts">import { ref } from 'vue'import { useI18n } from 'vue-i18n'import BaseBtn from '../../base-btn.vue'import CarouselSpaceFloating from '../carousel-space-floating.vue'
type Images = InstanceType<typeof CarouselSpaceFloating>['images']
const images: Images = [ '/low/painting-codfish-bakery.webp', '/low/painting-codfish-rain.webp', '/low/photography-fireworks.webp', '/low/photography-morning-light-of-rice.webp', '/low/photography-big-stupid-bird.webp', '/low/photography-ears-of-rice.webp', '/low/photography-gaomei-windmill.webp', '/low/photography-spider-at-night.webp', '/low/photography-street-cat.webp',]
const carouselRef = ref<InstanceType<typeof CarouselSpaceFloating>>()
function next() { carouselRef.value?.next()}
function prev() { carouselRef.value?.prev()}
const { t } = useI18n()</script>可以微調每張圖片的姿態,包含偏移、旋轉和縮放。
配合文字,製作獨特的輪播吧。( ´ ▽ ` )ノ
<template> <div class="frame w-full flex flex-col gap-4"> <carousel-space-floating ref="carouselRef" class="h-full example-wrap bg-[#e5e5d9]" :images="images" :fog-end="10" :init-index="0" > <div class="font-wenkai relative h-full w-full tracking-widest"> <transition-group name="scale-in"> <div v-if="isTarget('painting-codfish-bakery')" class="absolute right-0 top-0 h-full w-1/3 flex-center" > <div class="text text-xs md:text-xl"> {{ t('greedyCodfish') }} </div> </div>
<div v-if="isTarget('painting-codfish-rain')" class="absolute bottom-2 right-3" > <div class="p-3 text-right text-xs text-white leading-6 md:text-sm"> {{ t('rainLine1') }}<br>{{ t('rainLine2') }} </div> </div>
<div v-if="isTarget('photography-fireworks')" class="absolute left-0 top-0 h-full w-2/5 flex-center" > <div class="text text-center text-xs md:text-base"> {{ t('fireworksLine1') }} <br><br> {{ t('fireworksLine2') }} <br><br> {{ t('fireworksLine3') }} </div> </div>
<div v-if="isTarget('photography-big-stupid-bird')" class="absolute right-0 top-0 h-full w-1/3 flex-center" > <div class="text text-center md:text-xl"> {{ t('birdLine1') }}<br>{{ t('birdLine2') }}<br>{{ t('birdLine3') }} </div> </div>
<div v-if="isTarget('photography-gaomei-windmill')" class="absolute bottom-1 right-1" > <div class="text !px-4 md:text-xl"> {{ t('gaomeiWetland') }} </div> </div>
<div v-if="isTarget('photography-spider-at-night')" class="absolute left-0 top-0 h-full w-2/5 flex-center" > <div class="text-center text-xs text-white md:text-base md:leading-10"> {{ t('spiderLine1') }} <br> {{ t('spiderLine2') }} <br><br> {{ t('spiderLine3') }} <br> {{ t('spiderLine4') }} </div> </div>
<div v-if="isTarget('photography-street-cat')" class="absolute left-0 top-0 h-full w-1/2 flex-center" > <div class="text-center text-xs text-white leading-7 md:text-base"> {{ t('catLine1') }} <br> {{ t('catLine2') }} </div> </div> </transition-group> </div> </carousel-space-floating>
<div class="flex gap-4"> <base-btn :label="t('prev')" class="flex-1" @click="prev()" /> <base-btn :label="t('next')" class="flex-1" @click="next()" /> </div> </div></template>
<script setup lang="ts">import type { ExtractComponentProps } from '../../../types/utils.type'import { Vector2, Vector3 } from '@babylonjs/core'import { addProp, map } from 'remeda'import { ref } from 'vue'import { useI18n } from 'vue-i18n'
import BaseBtn from '../../base-btn.vue'import CarouselSpaceFloating from '../carousel-space-floating.vue'
type CarouselProp = ExtractComponentProps<typeof CarouselSpaceFloating>
const duration = 4000
const images: CarouselProp['images'] = map( [ { src: '/low/painting-codfish-bakery.webp', offset: new Vector2(0.5, 0), scale: 1.4, rotation: new Vector3(0, -0.08, 0), }, { src: '/low/painting-codfish-rain.webp', scale: 1.6, rotation: new Vector3(-0.05, 0, 0), }, { src: '/low/photography-fireworks.webp', offset: new Vector2(-0.7, 0), scale: 1.5, }, { src: '/low/photography-gaomei-windmill.webp', scale: 2, }, { src: '/low/photography-big-stupid-bird.webp', offset: new Vector2(0.5, 0), scale: 2, rotation: new Vector3(0, -0.7, 0), }, { src: '/low/photography-spider-at-night.webp', scale: 2.26, }, { src: '/low/photography-street-cat.webp', scale: 2, }, ], addProp('duration', duration),)
const carouselRef = ref<InstanceType<typeof CarouselSpaceFloating>>()
function isTarget(name: string) { const target = carouselRef.value?.stableTarget if (!target) return
if (typeof target === 'string') { return target.includes(name) }
return target.src.includes(name)}
function next() { carouselRef.value?.next()}
function prev() { carouselRef.value?.prev()}
const { t } = useI18n()</script>
<style scoped lang="sass">.frame aspect-ratio: 1.78
.font-wenkai color: #222 text-shadow: 0 0 10px rgba(#111, 0.2)
.text padding: 0.25rem background: rgba(white, 0.8) backdrop-filter: blur(2px)
.scale-in-enter-active, .scale-in-leave-active transition-duration: 0.4s.scale-in-enter-from, .scale-in-leave-to transform: scale(0.98) !important opacity: 0 !important</style>使用 babylon 建立 3D 場景,將所有圖片放置在空間中,再依目標圖片的位置移動鏡頭至其前方。
interface Props { initIndex?: number; images?: Array<ImageInfo | string>; fpsVisible?: boolean;
/** 背景色。無限遠處的顏色 */ clearColor?: Color4; /** 霧化色。足夠遠處的照片會被此顏色籠罩 */ fogColor?: Color3; /** 霧化終點距離。此距離以後的物體表面會完全不可見 */ fogEnd?: number;}Methods
Section titled “Methods”interface Expose { /** 目標 index */ targetIndex: Ref<number>; /** 鏡頭穩定時,注視的目標。還沒穩定時為 undefined */ stableTarget: ComputedRef<ImageInfo | string | undefined>; /** 下一張圖片 */ next: () => void; /** 上一張圖片 */ prev: () => void;}defineSlots<{ default?: (data: { targetIndex: number; isSwitching: boolean; stableTarget?: ImageInfo | string; }) => unknown;}>()