addeventlistener文字列操作

【JavaScript】 キーイベントどれ使う?ケース別文字入力コントロール例

更新日:2020/08/03

 

keydown/keyup/keypress/inputの違い

イベントタイミング受け取る引数の型入力キャンセル
keydown
  • キーボードが押されたとき
  • キーボードが押されているとき
KeyboardEvent
keyup
  • キーボードから指が離れたとき
KeyboardEvent×
keypress
非推奨
機能削除の可能性あり
  • キーボードが押され

    文字が入力されたとき

  • シフトキーなど
    文字でない入力は
    反応しない
KeyboardEvent
input
  • ユーザーの操作で

    valueが変更されたとき

  • ペーストなどにも反応する
InputEvent×

keydown/keyup/keypressイベントはキーボードを押したり離したりすることで発生することに対して、inputイベントはtextarea等のvalueプロパティが変更されたときに発生するという違いがあります。

keydownイベントについて

keydownイベントは、シフトキーなど文字ではないキーでも発生します。
そのキーと文字キーが同時に押された場合、シフトやコントロールキーが押されているかどうかの情報と押されている文字が通知されます。この情報は、キーに関連付けたコマンドを実行する仕組みを構築するのに役立ちます。

また、キーを押し続けると、keydownイベントが定期的(リピート)に発生します。
発生する間隔はOS依存です。

キー入力およびリピートは、キャンセルして入力されなかったことにできます。

ただし入力した文字を別のものに変更、例えば小文字を大文字に変更することはできません
(現在のカーソル位置などを取得して、コードを駆使して値を変更することは可能です)

keyupイベントについて

keyupイベントは、キーを押した状態から離したときに発生します。
キーを離したタイミングでコマンド等を実行したいときに、便利です。

文字はkeydownの時点で入力されているため、keyupでは入力をキャンセルできません。

keypressイベントについて[非推奨イベント]

keypressイベントは、現在(2020/8)は非推奨となっています。
今後は実装から削除される可能性があるため、使用するべきではありません

このイベントは、シフトキーなどの文字ではないキーでは発生しません。

inputイベントについて

文字入力の変更を検知したいときはinputイベントを使用します。
このイベントは、文字列のペーストやドラッグでの変更も検知します。

なお、inputイベントはユーザーの行動によってvalueが変更された後に発生します。
つまり変更済みのため、入力をキャンセルできません。

値の変更はvalueを読み取り、直接valueを変更します。
このときキャレット位置がリセットされるので、現在位置を保存しておいて、変更後に復元する必要があります。

IME入力中は、確定前でも一文字入力するごとにイベントが発生します。

 

イベント通知のタイミング

各イベントが発生する順番(タイミング)を実際に文字を入力して確認してみます。

次のキー入力確認ツールに文字を入力してみてください。

キー入力確認ツール

【入力エリア】

入力中のキー:
【結果】

イベント通知の順番

上の結果から、次の順番でイベントが発生しているのがわかります。

文字入力イベント通知のタイミング

キーが押された

(リピート範囲)

keydown

keypress

input

(リピート範囲)

keyup

※キーを押し続けると、リピート範囲間でイベントが繰り返し発生します。

 

イベントリスナーで受け取る引数

KeyboardEvent

KeyboardEvent{
    altKey: 真偽値。Altキーを押しているかどうか。
    ctrlKey: 真偽値。Ctrlキーを押しているかどうか。
    shiftKey: 真偽値。シフトキーを押しているかどうか。
    metaKey: 真偽値。メタキーを押しているかどうか。
    isComposing: 真偽値。IME入力中かどうか。
    
    repeat: キー長押しで2回目以降(リピート)かどうか

    key: キーの値
    code: キーの位置
}

metaKeyは、WindowsはWindows キー、MacはCommandキーなのです。
しかし僕のWindows環境でWindows キーを押しても、trueになりませんでした。
使用しない方がいいかもしれません。

isComposingはIME入力中かどうかを判定します。

keyは現在押している文字またはキーの名前がセットされます。
文字は表示されているものです。
この値が一文字以上なら、シフトキーなどの表示できない文字と判断できます。

codeは押されているキーの位置を示す文字列がセットされます。
例えばテンキーの1は"Numpad1"。キー配列上の1は"Digit1"です。

keyおよびcodeの実際の値は、キー入力確認ツールで確認してください。

InputEvent

InputEvent{

    data: 入力された文字
    inputType: value値が変更された理由
    isComposing: 真偽値。IME入力中かどうか。
}

dataはキーボードで入力した場合は、入力された一文字がセットされています。
ペーストなどで文字列を受け取った場合は、その文字列がセットされています。
(ただしnullがセットされるブラウザもあります)
Deleteキーなどでvalue値が変更された場合、Null値がセットされます。

