立體包裝器 wrapper
可以讓元素有酷酷的 3D 偏轉效果。ˋ( ° ▽、° )
使用範例
基本用法
可以隨手關閉,不過沒辦法省電。乁( ◔ ௰◔)「
安安
漂起來惹
酷酷的漂浮
安安
查看範例原始碼
vue
<template>
<div class="w-full flex flex-col gap-4 border border-gray-300 p-6">
<div class="flex gap-4 border rounded p-4">
<base-checkbox
v-model="enable"
label="懸浮開關"
class="w-full"
/>
</div>
<div class="flex flex-col items-start gap-4">
<wrapper-stereoscopic :enable>
<div class="h-80 w-80 flex-center rounded bg-gray-300">
<div class="h-40 w-40 flex-center rounded bg-gray-100">
<div class="text-xl text-gray-600 font-bold">
安安
</div>
</div>
</div>
</wrapper-stereoscopic>
<div class="flex flex-col items-start justify-start gap-4">
<wrapper-stereoscopic :enable>
<div class="border rounded-full p-4 text-xl text-gray-600 font-bold">
漂起來惹
</div>
</wrapper-stereoscopic>
<wrapper-stereoscopic :enable>
<div class="border rounded-full p-4 text-xl text-gray-600 font-bold">
酷酷的漂浮
</div>
</wrapper-stereoscopic>
<wrapper-stereoscopic :enable>
<div class="border rounded-full p-4 text-xl text-gray-600 font-bold">
安安
</div>
</wrapper-stereoscopic>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import BaseCheckbox from '../../base-checkbox.vue'
import WrapperStereoscopic from '../wrapper-stereoscopic.vue'
const enable = ref(true)
</script>
<style lang="sass" scoped>
.flex-center
display: flex
justify-content: center
align-items: center
</style>
多層視差
多層 layer 可以產生多層立體效果
安安
查看範例原始碼
vue
<template>
<div class="w-full flex flex-col gap-4 border border-gray-300 p-6">
<div class="content flex items-start gap-4">
<wrapper-stereoscopic v-slot="wrapper">
<div
class="h-80 w-80 flex-center rounded bg-gray-300"
:style="wrapper.style"
>
<wrapper-stereoscopic-layer v-slot="layer01">
<div
class="h-40 w-40 flex-center rounded bg-gray-200"
:style="layer01.style"
>
<wrapper-stereoscopic-layer v-slot="layer02">
<div
class="rounded bg-gray-100 p-4 text-xl font-bold"
:style="layer02.style"
>
安安
</div>
</wrapper-stereoscopic-layer>
</div>
</wrapper-stereoscopic-layer>
</div>
</wrapper-stereoscopic>
</div>
</div>
</template>
<script setup lang="ts">
import WrapperStereoscopicLayer from '../wrapper-stereoscopic-layer.vue'
import WrapperStereoscopic from '../wrapper-stereoscopic.vue'
</script>
<style lang="sass" scoped>
.content
perspective: 2000px
.flex-center
display: flex
justify-content: center
align-items: center
</style>
最大偏轉角
可以設定最大偏轉角度
( •̀ ω •́ )✧
查看範例原始碼
vue
<template>
<div class="w-full flex flex-col gap-4 border border-gray-300 p-6">
<div class="flex gap-4 border rounded p-4">
<base-input
v-model.number="x"
type="range"
:label="`X 最大角度: ${x} 度`"
class="w-full"
:min="0"
:max="90"
/>
<base-input
v-model.number="y"
type="range"
:label="`Y 軸最大角度: ${y} 度`"
class="w-full"
:min="0"
:max="90"
/>
</div>
<div class="content flex items-start gap-4">
<wrapper-stereoscopic
:x-max-angle="x"
:y-max-angle="y"
>
<div class="border rounded-full p-4 text-xl text-gray-600 font-bold">
( •̀ ω •́ )✧
</div>
</wrapper-stereoscopic>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import BaseInput from '../../base-input.vue'
import WrapperStereoscopic from '../wrapper-stereoscopic.vue'
const x = ref(15)
const y = ref(15)
</script>
<style lang="sass" scoped>
.content
perspective: 2000px
.flex-center
display: flex
justify-content: center
align-items: center
</style>
漂浮距離
可以設定每層之間的漂浮距離,想躺平就躺平。_(:3」ㄥ)_
安安
查看範例原始碼
vue
<template>
<div class="w-full flex flex-col gap-4 border border-gray-300 p-6">
<div class="flex gap-4 border rounded p-4">
<base-input
v-model.number="zOffset"
type="range"
:label="`懸浮距離: ${zOffset} px`"
class="w-full"
:min="0"
:max="200"
/>
</div>
<div class="content flex items-start gap-4">
<wrapper-stereoscopic
v-slot="wrapper"
:z-offset="zOffset"
>
<div
class="h-80 w-80 flex-center rounded bg-gray-300"
:style="wrapper.style"
>
<wrapper-stereoscopic-layer v-slot="layer01">
<div
class="h-40 w-40 flex-center rounded bg-gray-200"
:style="layer01.style"
>
<wrapper-stereoscopic-layer v-slot="layer02">
<div
class="rounded bg-gray-100 p-4 text-xl font-bold"
:style="layer02.style"
>
安安
</div>
</wrapper-stereoscopic-layer>
</div>
</wrapper-stereoscopic-layer>
</div>
</wrapper-stereoscopic>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import BaseInput from '../../base-input.vue'
import WrapperStereoscopicLayer from '../wrapper-stereoscopic-layer.vue'
import WrapperStereoscopic from '../wrapper-stereoscopic.vue'
const zOffset = ref(100)
</script>
<style lang="sass" scoped>
.content
perspective: 2000px
.flex-center
display: flex
justify-content: center
align-items: center
</style>
自訂策略
可以自訂旋轉、懸浮距離,做出更豐富的互動。
(´● ω ●`)
看這裡
查看範例原始碼
vue
<template>
<div class="w-full flex flex-col items-center justify-center gap-8 border border-gray-300 p-6">
<div class="content flex items-start">
<wrapper-stereoscopic
v-slot="wrapper"
v-bind="params"
>
<div
class="cursor-zoom-in select-none border rounded-full"
:style="wrapper.style"
>
<wrapper-stereoscopic-layer v-slot="layer01">
<div
class="flex-center px-14 py-8 text-2xl tracking-widest"
:style="layer01.style"
>
(´● ω ●`)
</div>
</wrapper-stereoscopic-layer>
</div>
</wrapper-stereoscopic>
</div>
<div
ref="blockRef"
class="w-full cursor-zoom-in border rounded border-dashed p-2 text-center text-xs tracking-widest"
>
看這裡
</div>
</div>
</template>
<script setup lang="ts">
import type { ExtractComponentProps } from '../../../types'
import { useElementHover } from '@vueuse/core'
import { ref } from 'vue'
import { mapNumber } from '../../../common/utils'
import WrapperStereoscopicLayer from '../wrapper-stereoscopic-layer.vue'
import WrapperStereoscopic from '../wrapper-stereoscopic.vue'
type Props = ExtractComponentProps<typeof WrapperStereoscopic>
const blockRef = ref()
const isHovered = useElementHover(blockRef)
const params: Props = {
strategy(params) {
const {
mousePosition: { x, y },
size: { width, height },
} = params
if (isHovered.value) {
return {
x: mapNumber(y, -height, height, -50, 50),
y: mapNumber(x, -width, width, -60, 60),
zOffset: -100,
}
}
if (
params.isOutside
|| !params.enable
|| !params.isVisible
|| params.isPressed) {
return {
x: 0,
y: 0,
zOffset: 0,
}
}
return {
x: mapNumber(y, -height, height, -30, 30),
y: mapNumber(x, -width, width, -40, 40),
zOffset: 100,
}
},
}
</script>
<style lang="sass" scoped>
.content
perspective: 2000px
</style>
原理
利用 CSS 的 perspective 與 transform3d,產生 3D 旋轉與透視變形效果。
其中 perspective 尤為重要,此屬性負責讓物體產生透視變形效果。
沒設定的話,物體看起來像莫名其妙扁掉。...('◉◞⊖◟◉` )
知道如何偏轉後,剩下的部分就簡單惹。( •̀ ω •́ )✧
計算從物體中心到滑鼠位置的向量,分別將向量的 x、y 分量映射到設定的角度範圍,最後套用到 transform 上即可。
不過這裡有個小技巧,我們不把「目前角度」直接設為「目標角度」,而是逐漸趨近「目標角度」。
這樣無論「目標角度」怎麼亂跳,都可以保證偏轉效果都有動畫呈現,看起來更自然、舒服。◝(≧∀≦)◟
原始碼
API
Props
interface StrategyParams {
enable: boolean;
xMaxAngle: number;
yMaxAngle: number;
zOffset: number;
/** 以元素中心為零點,目前滑鼠的座標 */
mousePosition: Record<'x' | 'y', number>;
/** 元素尺寸 */
size: Record<'width' | 'height', number>;
/** 滑鼠是否在元素外 */
isOutside: boolean;
/** 元素是否可見 */
isVisible: boolean;
/** 是否被按下 */
isPressed: boolean;
}
interface Props {
/** 是否開啟 */
enable?: boolean;
/** x 最大偏轉角度 */
xMaxAngle?: number;
/** y 最大偏轉角度 */
yMaxAngle?: number;
/** 懸浮高度 */
zOffset?: number;
/** 旋轉、懸浮距離邏輯 */
strategy?: (params: StrategyParams) => Record<'x' | 'y' | 'zOffset', number>;
/** 更新週期,越短會越快到達目標狀態
*
* @default 15
*/
updateInterval?: number;
}