Anamorphosis Button button
You need to rotate the button to a certain angle to display it normally, which can effectively block humans ( •̀ ω •́ )✧
Passerby: "Shouldn't it block robots! (╯°Д°)╯︵ ┻━┻"
Example
Basic Usage
Rotate and restore the button!
View Example Source Code
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>Principle
The core concept comes from Visual Anamorphosis, where a complete picture is broken and misaligned in 3D space, meaning you can only see the original complete image from a specific viewing angle (i.e. the front view). The implementation details are as follows:
Polygon Slicing and Misalignment:
- CSS
clip-pathis used to randomly cut the button into multiple irregular polygon slices (rows and columns are adjustable viarowCountandcolCount). - Using the CSS 3D
transform: translateZ, these slices are distributed across the Z-axis in a staggered manner (depth adjustable viazSpread). When viewed from non-front angles, the slices look shattered due to parallax. ◝( •ω• )◟
Cursor Lock and Perspective Rotation:
- Using VueUse's
usePointerLockanduseMouse, the cursor is locked when the user presses (pointerdown) the element, and the relative movement of the cursor (movementX,movementY) is captured. - These movements are converted into the 3D rotation angles of the component container (
rotateX,rotateY), allowing users to manually "rotate" this broken object.
Visual Interference:
- To increase difficulty and blocking effect, in addition to the main slices, multiple sets of "interference slices" rotated at different angles have been duplicated.
- As the user rotates the object closer to the target angle (
rotateXandrotateYapproach a multiple of 0), the opacity (noiseOpacity) of these interference slices decreases correspondingly, serving as visual feedback to guide users to the correct angle.
Auto-snap and Unlock:
- When the difference between the current angle and the front view is extremely small (interference slice opacity is extremely low), the cursor lock is automatically released, triggering the "auto-align" (
isAligning), forcing the rotation angle to 0 smoothly via CSS Transition. - Once the alignment animation finishes (
isDone), all spatial misalignment effects and noise are hidden. Only then does the true underlying button lose thepointer-events-nonestate, allowing users to interact with it, such as clicking.
Source Code
API
Props
interface Props {
label?: string;
/** 行數 */
rowCount?: number;
/** 列數 */
colCount?: number;
/** 切片之間的 Z 軸間距 (px) */
zSpread?: number;
}Emits
const emit = defineEmits<{
click: [];
unlock: [];
}>()Methods
interface Expose {
init: () => void;
}Slots
interface Slots {
default?: () => unknown;
}