inputTypeは、"insertText"、"insertCompositionText"、"insertFromPaste"など、value値が変更された理由が文字列でセットされます。

isComposingは、IME入力中かどうかを判定できます。
ただし僕が使っているiPadのsafariは、undefinedでした。
使わない方がいいかもしれません。

IME入力中は一文字ごとにイベントが発生します。
しかし、イベントで受け取ったdataには入力中の文字全てがセットされます。
キー入力確認ツールで確認してみてください。

inputで処理しようとすると、非常に扱いにくいです。
IME確定文字はcompositionendイベントで取得できるので、そちらで処理したほうがよいです。

 

ケース別サンプル

文字入力イベントの処理例をいくつか挙げてみます。

DIV要素でキーベントを検知する

html


<div  id="inputarea" tabindex="0"></div>

または

JavaScriptl


const inputarea = document.getElementById("inputarea");
inputarea.tabindex="0"

通常、フォーカスを受け取らないDIV要素はキーイベントが発生しません。
tabindex属性をセットすることで、フォーカスを受け取るようになり、keydown/keypress/keyupイベントが通知されるようになります。

ただし、inputイベントは発生しません。

DIV要素で文字入力を可能にする

html


<div  id="inputarea" contenteditable="true"></div>

または

JavaScriptl


const inputarea = document.getElementById("inputarea");
inputarea.contenteditable="true"

div要素にcontenteditableを付加すると、textareaのように文字入力が可能になります。
keydown/keypress/keyup/inputイベントが通知されるようになります。

※div要素はvalueプロパティを持っていないので入力された文字列を取得するには、innerHTMLやtextContentを使用します。

繰り返し(リピート)入力を制限する

JavaScriptl


const inputarea = document.getElementById("inputarea");
inputarea.addEventListener( "keydown" , ( ) => {
     if(e.repeat) e.preventDefault();
});

キーを長押ししたとき、keydownイベントのrepeatフラグがtrueになります。
このときe.preventDefault()で既定の処理をキャンセルすると、繰り返し入力を制限することができます。

キーを押したときショートカットキーを実行する

JavaScriptl


const inputarea = document.getElementById("inputarea");
inputarea.addEventListener( "keydown" , ( ) => {
    if( e.ctrlKey){
                switch(e.code ){
                    case "KeyV": // Crtk + v
                        e.preventDefault();
                        if( !e.repeat ) {
                           // Crtk + vのときの処理
                        }
                        break;
                    case "KeyC": // Crtk + c
                        e.preventDefault();
                        if( !e.repeat ) {
                            // Crtk + cのときの処理
                        }
                        break;
                }
            }
    }
});

上の例は、[Ctrl+c]と[Crtk+v]のデフォルト処理を無効にして、一回のみ(リピート入力を制限)独自の処理をおこなっています。

キーを離したときショートカットキーを実行する

JavaScriptl


const inputarea = document.getElementById("inputarea");
inputarea.addEventListener( "keydown" , ( ) => {
    if( e.ctrlKey){
                switch(e.code ){
                    case "KeyV": // Crtk + v
                    case "KeyC": // Crtk + c
                        e.preventDefault();
                        break;
                }
            }
    }
});
inputarea.addEventListener( "keyUp" , ( ) => {
    if( e.ctrlKey){
                switch(e.code ){
                    case "KeyV": // Crtk + v
                           // Crtk + vのときの処理
                        break;
                    case "KeyC": // Crtk + c
                            // Crtk + cのときの処理
                        break;
                }
            }
    }
});

上の例は、[Ctrl+c]と[Crtk+v]のデフォルト処理を無効にして、キーを離したときに独自の処理をおこなっています。

inputイベントで入力された文字を置き換える

inputイベントでの文字の置き換えは、value値を取得して文字列を加工し、valueに再セットします。

ただしinputイベントは頻繁に発生するため、ユーザーの文字入力を妨げる可能性があります。
フォーカスが外れたときに一気に変換することも考慮に入れた方がよいです。

なお一部の例で正規表現と replaceメソッドを使用しています。
正規表現と replaceメソッドについては、次のページを参考にしてください。

参考記事:
【JavaScript】 正規表現まとめメモ
【JavaScript】 replace()の使い方 単純な置換と正規表現での置換

小文字英字を大文字に変換

JavaScriptl


const inputarea = document.getElementById("inputarea");
inputarea.addEventListener( "input" , ( ) => {
    const {selectionStart,selectionEnd} = inputarea;
    inputarea.value =  inputarea.value.toUpperCase();
    [inputarea.selectionStart,inputarea.selectionEnd] = [selectionStart,selectionEnd];
});

入力されている文字列をtoUpperCase()で大文字に変換しています。
その際value値を変更したことでキャレット位置がリセットされてしまうため、あらかじめ位置を保存しておき、最後に復元しています。

