Skip to content

游標小跟班

跟著游標跑的小跟班。(´ ・ω・`)ノ╰(・ิω・ิ )

TIP

此元件針對滑鼠設計,建議使用電腦或可以使用滑鼠的裝置瀏覽。

使用範例

基本用法

平時跟著游標跑,碰到特定元素會有特殊互動

我是標題
我是一段文字(嘗試反白文字看看)

可編輯的 div


鱈魚的魚缸 貪吃的鱈魚
查看範例原始碼
vue
<template>
  <div class="w-full flex flex-col gap-4 border border-gray-300 p-6">
    <cursor-sidekick v-if="enable" />

    <base-checkbox
      v-model="enable"
      label="啟用小跟班"
      class="border rounded bg-gray-100 p-4"
    />

    <div class="flex flex-col gap-2">
      <div class="text-2xl font-bold">
        我是標題
      </div>

      <div class="">
        我是一段文字(嘗試反白文字看看)
      </div>

      <hr>

      <base-input
        v-model="text"
        label="文字輸入框"
      />
      <div class="h-[20vh] w-1/2 border rounded">
        <textarea
          v-model="text"
          placeholder="多行文字"
          class="h-full w-full p-2"
        />
      </div>

      <div
        contenteditable
        class="w-2/3 border rounded p-2"
      >
        可編輯的 div
      </div>

      <hr>

      <base-btn label="按鈕" />
      <base-btn
        disabled
        label="不可以色色 (゚Д゚*)ノ"
      />

      <hr>

      <a
        href="https://codlin.me/"
        target="_blank"
      >
        鱈魚的魚缸
      </a>

      <img
        src="/painting-codfish-bakery.webp"
        alt="貪吃的鱈魚"
      >
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import BaseBtn from '../../base-btn.vue'
import BaseCheckbox from '../../base-checkbox.vue'
import BaseInput from '../../base-input.vue'
import CursorSidekick from '../cursor-sidekick.vue'

const enable = ref(false)
const text = ref('')
</script>

自定義內容

自行設計 Provider,產生各種奇奇怪怪的互動吧!ლ(´∀`ლ)

嘗試反白這段文字中有鱈魚的部分和沒有鱈魚的部分,看看有甚麼差別。


查看範例原始碼
vue
<template>
  <div class="w-full flex flex-col gap-4 border border-gray-300 p-6">
    <cursor-sidekick
      v-if="enable"
      color="#35abf0"
      :hover-providers="hoverProviders"
      :select-providers="selectProviders"
    />

    <base-checkbox
      v-model="enable"
      label="啟用小跟班"
      class="border rounded bg-gray-100 p-4"
    />

    <div class="flex flex-col gap-2">
      <div class="">
        嘗試反白這段文字中有鱈魚的部分和沒有鱈魚的部分,看看有甚麼差別。
      </div>

      <hr>

      <base-btn
        disabled
        label="不可以色色 ('◉◞⊖◟◉` )"
      />

      <hr>

      <img
        src="/photography-ears-of-rice.jpg"
        url="https://www.flickr.com/photos/coodfish/albums/"
      >
    </div>
  </div>
</template>

<script setup lang="ts">
import type { ContentProvider } from '../use-content-provider'
import { ref } from 'vue'
import BaseBtn from '../../base-btn.vue'
import BaseCheckbox from '../../base-checkbox.vue'
import CursorSidekick from '../cursor-sidekick.vue'

const enable = ref(false)

const hoverProviders: ContentProvider[] = [
  // hover 含有色色文字的按鈕時,提供色色傳送門
  {
    match(data) {
      if ('rect' in data)
        return false

      if (
        !(data instanceof HTMLButtonElement)
        && data?.getAttribute('role') !== 'button'
      ) {
        return false
      }

      return data.innerHTML.includes('色色')
    },
    getContent: () => ({
      btnList: [
        {
          label: '色色傳送門 (´,,•ω•,,)',
          onClick() {
            window.open(
              'https://raw.githubusercontent.com/tpai/dogedeck/main/cards/%E6%8A%97%E8%89%B2%E8%89%B2%E8%97%A5.png',
              '_blank',
            )
          },
        },
      ],
    }),
  },

  // 當圖片含有 url attr 時,提供開啟連結按鈕
  {
    match(data) {
      if ('rect' in data)
        return false

      if (data instanceof HTMLImageElement) {
        return true
      }

      return false
    },
    getContent(param) {
      const { element } = param
      const target = element?.value

      if (!(target instanceof HTMLImageElement))
        return

      const url = target.getAttribute('url')
      if (!url)
        return

      return {
        btnList: [
          {
            label: '📷 查看更多精彩照片',
            onClick() {
              window.open(url ?? '', '_blank')
            },
          },
        ],
      }
    },
  },
]

const selectProviders: ContentProvider[] = [
  // 選取文字包含鱈魚時,提供額外選單
  {
    match(data) {
      if (!('rect' in data))
        return false

      return data.text.includes('鱈魚')
    },
    getContent: () => ({
      text: '被你發現惹 ᕕ( ゚ ∀。)ᕗ<br>歡迎來以下連結逛逛',
      class: ' text-nowrap ',
      btnList: [
        {
          label: '🎬 Youtube',
          onClick() {
            window.open('https://www.youtube.com/@codfish2140', '_blank')
          },
        },
        {
          label: '💡 CodePen',
          onClick() {
            window.open('https://codepen.io/Codfish2140', '_blank')
          },
        },
        {
          label: '✏️ 鱈魚的魚缸',
          onClick() {
            window.open('https://codlin.me/', '_blank')
          },
        },
      ],
    }),
  },
]
</script>

原理

這個小廢物元件應該是使用最多種瀏覽器 API 的元件了,以下是 API 與其應用範圍:

小跟班的變形動畫是靠老朋友 anime.js 實現。

原始碼

API

Props

interface Props {
  /** 單位 px */
  size?: number;
  /** \# 前綴之 HEX 格式
   * @default '#515151'
   */
  color?: string;
  /** 最大速度。越慢小跟班越悠哉。單位 px/ms
   * @default 1
   */
  maxVelocity?: number;
  /**  @default 100 */
  zIndex?: number;

  /** 匹配 active element 的 provider。
   *
   * 通常用於可點擊或 focus 的元素。
   */
  activeProviders?: ContentProvider[];

  /** 匹配 hover element 的 provider
   *
   * 只要 hover 到符合條件的元素,即會觸發。
   */
  hoverProviders?: ContentProvider[];

  /** 匹配選取文字的 provider  */
  selectProviders?: ContentProvider[];
}

ContentProvider 定義如下:

interface BtnOption {
  label: string;
  onClick: (event: Event) => void;
}

interface Content {
  /** 用於調整內容樣式 */
  class?: string;
  text?: string;
  /** 按鈕清單 */
  btnList?: BtnOption[];
}

export interface ContentProvider {
  /** 判斷目前元素或文字是否符合 */
  match: (
    data: HTMLElement | SelectionState
  ) => boolean;
  /** 取得小跟班顯示用內容 */
  getContent: (
    param: TargetParam
  ) => Content | undefined;
}

v0.23.1