Vue3×TypeScript×TailwindCSSで、ワンタイムパスワードの入力フォームを作る方法
業務で必要になったのでワンタイムパスワードの入力フォームの実装方法について調べたところ、情報があまりなかったので、今回は、Vue3×TypeScript×TailwindCSSで、ワンタイムパスワードの入力フォームを作る方法について解説したいと思います。
まず、今回僕が作ったワンタイムパスワードの入力フォームの見た目は以下のような感じです。
大まかな仕様は以下のとおり。
仕様
- 1つ1つの入力欄には0~9までの1桁のみしか入れられない
- ワンタイムパスワードはコピペでも入力できる
- 入力時に次の入力欄にフォーカスが自動で移動する
それでは、実装するにあたってのポイントをいくつか紹介していきます。
Vue3×TypeScript×TailwindCSSで、ワンタイムパスワードの入力フォームを作る方法
テンプレートの実装
まず、vueファイルのテンプレート部分を実装していきます。
ここでは、otp.vueというファイルを作成します。(otpはonetime-passwordの略。)
<template>
<div class="flex items-center justify-center>
<div class="flex items-center justify-center>
<form class="flex gap-4">
<input
v-for="(n, index) in code"
:id="'input_' + index"
:key="index"
v-model="code[index]"
type="number"
pattern="\d*"
maxlength="1"
@input="handleInput"
@keypress="isNumber"
@keydown.delete="handleDelete"
@paste="onPaste"
class="w-24 h-12 text-5xl text-center appearance-none focus:outline-none"
/>
</form>
</div>
</div>
</template>
HTML部分だけ先に見てもわかりにくいと思いますが、inputタグ6つを用意して、それぞれに1桁の数値を文字列の形で入力していることがわかればここではOKです。(scriptタグの中を理解した後にテンプレートのコードに戻ってくると読みやすいかと思います。)
ちなみに、inputタグを1つだけ用意してそれを非表示にしつつ、v-forなどでdivタグを6つ用意するやり方もあるのですが、CSSの使い方が少し難しいと感じたので断念しました。
スクリプトの実装
次に、script部分ですが、こちらは以下のような感じです。
<script setup lang="ts">
/** 入力値の配列 */
const code: string[] = Array(6)
/** コピー&ペーストした文字列を1文字ずつ要素として持つ配列 */
let dataFromPaste: string[] | undefined
/** 許可する入力値の配列 */
const keysAllowed: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
/** 入力値のバリデーション */
const isNumber = (event: Event) => {
// prettier-ignore
(event.currentTarget as HTMLInputElement).value = ''
const keyPressed: string = (event as KeyboardEvent).key
if (!keysAllowed.includes(keyPressed)) {
event.preventDefault()
}
}
/** 入力の仕方に応じてフォーカス移動 */
const handleInput = (event: Event) => {
const inputType = (event as InputEvent).inputType
let currentActiveElement = event.target as HTMLInputElement
// 手入力のとき
if (inputType === 'insertText')
(currentActiveElement.nextElementSibling as HTMLElement)?.focus()
// コピー&ペーストのとき
if (inputType === 'insertFromPaste' && dataFromPaste) {
for (const num of dataFromPaste) {
const id: number = parseInt(currentActiveElement.id.split('_')[1])
currentActiveElement.value = num
code[id] = num
if (currentActiveElement.nextElementSibling) {
currentActiveElement =
currentActiveElement.nextElementSibling as HTMLInputElement
(currentActiveElement.nextElementSibling as HTMLElement)?.focus()
}
}
}
}
/** 削除時に入力欄に値があるかどうかに応じてフォーカス移動 */
const handleDelete = (event: Event) => {
const value = (event.target as HTMLInputElement).value
const currentActiveElement = event.target as HTMLInputElement
if (!value)
(currentActiveElement.previousElementSibling as HTMLElement)?.focus()
}
/** クリップボードにある文字列から空白を取り除く */
const onPaste = (event: Event) => {
dataFromPaste = (event as ClipboardEvent).clipboardData
?.getData('text')
.trim()
.split('')
if (dataFromPaste) {
for (const num of dataFromPaste) {
if (!keysAllowed.includes(num)) event.preventDefault()
}
}
}
</script>
定義した関数について順番に解説していきます。
isNumber
isNumberは、入力が0~9までの数値の文字列かどうかを判定するための関数です。
もしそれ以外のものが入力された場合は、preventDefaultで処理を無効化しています。
handleInput
handleInputは、入力の仕方に応じてフォーカス移動するための関数です。
手入力の場合は、次の要素があればそこにフォーカスを移動、コピペの場合はコピペした文字列の分だけフォーカスを移動します。
ちなみに、currentActiveElement.nextElementSiblingは、currentActiveElementに隣接する次の要素を表します。
handleDelete
handleDeleteは、削除時に入力欄に値があるかどうかに応じてフォーカス移動する関数です。
こちらは仕様にはない部分なので、実装しなくても仕様は満たせますが、あると便利なので実装しています。
ちなみに、currentActiveElement.previousElementSiblingは、currentActiveElementに隣接する手前の要素を表します。
onPaste
onPasteは、ペースト時、クリップボードにある文字列から空白を取り除くための関数です。
こちらも仕様にはない部分なので、実装しなくても仕様は満たせますが、あると便利なので実装しています。
styleの実装
最後に、inputタグのスピナーを非表示にするために以下のCSSをstyleタグ内に記述すればOKです。
<style>
/* Chrome, Safari, Edge */
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
</style>
参考記事
以下の記事がとても参考になりました。
How to Implement Verification Code With Vue 3 & TypeScript
Xについて
僕のXアカウントでは、主にweb開発、AI、会社経営のノウハウについて発信しています。もし興味があれば、フォローしていただけると嬉しいです。
プログラミング学習サポート&キャリア相談について
プログラミング学習サポート&キャリア相談も始めました。興味のある方はこちらから詳細をご覧ください。
Discussion