この例では小文字入力以外でもtoUpperCase()を実行するため、少し効率が悪いと感じる人も多いです。

そこで次のように手を加えてみます。

JavaScriptl


const inputarea = document.getElementById("inputarea");
inputarea.addEventListener( "input" , ( ) => {
    if( /[a-z]/.test( e.data ) ){
         const {selectionStart,selectionEnd} = inputarea;
         inputarea.value =  inputarea.value.toUpperCase();
         [inputarea.selectionStart,inputarea.selectionEnd] = [selectionStart,selectionEnd];
    }
});

入力された文字がアルファベットの小文字かどうか確認してから、value値全体を大文字に変換しています。
入力済みの文字列が長いほど効率的ですが、一部のブラウザではペーストされたときにdata値がnullになるものがあります。
(chrome84.0.4147.105はnullでした)

ペーストされたときブラウザによって変換されないという問題が出てくるので、入力の確認をしない方がよいです。

半角数字を全角に変換

JavaScriptl


const inputarea = document.getElementById("inputarea");
inputarea.addEventListener( "input" , ( ) => {
         const {selectionStart,selectionEnd} = inputarea;
         inputarea.value =  inputarea.value.replace(/[0-9]/g,
                     m=>[ "0","1","2","3","4",
                               "5","6","7","8","9"][m]
                   );
         [inputarea.selectionStart,inputarea.selectionEnd] = [selectionStart,selectionEnd];
});

/[0-9]/は0から9までの一文字と一致し、mには一致した文字が入っています。
その文字をインデックスとして、全角文字の配列から一文字取得しています。

IME入力文字でひらがなをカタカナに変換

JavaScriptl


const inputarea = document.getElementById("inputarea");
inputarea.addEventListener( "input" , ( ) => {
         const {selectionStart,selectionEnd} = inputarea;
         inputarea.value =  hiraToKana( inputarea.value );
         [inputarea.selectionStart,inputarea.selectionEnd] = [selectionStart,selectionEnd];
});

【JavaScript】 ひらがなをカタカナに変換・逆変換するで紹介している、hiraToKana関数を使用して、ひらがなをカタカナに変換しています。

カタカナからひらがなへの変換は、同記事のkanaToHira関数を使用してください。

なおIME入力を監視して一度に変換することも可能です。

JavaScriptl


const inputarea = document.getElementById("inputarea");
inputarea.addEventListener( "input" , ( ) => {
         if( e.inputType.includes("Composition") ) return;
         const {selectionStart,selectionEnd} = inputarea;
         inputarea.value =  hiraToKana( inputarea.value );
         [inputarea.selectionStart,inputarea.selectionEnd] = [selectionStart,selectionEnd];
});
inputarea.addEventListener( "compositionend" , ( ) => {
         const {selectionStart,selectionEnd} = inputarea;
         inputarea.value =  hiraToKana( inputarea.value );
         [inputarea.selectionStart,inputarea.selectionEnd] = [selectionStart,selectionEnd];
});

inputイベントではinputTypeに"Composition"が含まれるときは変換をおこなわずに、IME確定時に呼び出されるcompositionendイベントで変換をしています。

inputイベントでinputTypeに"Composition"が含まれないときに変換処理をおこなっているのは、ペースト等でひらがな文字が入力されるからです。

keydownイベントで入力された文字を置き換える

次の例は、キーボードでアルファベットの小文字が入力されたとき大文字に変更しています。

JavaScriptl


const inputarea = document.getElementById("inputarea");
inputarea.addEventListener( "input" , ( ) => {
    if( e.key >= "a" && e.key<="z" ){
        e.preventDefault();

        const {selectionStart,selectionEnd,value} = e.target;

        if( selectionStart === null ||  selectionStart>=value.length ){
                inputarea.value = value + e.key.toUpperCase();
        }else if( selectionStart <= 0 ){
            inputarea.value = e.key.toUpperCase() + value;
        }else{
            inputarea.value = value.substring(0,selectionStart) + e.key.toUpperCase() + value.substring(selectionEnd);
        }
        inputarea.selectionStart = inputarea.selectionEnd = selectionStart + e.key.length;

    }
});

具体的には、次のような処理をおこなっています。

  • 対象の文字が入力されたら既定の処理をキャンセル
  • 選択範囲を確認して、入力エリアのvalue値を変更する
  • キャレット位置を今回追加した文字の後ろにセットする

更新日:2020/08/03

書いた人(管理人):けーちゃん

スポンサーリンク

記事の内容について

null

こんにちはけーちゃんです。
説明するのって難しいですね。

「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。

掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。

ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php

 

このサイトは、リンクフリーです。大歓迎です。