Skip to content

Text Hates Mouse text

Text that runs away when your mouse gets close。

TIP

This component is designed for mouse interaction. It's best viewed on a desktop or a device with a mouse.

Usage Examples

Basic Usage

When the mouse approaches, the text will move away from it。

Musophobia refers to an extreme fear or aversion to mice, also known as Muriphobia. This fear may stem from childhood experiences, cultural influences, social concepts, or associations with diseases and dirty environments that mice might bring.

Check out the source code
vue
<template>
  <div class="w-full border border-gray-300 p-6">
    <text-hate-mouse
      :text="t('musophobiaDescription')"
    />
  </div>
</template>

<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import TextHateMouse from '../text-hate-mouse.vue'

const { t } = useI18n()
</script>

Different Combinations

Different parameters create different effects。

ScaredScaredScaredScaredScaredScaredScaredScaredScaredScaredScared

Go AwayGo AwayGo AwayGo AwayGo AwayGo AwayGo AwayGo AwayGo AwayGo Away

🀀🀁🀂🀃🀄🀅🀆🀇🀈🀉🀊🀋🀌🀍🀎🀏🀐🀑🀒🀓🀔🀕🀖🀗🀘🀖🀗🀘🀙🀚🀛🀜🀝🀞🀟🀠🀡🀢🀣🀤🀥🀦🀧🀨🀩🀪🀫

Check out the source code
vue
<template>
  <div class="w-full flex flex-col gap-8 border border-gray-300 p-6 text-center">
    <text-hate-mouse
      v-for="item, i in list"
      :key="i"
      v-bind="item"
    />
  </div>
</template>

<script setup lang="ts">
import type { ComponentProps } from 'vue-component-type-helpers'
import { times } from 'remeda'
import { useI18n } from 'vue-i18n'
import TextHateMouse from '../text-hate-mouse.vue'

type Props = ComponentProps<typeof TextHateMouse> & {
  class?: string;
}

const { t } = useI18n()

const list: Props[] = [
  {
    text: t('scaredText'),
    stiffness: 0.2,
    damping: 0,
    evasionRadius: 30,
  },
  {
    text: times(10, () => t('goAway')),
    evasionRadius: 200,
  },
  {
    text: [
      '🀀🀁🀂🀃🀄🀅🀆🀇🀈🀉🀊🀋🀌🀍🀎🀏',
      '🀐🀑🀒🀓🀔🀕🀖🀗🀘🀖🀗🀘',
      '🀙🀚🀛🀜🀝🀞🀟🀠🀡',
      '🀢🀣🀤🀥🀦🀧🀨🀩🀪🀫',

    ].join(''),
    stiffness: 0.002,
    damping: 1,
    class: 'text-2xl',
  },
]
</script>

Custom Props

Create some fun effects! ԅ(´∀` ԅ)

Doraemon Doraemon Doraemon

Check out the source code
vue
<template>
  <div class="w-full flex flex-col gap-4 border border-gray-300 p-6 text-center">
    <div class="flex flex-col gap-4">
      <base-input
        v-model="params.text"
        :label="t('customText')"
        type="text"
        class="w-full text-left"
      />

      <base-input
        v-model.number="params.evasionRadius"
        :label="t('evasionRadiusLabel', { value: params.evasionRadius })"
        type="range"
        class="w-full text-left"
        :max="300"
        :step="1"
        :min="20"
      />

      <base-input
        v-model.number="params.stiffness"
        :label="t('stiffnessLabel', { value: params.stiffness })"
        type="range"
        class="w-full text-left"
        :max="0.3"
        :step="0.001"
        :min="0.001"
      />

      <base-input
        v-model.number="params.damping"
        :label="t('dampingLabel', { value: params.damping })"
        type="range"
        class="w-full text-left"
        :max="1"
        :step="0.01"
        :min="0"
      />
    </div>

    <div class="border p-4">
      <text-hate-mouse v-bind="params" />
    </div>
  </div>
</template>

<script setup lang="ts">
import type { ComponentProps } from 'vue-component-type-helpers'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import BaseInput from '../../base-input.vue'
import TextHateMouse from '../text-hate-mouse.vue'

type Props = ComponentProps<typeof TextHateMouse>

const { t } = useI18n()

const params = ref({
  text: t('defaultText'),
  evasionRadius: 40,
  stiffness: 0.01,
  damping: 0.05,
} satisfies Props)
</script>

Polite Quotation

When the budget is too low, gently hint at the client。

Please select your budget range:

Contact Phone: 0987-654-321

E-mail: hi@codlin.me

Check out the source code
vue
<template>
  <div class="w-full flex flex-col gap-8 border border-gray-300 p-6 text-center">
    <!-- 預算 radio -->
    <div class="flex flex-col items-start gap-4 border border-gray-300 rounded-xl p-6">
      <div class="font-bold">
        {{ t('budgetPrompt') }}
      </div>

      <div class="w-full flex flex-wrap justify-between gap-4 whitespace-nowrap">
        <label
          v-for="item, i in budgetList"
          :key="i"
        >
          <input
            v-model="budget"
            type="radio"
            :value="i"
          >
          {{ item.text }}
        </label>
      </div>
    </div>

    <div class="flex flex-col gap-1 p-6">
      <text-hate-mouse
        v-for="item, i in list"
        :key="i"
        v-bind="item"
        :evasion-radius
        class="!m-0"
        :class="{ 'select-none': evasionRadius > 0 }"
      />
    </div>
  </div>
</template>

<script setup lang="ts">
import type { ComponentProps } from 'vue-component-type-helpers'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import TextHateMouse from '../text-hate-mouse.vue'

type Props = ComponentProps<typeof TextHateMouse> & {
  class?: string;
}

const { t } = useI18n()

const budget = ref(-1)
const budgetList = [
  { text: t('budget.under10k') },
  { text: t('budget.10kTo50k') },
  { text: t('budget.50kTo100k') },
  { text: t('budget.over100k') },
]

const evasionRadius = computed(() => budget.value === 0 ? 50 : 0)

const list: Props[] = [
  { text: t('contact.phone') },
  { text: t('contact.email') },
]
</script>

How it Works

It uses Matter.js for physics simulation effects。

Basically, it's a mix of Text Characters Transition and Physics Wrapper.

Source Code

API

Props

interface Props {
  /** string[] 表示提供已分割好的文字 */
  text: string | string[];

  /** html tag
   *
   * @default 'p'
   */
  tag?: string;

  /** 如何切割文字
   *
   * 只有在 text 為 string 時有效
   *
   * @default /.*?/u
   */
  splitter?: RegExp | ((text: string) => string[]);

  /** 閃避半徑 */
  evasionRadius?: number;

  /** 約束力
   *
   * 表示回復原味的力量,1 表示最強,0.2 則類似軟彈簧作用
   */
  stiffness?: number;

  /** 阻尼
   *
   * 0.1 表示強烈阻尼,幾乎不會有任何震盪,0 表示沒有任何阻尼
   */
  damping?: number;
}

v0.38.10