Skip to content

解碼輸入框 input

輸入文字時會有酷酷的解碼效果。(⌐■_■)✧

使用範例

基本用法

不管是輸入還是貼上,都會有解碼效果,而且完全不會干擾輸入過程。

查看範例原始碼
vue
<template>
  <div class="w-full flex flex-col items-center gap-4 border border-gray-300 p-6">
    <input-decoding
      v-model="text"
      class="input-decoding px-3 py-2"
    />

    <span class="break-all border-b px-3 pb-1">
      {{ text }}
    </span>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import InputDecoding from '../input-decoding.vue'

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

<style scoped lang="sass">
.input-decoding
  border: 1px solid #ccc
  border-inline: 2px solid #AAA
</style>

自定義解碼表

自定義解碼表,玩出更多變化。

查看範例原始碼
vue
<template>
  <div class="w-full flex flex-col items-center gap-4 border border-gray-300 p-6">
    <input-decoding
      v-model="text"
      class="input-decoding px-3 py-2"
      :charset
      :decode-interval="40"
    />

    <span class="break-all border-b px-3 pb-1">
      {{ text }}
    </span>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import InputDecoding from '../input-decoding.vue'

const text = ref('')

const charset = [
  /** 數字 */
  (char: string) => char.match(/\d/)
    ? '¥$¢€£'
    : undefined,

  /** 中文 */
  (char: string) => char.match(/[\u4E00-\u9FA5]/)
    ? 'ㄅㄆㄇㄈㄉㄊㄋㄌㄍㄎㄏㄐㄑㄒㄓㄔㄕㄖㄗㄘㄙㄚㄛㄜㄝㄞㄟㄠㄡㄢㄣㄤㄥㄦ'
    : undefined,

  /** emoji */
  (char: string) => char.match(/\p{Emoji}/u)
    ? '♩♪♫♬𝄞'
    : undefined,

  /** 其他 */
  () => 'ᚠᚢᚦᚨᚱᚳᚷᚹᚻᚾᛁᛂᛇᛈᛉᛊᛏᛒᛖᛗᛚᛝᛟᛡᛢᛣᛤᛥᛦᛧᛨ',
]
</script>

<style scoped lang="sass">
.input-decoding
  border: 1px solid #ccc
  border-inline: 2px solid #AAA
</style>

科幻輸入框

配合科幻卡片元件,打造未來感十足的輸入框。

codlin
查看範例原始碼
vue
<template>
  <div class="w-full flex flex-col items-center gap-4 border border-gray-300 p-6">
    <div class="flex flex-col gap-4 border rounded">
      <base-checkbox
        v-model="visible"
        label="顯示輸入框"
        class="p-4"
      />
    </div>

    <div class="flex flex-col items-center gap-4 py-6">
      <card-futuristic
        :visible
        v-bind="cardParams"
      >
        <input-decoding
          v-model="text"
          class="font-orbitron px-3 py-2"
          :class="{ 'pointer-events-none': !visible }"
          charset="ᚠᚢᚦᚨᚱᚳᚷᚹᚻᚾᛁᛂᛇᛈᛉᛊᛏᛒᛖᛗᛚᛝᛟᛡᛢᛣᛤᛥᛦᛧᛨ"
          @focus="cardParams.selected = true"
          @blur="cardParams.selected = false"
        />
      </card-futuristic>

      <div class="font-orbitron break-all">
        {{ text }}
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import type { Writable } from 'type-fest'
import type { ExtractComponentProps } from '../../../types'
import { ref } from 'vue'
import BaseCheckbox from '../../base-checkbox.vue'
import CardFuturistic from '../../card-futuristic/card-futuristic.vue'
import InputDecoding from '../input-decoding.vue'

type CardParams = Writable<ExtractComponentProps<typeof CardFuturistic>>

const visible = ref(false)
const text = ref('codlin')

const cardParams = ref<CardParams>({
  selected: false,
  corner: {
    type: 'square',
    size: 4,
  },
  bg: null,
  content: {
    type: 'typical',
    class: 'p-1',
  },
  border: {
    type: 'typical',
    color: '#BBB',
  },
  animeSequence: {
    visible: {
      corner: { delay: 0 },
      border: { delay: 400 },
      content: { delay: 500 },
    },
    hidden: {
      corner: { delay: 400 },
      border: { delay: 0 },
      content: { delay: 0 },
    },
  },
})
</script>

<style lang="sass">
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+TC:[email protected]&family=Orbitron:[email protected]&family=Oxanium:[email protected]&display=swap')

.font-orbitron
  font-family: "Orbitron", sans-serif
</style>

原理

透過 input 輸入事件偵測每個輸入文字,轉換每個字元為動態解碼效果。

中文拼字怎麼處理?

開發這個元件讓我長了新知識,就是 inputcompositionstartcompositionend 事件,可以確保中文正常輸入,拼字的時候不會被誤判成多次輸入。

輸入時,每個字元會被轉換為 useChar 產生的物件,根據設定產生隨機變化的效果,使字元看起來像是被「解碼」一樣逐漸顯示。

說起來簡單,實際上還要考慮各種輸入情境,包括:

  1. 輸入、貼上、拖動文字

    都是插入文字,但是有些微差別,需要分別在 onInputonBeforeInput 處理。

  2. 選取

    第 1 點的操作加上選取,需特別注意 caret 位置或隱含的刪除行為。

  3. 刪除

    有分 ForwardBackward 兩個方向。

  4. 維持插入點(caret)

    input 內容變化時,插入點位置會被重置到最尾端,所以解碼動畫播放時,也要不斷維持插入點位置。

  5. 拼字與插入點(caret)

    中文拼字時,onInputonBeforeInput 中的 input.selectionStartinput.selectionEnd 與英文輸入不同,需要特別處理。

  6. emoji

    1 個 emoji 為 2 字元,需要謹慎處理。

已知 Bug

若文字內有 emoji,反白後編輯與段落中插入文字,都會有錯位問題。

主因是因為 emoji 會讓 selectionStartselectionEnd 與實際文字長度不同,

目前還想不到甚麼好辦法。( ´•̥̥̥ ω •̥̥̥` )

原始碼

API

Props

interface Props {
  modelValue?: string;

  /** 解碼效果的字元集
   *
   * 可以根據 char 來決定字元集,依矩陣順序判斷
   *
   * @default 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
   */
  charset?: string | Array<(char: string) => string | undefined>;

  /** 字符變化間隔毫秒數
   *
   * @default 20
   */
  decodeInterval?: number;

  /** 解碼次數
   *
   * @default 10
   */
  decodeTimes?: number;

  /** 每個字符的延遲毫秒數
   *
   * @default 20
   */
  perCharDecodeDelay?: number;

  /** 最大延遲毫秒數
   *
   * @default 1000
   */
  maxDecodeDelay?: number;
}

Emits

interface Emits {
  'update:modelValue': [value: Props['modelValue']];
}

Slots

interface Slots {
  default?: () => unknown;
}

v0.32.3