Skip to content
Welcome to vote for your favorite component! You can also tell me anything you want to say! (*´∀`)~♥

Decoding Input input

A cool decoding effect when typing text. (⌐■_■)✧

Usage Examples

Basic Usage

Whether typing or pasting, you get a decoding effect without interfering with the input process.

View example source code
vue
<template>
  <div class="w-full flex flex-col items-center gap-4 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>

Custom Charset

Customize the decoding charset for more variations.

View example source code
vue
<template>
  <div class="w-full flex flex-col items-center gap-4 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>

Futuristic Input

Combine with the Futuristic Card component to create a futuristic input field.

codlin
View example source code
vue
<template>
  <div class="w-full flex flex-col items-center gap-4 border border-gray-200 rounded-xl p-6">
    <div class="flex flex-col gap-4 border rounded">
      <base-checkbox
        v-model="visible"
        :label="t('showInput')"
        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 { useI18n } from 'vue-i18n'
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 { t } = useI18n()

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">
</style>

How It Works

Detects each input character through the input event and transforms each character into a dynamic decoding effect.

How is Chinese character composition handled?

Developing this component taught me something new: the compositionstart and compositionend events of input, which ensure Chinese input works correctly without mistaking composition as multiple inputs.

When typing, each character is converted into an object generated by useChar, which produces random variations to make characters appear as if they are being "decoded" gradually.

Sounds simple, but you also need to handle various input scenarios:

  1. Typing, pasting, dragging text

    All involve inserting text, but with subtle differences that need to be handled separately in onInput or onBeforeInput.

  2. Selection

    Combined with the operations in point 1, special attention is needed for caret position or implicit delete behavior.

  3. Deletion

    There are two directions: Forward and Backward.

  4. Maintaining the caret position

    When input content changes, the caret position resets to the end, so during the decoding animation, the caret position must be continuously maintained.

  5. Composition and caret

    During Chinese character composition, input.selectionStart and input.selectionEnd in onInput and onBeforeInput differ from English input and require special handling.

  6. Emoji

    One emoji equals 2 characters, requiring careful handling.

Known Bugs

If the text contains emoji, editing after selecting and inserting text mid-paragraph will cause offset issues.

The main reason is that emoji causes selectionStart and selectionEnd to differ from the actual text length.

No good solution has been found yet. ( ´•̥̥̥ ω •̥̥̥` )

Source Code

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.60.0