Skip to content

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.

POINTER
NOT_ALLOWED
View example source code
vue
<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:

  1. Set cursor: url('transparent image'), auto on body, and hide the cursor when the cursor value is auto or default.
  2. Use document.querySelectorAll to get all elements whose cursor styles need to be handled.
  3. For visible elements, store their cursor values in dataset; for invisible elements, register them with IntersectionObserver.

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;
}

v0.50.2