Roachie Wrapper wrapper
Two antennae peek out from under the wrapped element... ( ・ิω・ิ)
Usage Examples
Basic Usage
Get close and they'll hide.
View example source code
<template>
<div class="flex flex-col items-center gap-8 py-10">
<wrapper-roachie>
<base-btn
class="!border-orange-300 !rounded-2xl !from-orange-400 !to-red-500 !bg-gradient-to-br !px-14 !py-6 !text-white !shadow-lg !shadow-orange-300/40 dark:!border-orange-500 dark:!shadow-orange-900/40"
@click="handleBuy"
>
<span class="flex items-center gap-2 text-lg font-bold tracking-wide">
立即購買!
</span>
</base-btn>
</wrapper-roachie>
</div>
</template>
<script setup lang="ts">
import BaseBtn from '../../base-btn.vue'
import WrapperRoachie from '../wrapper-roachie.vue'
function handleBuy() {
window.open('https://codlin.me', '_blank')
}
</script>How It Works
useElementBounding tracks the element's bounding box, and useMouse watches the cursor position.
On every mouse move, Math.atan2 calculates the angle from the element center → cursor, and the antennae emerge from the opposite direction (angle + π). A ray-rectangle intersection formula then finds the exact point on the element's edge. CSS rotate handles the direction and translateY slides the antenna in and out.
Distance from cursor to element border:
dx = max(left - mouseX, 0, mouseX - right)
dy = max(top - mouseY, 0, mouseY - bottom)
distance = sqrt(dx² + dy²)When distance drops below hideDistance, the antenna snaps back; when the cursor moves away, it slides out again with a smooth animation.
Antenna Physics
At first the antennae were just two straight lines oscillating with a sine wave.
They looked like two iron rods swinging back and forth — nothing like a living creature (´・ω・`)
After some research, a double pendulum turned out to be a much better fit. The root segment actively oscillates, and the tip segment follows passively with physical inertia, giving the tip a natural delay and swing.
Codlin: "It actually looks like cockroach antennae now! ◝( •ω• )◟"
Passerby: "Did you really spend this much effort on this? ╭(°A ,°`)╮"
The root segment uses two sine waves at different frequencies to create irregular motion:
θ = (sin(t × f₁ + φ₁) × A₁ + sin(t × f₂ + φ₂) × A₂) × ampMultThe two frequencies (1.4 and 3.1 rad/s) are not integer multiples of each other, so no repeating cycle appears — it looks genuinely alive rather than looping (`・ω・´)
The tip segment is a damped driven pendulum, driven by the root segment's angular acceleration (θ₁''):
φ'' = -ωₙ² φ - 2ζωₙ φ' - θ₁''φis the tip's deflection angle relative to the root;φ = 0means both segments are collinear (straight)ωₙ = 2: natural frequency — higher means a stiffer, faster-reacting tipζ = 0.45: damping ratio — less than 1 is underdamped, so the tip overshoots and gradually settles
Velocity and angle are updated each frame with Euler integration:
φ' += φ'' × dt
φ += φ' × dtSource Code
API
Props
interface Props {
/** 觸發觸鬚縮回的距離,單位 px。@default 150 */
hideDistance?: number;
}Slots
interface Slots {
default?: () => unknown;
}