跳到內容

每條游來游去的小魚,都是正在瀏覽這一頁的訪客,有一條是你自己 ( ´ ▽ ` )ノ 覺得干擾閱讀的話,可以把小魚關掉。

解碼輸入框

input

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

名稱描述
Input 事件input 元素有 compositionstart、compositionupdate、compositionend 等事件,用於處理輸入法組字過程中的文字輸入
Document activeElement取得目前擁有焦點的元素

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

<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 light-dark(#ccc, #555)
border-inline: 2px solid light-dark(#AAA, #666)
</style>

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

<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 light-dark(#ccc, #555)
border-inline: 2px solid light-dark(#AAA, #666)
</style>

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

codlin
<template>
<div class="w-full flex flex-col items-center gap-4 example-wrap">
<div class="example-ctrl flex flex-col gap-4">
<base-checkbox
v-model="visible"
:label="t('showInput')"
/>
</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>

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

輸入時,每個字元會轉換成 useChar 產生的物件,根據設定產生隨機變化的效果,讓字元像逐漸解碼一樣顯示。

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

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

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

  2. 選取

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

  3. 刪除

    有分 Forward 與 Backward 兩個方向。

  4. 維持插入點(caret)

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

  5. 拼字與插入點(caret)

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

  6. emoji

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

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

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

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

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;
}
interface Emits {
'update:modelValue': [value: Props['modelValue']];
}
interface Slots {
default?: () => unknown;
}