歪像按鈕 button
需要將按鈕旋轉至某個角度,才能正常顯示,可以有效防堵人類 ( •̀ ω •́ )✧
路人:「防機器人才對吧!(╯°Д°)╯︵ ┻━┻」
技術關鍵字
| 名稱 | 描述 |
|---|---|
| Pointer 事件 | 偵測滑鼠或觸控點移動、點擊、懸停等等事件,取得座標、目標等等資訊 |
| JS 動畫 | 基於 JavaScript 實現的動畫,達成更複雜、精準的動畫控制,常見套件有 GSAP、anime.js 等 |
| CSS 3D | 使用 CSS transform 和 perspective 實現 3D 效果 |
使用範例
基本用法
將按鈕轉回原樣吧!◝( •ω• )◟
查看範例原始碼
vue
<template>
<div class="w-full flex flex-col gap-4 border border-gray-200 rounded-xl p-6">
<div class="flex justify-center">
<btn-anamorphosis
:label="t('按鈕')"
@click="handleClick"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import BtnAnamorphosis from '../btn-anamorphosis.vue'
const { t } = useI18n()
function handleClick() {
// eslint-disable-next-line no-alert
alert(t('點擊成功'))
}
</script>取消訂閱
避免用戶落跑,避免用戶誤觸的貼心設計。◝( •ω• )◟
感謝您訂閱超級會員!
每月將自動扣款 $999
如果您真的不想再支持我們了,您可以嘗試點擊這裡...
(視角轉正才能拼起來點擊哦)
查看範例原始碼
vue
<template>
<div
class="relative w-full flex flex-col items-center justify-center gap-6 overflow-hidden border border-gray-200 rounded-xl p-6"
>
<div class="text-center">
<p class="mb-2 text-2xl font-bold">
{{ t('感謝您訂閱超級會員!') }}
</p>
<p class="">
{{ t('每月將自動扣款 $999') }}
</p>
</div>
<button
class="transform rounded-full from-green-400 to-green-600 bg-gradient-to-r px-10 py-4 text-lg text-white font-bold shadow-lg transition active:scale-95 hover:scale-105 hover:from-green-500 hover:to-green-700"
@click="handleUpgrade"
>
🌟 {{ t('升級為終身會員 ($9999)') }} 🌟
</button>
<div class="mt-3 flex flex-col items-center gap-4 text-center">
<p class="text-xs text-gray-400">
{{ t('如果您真的不想再支持我們了,您可以嘗試點擊這裡...') }}<br>
<span class="text-[10px] text-gray-400">
({{ t('視角轉正才能點擊哦') }})
</span>
</p>
<btn-anamorphosis
ref="btnRef"
@click="handleUnsubscribe"
>
<button
class="rounded bg-gray-100 px-3 py-1 text-xs text-gray-400 underline transition-colors hover:bg-gray-200 hover:text-gray-600"
>
{{ t('取消訂閱') }}
</button>
</btn-anamorphosis>
</div>
<transition name="opacity">
<div
v-if="isSubmitted"
class="absolute inset-0 z-[40] flex flex-col items-center justify-center gap-6 rounded-xl bg-slate-800 bg-opacity-95 text-white"
@click="reset"
>
<span class="text-xl font-bold tracking-wide">
{{ t('退訂成功') }}
</span>
<span class="cursor-pointer text-xs text-gray-400 transition-colors hover:text-white">
{{ t('點一下再來一次') }}
</span>
</div>
</transition>
</div>
</template>
<script setup lang="ts">
import { ref, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'
import BtnAnamorphosis from '../btn-anamorphosis.vue'
const { t } = useI18n()
const btnRef = useTemplateRef('btnRef')
const isSubmitted = ref(false)
function handleUnsubscribe() {
isSubmitted.value = true
}
function handleUpgrade() {
open('https://codlin.me', '_blank')
}
function reset() {
isSubmitted.value = false
btnRef.value?.init()
}
</script>
<style lang="sass" scoped>
.opacity-enter-active, .opacity-leave-active
transition-duration: 0.4s
.opacity-enter-from, .opacity-leave-to
opacity: 0 !important
</style>原理
核心概念源自視覺歪像(Anamorphosis),透過將完整的畫面打碎並在 3D 空間中錯位排列,只有在特定的觀察角度下才能看到原本完整的模樣。實作細節如下:
多邊形切片與錯位:
- 使用 CSS
clip-path將按鈕隨機切割成多個不規則的多邊形切片(透過rowCount與colCount調整行列數量)。 - 透過 CSS 3D 的
transform: translateZ將這些切片在 Z 軸上前後錯開分布(透過zSpread調整深度)。在非正面的角度觀看時,切片之間會因為視差而支離破碎。◝( •ω• )◟
游標鎖定與視角旋轉:
- 藉由 VueUse 的
usePointerLock與useMouse,在使用者按下(pointerdown)元素時鎖定游標,並擷取游標的相對移動量(movementX、movementY)。 - 將這些移動量轉換為元件容器的 3D 旋轉角度(
rotateX、rotateY),讓使用者能親手「轉動」這個破碎的物件。
視覺干擾:
- 為了增加難度與防堵效果,除了主切片外,還複製了多組旋轉過角度的「干擾切片」。
- 隨著使用者將物件旋轉到越接近目標角度(
rotateX與rotateY趨近於 0 的倍數),這些干擾切片的透明度(noiseOpacity)會隨之降低,作為視覺回饋引導使用者找到正確的角度。
自動吸附歸位與解鎖:
- 當旋轉角度與正面差異極小(干擾切片透明度極低)時,自動解除游標鎖定,並觸發「自動對齊」(
isAligning),透過 CSS Transition 緩動強制歸零轉角。 - 對齊動畫結束後(
isDone),隱藏所有空間錯位特效與雜訊,此時底下真正的按鈕才會解除pointer-events-none狀態,讓使用者得以進行點擊等互動。
原始碼
API
Props
interface Props {
label?: string;
/** 行數 */
rowCount?: number;
/** 列數 */
colCount?: number;
/** 切片之間的 Z 軸間距 (px) */
zSpread?: number;
/** 旋轉接近歸零時,是否淡出干擾切片。設為 false 則干擾切片永遠不透明 */
noiseFadeOut?: boolean;
}Emits
const emit = defineEmits<{
click: [];
unlock: [];
}>()Methods
interface Expose {
init: () => void;
}Slots
interface Slots {
default?: () => unknown;
}