Lend a Cat Paw cursor
Cat paws click for you! (=①ω①=)
This component was inspired by the Japanese proverb 「猫の手も借りたい」, which means “so busy that you’d even like to borrow a cat’s paws”.
Unlike the proverb though, this component actually has a cat paw! ԅ(´∀` ԅ)
TIP
This component is designed for mouse input. It’s recommended to view it on a computer or on a device that can use a mouse.
Usage Examples
Basic Usage
The cat paw follows the mouse and changes based on its interaction with elements.
View example source code
<template>
<div class="w-full flex flex-col gap-4 rounded-xl py-10">
<div class="flex flex-col select-none items-center justify-center gap-[6vh]">
<base-checkbox
v-model="visible"
:label="t('showCursor')"
class="cursor-pointer border border-l-4 p-4"
/>
<div class="card bg-pointer cursor-pointer p-4">
POINTER
</div>
<div class="card bg-not-allowed cursor-not-allowed p-4">
NOT_ALLOWED
</div>
</div>
<cursor-lend-a-paw
v-if="visible"
z-index="9999"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import BaseCheckbox from '../../base-checkbox.vue'
import CursorLendAPaw from '../cursor-lend-a-paw.vue'
interface Props {
visible?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
visible: true,
})
const visible = ref(props.visible)
const { t } = useI18n()
</script>
<style lang="sass" scoped>
.card
font-weight: 700
transition-duration: 0.3s
.bg-pointer
color: light-dark(#111, #FFF)
background-color: light-dark(#FFF, #222)
$bg-color: light-dark(#EEE, #555)
background-image: linear-gradient(45deg, $bg-color 25%, transparent 25%, transparent 75%, $bg-color 75%, $bg-color), linear-gradient(45deg, $bg-color 25%, transparent 25%, transparent 75%, $bg-color 75%, $bg-color)
background-size: 10px 10px
background-position: 0 0, 5px 5px
.bg-not-allowed
color: light-dark(#7f1d1d, #e83a3a)
background: repeating-linear-gradient(135deg, light-dark(#ffadad, #6b2828) 0 10px, light-dark(#FFF, #222) 10px 20px)
</style>How It Works
Use Zdog to draw the cat paw, and JavaScript animations to simulate the paw’s movements.
Postscript
At first I was struggling with how to draw the cat paw: I couldn’t find a suitable 3D model, drawing it myself would be a hassle; SVG can’t really simulate 3D motion; DOM shapes are too rigid and get complicated easily.
I just couldn’t find a drawing method that matched what I had in mind, so the component ended up being put on hold for a long time. ( ˘・з・)
Until I accidentally discovered Zdog, this super cool library, and thanks to it this component finally came to life. ✧⁑。٩(ˊᗜˋ*)و✧⁕。
2025/07/16
Thanks to great suggestions from the community (Ian Ng, 天地毛毛, JimYe), a paw-print effect was added after clicking, which doubled the cuteness (*´∀`)~♥
2025/07/17
Thanks to a great suggestion from kevindawnsu, the mouse cursor was turned into a laser pointer, which really was the finishing touch (ゝ∀・)b
Fading Out the Paw Print
Leaving a paw print after clicking isn’t hard; the logic is similar to how the cat paw is drawn. But when it came to implementing the fade-out effect, I ran into a problem.
Zdog’s drawing doesn’t support directly modifying opacity, so what now? (。-`ω´-)
I’ll keep you in suspense a bit—if you’re curious, you can check out the source code and see if you can guess how it’s done. ヽ(●`∀´●)ノ
Turning the Cursor into a Laser Dot
I originally thought hiding the cursor wouldn’t be hard—just use cursor: none in CSS.
Naturally, something unexpected had to happen (´;ω;`)
If you directly set cursor: none on body, then all child elements will end up with cursor: none as well, which means you can no longer determine the current action by checking the hovered DOM element’s cursor style. ( ´•̥̥̥ ω •̥̥̥` )
In the end, I used a combo of dataset, IntersectionObserver, and a “hidden cursor image” to create a solution that balances both effect and performance.
The idea is:
- Set
cursor: url('transparent image'), autoonbody, and hide the cursor when the cursor value isautoordefault. - Use
document.querySelectorAllto get all elements whose cursor styles need to be handled. - For visible elements, store their cursor values in
dataset; for invisible elements, register them withIntersectionObserver.
Of course, there’s also the part about restoring the original styles, but I won’t go into detail here.
If you’re interested, you can check the source code to see the concrete implementation. ( ´ ▽ ` )ノ
Source Code
API
Props
interface Props {
/** [x, y]。@default [60, 30] */
offset?: [number, number];
/** @default 40 */
size?: number;
/** @default '#444' */
furColor?: string;
/** @default '#FFA5A5' */
padColor?: string;
/** @default '#DDD' */
padPrintColor?: string;
/** ms。@default 5000 */
maxPrintLifeTime?: number;
/** @default '#F00' */
laserColor?: string;
zIndex?: string | number;
}