JavaScriptを使用して複数のキーが同時に押されたかどうかを検出するにはどうすればよいですか?


173

私はJavaScriptゲームエンジンを開発しようとしていて、この問題に遭遇しました。

  • SPACEキャラクターを押すとジャンプします。
  • 押すとキャラクターが右に移動します。

問題は、右を押してからスペースを押すと、キャラクターがジャンプして停止します。

keydown関数を使用してキーを押します。複数のキーが同時に押されているかどうかを確認するにはどうすればよいですか?


3
これは、押されたすべてのキーのリストを自動的に印刷するWebページのデモです。stackoverflow.com/ a / 13651016/975097
Anderson Green

回答:


327

注:keyCodeは非推奨になりました。

概念を理解していれば、複数のキーストロークの検出は簡単です

私のやり方は次のとおりです:

var map = {}; // You could also use an array
onkeydown = onkeyup = function(e){
    e = e || event; // to deal with IE
    map[e.keyCode] = e.type == 'keydown';
    /* insert conditional here */
}

このコードは非常に単純です。コンピュータは一度に1つのキーストロークしか渡さないため、複数のキーを追跡するための配列が作成されます。その後、配列を使用して、1つ以上のキーを一度にチェックできます。

説明のために、あなたがAand を押すとB、それぞれがtrueまたはfalseに評価されるの値にkeydown設定するイベントを起動するとします。これで両方とに設定されました。を離すと、イベントが発生し、同じロジックで(A)の反対の結果を決定します。これは現在falseですが、(B)はまだ「ダウン」しているため(キーアップイベントはトリガーされていません)、それは本当のままですmap[e.keyCode]e.type == keydownmap[65]map[66]trueAkeyupmap[65]map[66]

map配列は、両方のイベントを通じて、次のようになります。

// keydown A 
// keydown B
[
    65:true,
    66:true
]
// keyup A
// keydown B
[
    65:false,
    66:true
]

今できることは2つあります。

A)キーロガー()は、後で1つ以上のキーコードをすばやく把握したい場合に参照として作成できます。あなたがhtml要素を定義し、変数でそれを指し示していると仮定しますelement

element.innerHTML = '';
var i, l = map.length;
for(i = 0; i < l; i ++){
    if(map[i]){
        element.innerHTML += '<hr>' + i;
    }
}

注:id属性によって要素を簡単に取得できます。

<div id="element"></div>

これにより、JavaScriptで簡単に参照できるHTML要素が作成されます。 element

alert(element); // [Object HTMLDivElement]

あなたはそれを使用しdocument.getElementById()たり$()つかんだりする必要さえありません。ただし、互換性のために、jQueryを使用することを$()お勧めします。

スクリプトタグがHTMLの本文の後に来るようにしてください。最適化のヒント:ほとんどの有名なWebサイトでは、最適化のためにbodyタグの後に scriptタグを配置しています。これは、scriptタグが、スクリプトのダウンロードが完了するまで、それ以上の要素の読み込みをブロックするためです。コンテンツの前に置くと、コンテンツを事前に読み込むことができます。

B(興味のある場所)一度に1つ以上のキーを確認でき/*insert conditional here*/ます。次の例をご覧ください。

if(map[17] && map[16] && map[65]){ // CTRL+SHIFT+A
    alert('Control Shift A');
}else if(map[17] && map[16] && map[66]){ // CTRL+SHIFT+B
    alert('Control Shift B');
}else if(map[17] && map[16] && map[67]){ // CTRL+SHIFT+C
    alert('Control Shift C');
}

編集:これは最も読みやすいスニペットではありません。読みやすさが重要なので、次のようなことを試して、見やすくすることができます。

function test_key(selkey){
    var alias = {
        "ctrl":  17,
        "shift": 16,
        "A":     65,
        /* ... */
    };

    return key[selkey] || key[alias[selkey]];
}

function test_keys(){
    var keylist = arguments;

    for(var i = 0; i < keylist.length; i++)
        if(!test_key(keylist[i]))
            return false;

    return true;
}

使用法:

test_keys(13, 16, 65)
test_keys('ctrl', 'shift', 'A')
test_key(65)
test_key('A')

これは良いですか?

if(test_keys('ctrl', 'shift')){
    if(test_key('A')){
        alert('Control Shift A');
    } else if(test_key('B')){
        alert('Control Shift B');
    } else if(test_key('C')){
        alert('Control Shift C');
    }
}

(編集終了)


この例のチェックのためにCtrlShiftACtrlShiftBと、CtrlShiftC

それはそれと同じくらい簡単です:)

ノート

キーコードの追跡

一般的な規則として、コード、特にキーコード(など // CTRL+ENTER)を文書化して、それらが何であったかを思い出せるようにすることをお勧めします。

また、ドキュメントと同じ順序でキーコードを配置する必要があります(CTRL+ENTER => map[17] && map[13]、NOT map[13] && map[17])。このようにして、コードに戻って編集する必要があるときに混乱することはありません。

if-elseチェーンの落とし穴

異なる量のコンボ(CtrlShiftAltEnterやなどCtrlEnter)をチェックする場合は大きいコンボのに小さいコンボを配置するか、小さいコンボが十分に類似している場合は、大きいコンボをオーバーライドします。例:

// Correct:
if(map[17] && map[16] && map[13]){ // CTRL+SHIFT+ENTER
    alert('Whoa, mr. power user');
}else if(map[17] && map[13]){ // CTRL+ENTER
    alert('You found me');
}else if(map[13]){ // ENTER
    alert('You pressed Enter. You win the prize!')
}

// Incorrect:
if(map[17] && map[13]){ // CTRL+ENTER
    alert('You found me');
}else if(map[17] && map[16] && map[13]){ // CTRL+SHIFT+ENTER
    alert('Whoa, mr. power user');
}else if(map[13]){ // ENTER
    alert('You pressed Enter. You win the prize!');
}
// What will go wrong: When trying to do CTRL+SHIFT+ENTER, it will
// detect CTRL+ENTER first, and override CTRL+SHIFT+ENTER.
// Removing the else's is not a proper solution, either
// as it will cause it to alert BOTH "Mr. Power user" AND "You Found Me"

Gotcha:「キーを押さなくても、このキーコンボがアクティブになり続ける」

アラートまたはメインウィンドウからフォーカスを取得するものを処理する場合map = []、条件が完了した後にアレイをリセットするように含めることができます。これは、などの一部の機能alert()により、メインウィンドウからフォーカスが外れ、「keyup」イベントがトリガーされないためです。例えば:

if(map[17] && map[13]){ // CTRL+ENTER
    alert('Oh noes, a bug!');
}
// When you Press any key after executing this, it will alert again, even though you 
// are clearly NOT pressing CTRL+ENTER
// The fix would look like this:

if(map[17] && map[13]){ // CTRL+ENTER
    alert('Take that, bug!');
    map = {};
}
// The bug no longer happens since the array is cleared

Gotcha:ブラウザのデフォルト

ここに私が見つけた迷惑なものがあります、解決策が含まれています:

問題:ブラウザーは通常CtrlDCtrlShiftCキーコンボでデフォルトのアクション(ブックマークウィンドウをアクティブにする、またはmaxthonでスカイノートをアクティブにするなど)を持っているため、のreturn false後に追加することもできますmap = []。これにより、「ファイルの複製」時にサイトのユーザーが不満を感じないようになります。関数がに置かれCtrlD、代わりにページをブックマークします。

if(map[17] && map[68]){ // CTRL+D
    alert('The bookmark window didn\'t pop up!');
    map = {};
    return false;
}

がなければreturn false、ブックマークウィンドウポップアップし、ユーザーをがっかりさせます。

returnステートメント(新規)

わかりましたので、その時点で関数を常に終了する必要はありません。それがevent.preventDefault()関数が存在する理由です。それが行うことは、ブラウザにデフォルトのアクションを実行させないようにインタープリタに指示する内部フラグを設定することです。その後、関数の実行が続行されます(関数returnはすぐに終了します)。

あなたが使用するかどうかを決定する前にこの区別を理解しますreturn falsee.preventDefault()

event.keyCode 廃止されました

ユーザーSeanVieiraevent.keyCodeは、非推奨となったコメントで指摘しました。

そこで、彼は優れた代替手段を提供しました:はevent.key"a"for A"Shift"forのように、押されているキーの文字列表現を返しますShift

私は先に進んで、上記の文字列を調べるためのツールを作りました。

element.oneventelement.addEventListener

に登録されたハンドラーaddEventListenerは積み重ねることができ、登録順に呼び出されますが、.onevent直接設定はかなり積極的であり、以前に持っていたものをオーバーライドします。

document.body.onkeydown = function(ev){
    // do some stuff
    ev.preventDefault(); // cancels default actions
    return false; // cancels this function as well as default actions
}

document.body.addEventListener("keydown", function(ev){
    // do some stuff
    ev.preventDefault() // cancels default actions
    return false; // cancels this function only
});

.oneventプロパティは、すべての動作を上書きするようだev.preventDefault()return false;、むしろ予測できないことができます。

どちらの場合でも、経由で登録されたハンドラーのaddEventlistener方が記述しやすく、理由付けも簡単です。

attachEvent("onevent", callback)Internet Explorerの非標準実装からのものもありますが、これは非推奨ではなく、JavaScriptにも関係しません(JScriptと呼ばれる難解な言語に関係します)。ポリグロットコードをできるだけ回避することをお勧めします。

ヘルパークラス

混乱/苦情に対処するために、この抽象化を行う「クラス」を作成しました(pastebin link):

function Input(el){
    var parent = el,
        map = {},
        intervals = {};
    
    function ev_kdown(ev)
    {
        map[ev.key] = true;
        ev.preventDefault();
        return;
    }
    
    function ev_kup(ev)
    {
        map[ev.key] = false;
        ev.preventDefault();
        return;
    }
    
    function key_down(key)
    {
        return map[key];
    }

    function keys_down_array(array)
    {
        for(var i = 0; i < array.length; i++)
            if(!key_down(array[i]))
                return false;

        return true;
    }
    
    function keys_down_arguments()
    {
        return keys_down_array(Array.from(arguments));
    }
    
    function clear()
    {
        map = {};
    }
    
    function watch_loop(keylist, callback)
    {
        return function(){
            if(keys_down_array(keylist))
                callback();
        }
    }

    function watch(name, callback)
    {
        var keylist = Array.from(arguments).splice(2);

        intervals[name] = setInterval(watch_loop(keylist, callback), 1000/24);
    }

    function unwatch(name)
    {
        clearInterval(intervals[name]);
        delete intervals[name];
    }

    function detach()
    {
        parent.removeEventListener("keydown", ev_kdown);
        parent.removeEventListener("keyup", ev_kup);
    }
    
    function attach()
    {
        parent.addEventListener("keydown", ev_kdown);
        parent.addEventListener("keyup", ev_kup);
    }
    
    function Input()
    {
        attach();

        return {
            key_down: key_down,
            keys_down: keys_down_arguments,
            watch: watch,
            unwatch: unwatch,
            clear: clear,
            detach: detach
        };
    }
    
    return Input();
}

このクラスはすべてを行うわけではなく、考えられるすべてのユースケースを処理するわけではありません。私は図書館の人ではありません。ただし、一般的なインタラクティブな使用には問題ありません。

このクラスを使用するには、インスタンスを作成して、キーボード入力を関連付ける要素をポイントします。

var input_txt = Input(document.getElementById("txt"));

input_txt.watch("print_5", function(){
    txt.value += "FIVE ";
}, "Control", "5");

これにより、要素に新しい入力リスナーが#txt(textareaであると想定して)接続され、キーcomboのウォッチポイントが設定されますCtrl+5。ときに両方Ctrl5ダウンしている、あなたが渡されたコールバック関数(この場合には、追加機能"FIVE "テキストエリアには)と呼ばれます。コールバックはname print_5に関連付けられているので、それを削除するには、次のように使用します。

input_txt.unwatch("print_5");

要素input_txtから切り離すにはtxt

input_txt.detach();

このようにして、ガベージコレクションはオブジェクト(input_txt)を取得できます。オブジェクトが破棄された場合、古いゾンビイベントリスナーが残ってしまうことはありません。

完全を期すために、C / Javaスタイルで提示されたクラスのAPIのクイックリファレンスを以下に示します。これにより、クラスが返すものと期待する引数がわかります。

Boolean  key_down (String key);

ダウンしているtrue場合keyはfalseを返し、それ以外の場合はfalseを返します。

Boolean  keys_down (String key1, String key2, ...);

trueすべてのキーkey1 .. keyNが押されている場合はを返し、それ以外の場合はfalseを返します。

void     watch (String name, Function callback, String key1, String key2, ...);

すべてを押すkeyNとコールバックがトリガーされるような「ウォッチポイント」を作成します

void     unwatch (String name);

そのウォッチポイントをその名前で削除します

void     clear (void);

「キーダウン」キャッシュをワイプします。map = {}上記と同等

void     detach (void);

ev_kdownev_kupリスナーを親要素から切り離し、インスタンスを安全に削除できるようにします

更新2017-12-02これをgithubに公開するリクエストに応えて、要旨を作成しました。

更新2018-07-21私はしばらく宣言型スタイルのプログラミングを行ってきましたが、これが今では私の個人的なお気に入りです:fiddlepastebin

一般的には、現実的に必要なケース(Ctrl、Alt、Shift)で機能しますが、たとえば、a+w同時にヒットする必要がある場合は、アプローチを "組み合わせて"マルチキールックアップ。


これが完全に説明された回答のミニブログが役に立てば幸いです:)


私はこの答えを大きく更新しました!キーロガーの例はより一貫性があり、「ノート」セクションが読みやすくなるようにフォーマットを更新し、return falsevspreventDefault()
Braden Best

ドキュメントにフォーカスがある状態でキーを押し続けると、URLボックスをクリックして、キーを離します。keyupが実行されることはありませんが、キーが押されているため、リストが正しくありません。また、その逆も同様です。URLボックスでキーを押したままにする、keydownが起動されない、ドキュメントにフォーカスが置かれる、keydownステータスがリストに表示されない。基本的に、ドキュメントがフォーカスを取り戻すときはいつでも、キーステータスを確認することはできません。
user3015682

3
注意:keyCode廃止予定です-に切り替えるとkey、キーの実際の文字表現が得られます。
Sean Vieira 2016

1
@SeanVieiraその後、Cでも奇妙なことができます。たとえば、それがとmyString[5]同じで5[myString]、コンパイルの警告が表示されないことを知っていますか(を使用しても-Wall -pedantic)?これは、pointer[offset]表記がポインタを受け取り、オフセットを追加し、結果を逆参照しmyString[5]てと同じにするため*(myString + 5)です。
ブレーデンベスト

1
@inorganikはヘルパークラスを参照していますか?要旨をリポジトリのように使用できますか?コードの小さなスニペットに対して完全なリポジトリを作成するのは面倒です。かしこまりました。今夜は撃ちます。ミッドナイトマウンテンタイム
ブレーデンベスト

30

あなたは使用する必要があるのkeydown押されたキーを追跡するためのイベントを、そしてあなたが使用する必要がありますからkeyupキーがリリースされたときを追跡するためのイベントを。

この例を参照してください:http : //jsfiddle.net/vor0nwe/mkHsU/

(更新:jsfiddle.netが保釈する場合に備えて、ここでコードを再現しています:) HTML:

<ul id="log">
    <li>List of keys:</li>
</ul>

...そしてJavaScript(jQueryを使用):

var log = $('#log')[0],
    pressedKeys = [];

$(document.body).keydown(function (evt) {
    var li = pressedKeys[evt.keyCode];
    if (!li) {
        li = log.appendChild(document.createElement('li'));
        pressedKeys[evt.keyCode] = li;
    }
    $(li).text('Down: ' + evt.keyCode);
    $(li).removeClass('key-up');
});

$(document.body).keyup(function (evt) {
    var li = pressedKeys[evt.keyCode];
    if (!li) {
       li = log.appendChild(document.createElement('li'));
    }
    $(li).text('Up: ' + evt.keyCode);
    $(li).addClass('key-up');
});

その例では、どのキーが押されているかを追跡するために配列を使用しています。実際のアプリケーションでdeleteは、関連付けられたキーが解放されたら、各要素を使用することができます。

この例では、jQueryを使用して物事を簡単に行えるようにしましたが、この概念は「生の」JavaScriptで作業する場合にも機能します。


しかし、私が思ったようにバグがあります。あるボタンを押し続けた場合、スクリットに再度フォーカスしたときにボタンを押したまま別のタブに切り替える(またはフォーカスを緩める)と、ボタンが押されていなくても押されていることが示されます。:D
XCS

3
@Cristy:次にonblur、押されたすべてのキーを配列から削除するイベントハンドラーを追加することもできます。フォーカスを失ったら、すべてのキーをもう一度押す必要があります。残念ながら、に相当するJSはありませんGetKeyboardState
Martijn

1
Mac(Chrome)での貼り付けに問題があります。それは正常にkeydown 91(コマンド)、keydown 86(v)を取得しますが、91のみをキーアップし、86をダウンのままにします。キーのリスト:上:91、下:86。これは、コマンドキーを2番目に離したときにのみ発生するようです。最初に離すと、両方のキーアップが正しく登録されます。
James Alday

2
一度に3つ以上のキーを押すと、キーを1つ持ち上げるまで、それ以上のキーの検出が停止するようです。(Firefox 22でテスト済み)
Qvcool 2013

1
@JamesAlday同じ問題。MacのMeta(OS)キーにのみ影響するようです。ここでの問題に#3を参照してください。bitspushedaround.com/...
ドン・マッカーディ

20
document.onkeydown = keydown; 

function keydown (evt) { 

    if (!evt) evt = event; 

    if (evt.ctrlKey && evt.altKey && evt.keyCode === 115) {

        alert("CTRL+ALT+F4"); 

    } else if (evt.shiftKey && evt.keyCode === 9) { 

        alert("Shift+TAB");

    } 

}

1
これが私が望んでいたすべてであり、最良の答え
Randall Coding

7

私はこの方法を使用しました(Shift + Ctrlが押されている場所を確認する必要がありました)。

// create some object to save all pressed keys
var keys = {
    shift: false,
    ctrl: false
};

$(document.body).keydown(function(event) {
// save status of the button 'pressed' == 'true'
    if (event.keyCode == 16) {
        keys["shift"] = true;
    } else if (event.keyCode == 17) {
        keys["ctrl"] = true;
    }
    if (keys["shift"] && keys["ctrl"]) {
        $("#convert").trigger("click"); // or do anything else
    }
});

$(document.body).keyup(function(event) {
    // reset status of the button 'released' == 'false'
    if (event.keyCode == 16) {
        keys["shift"] = false;
    } else if (event.keyCode == 17) {
        keys["ctrl"] = false;
    }
});

5

完全なサンプルコードが必要な人のために。右+左が追加されました

var keyPressed = {};
document.addEventListener('keydown', function(e) {

   keyPressed[e.key + e.location] = true;

    if(keyPressed.Shift1 == true && keyPressed.Control1 == true){
        // Left shift+CONTROL pressed!
        keyPressed = {}; // reset key map
    }
    if(keyPressed.Shift2 == true && keyPressed.Control2 == true){
        // Right shift+CONTROL pressed!
        keyPressed = {};
    }

}, false);

document.addEventListener('keyup', function(e) {
   keyPressed[e.key + e.location] = false;

   keyPressed = {};
}, false);

3

keydownが複数の関数を呼び出すようにし、各関数が特定のキーをチェックして適切に応答するようにします。

document.keydown = function (key) {

    checkKey("x");
    checkKey("y");
};

2

keypress Eventハンドラーを追加してみますkeydown。例えば:

window.onkeydown = function() {
    // evaluate key and call respective handler
    window.onkeypress = function() {
       // evaluate key and call respective handler
    }
}

window.onkeyup = function() {
    window.onkeypress = void(0) ;
}

これはパターンを説明するためだけのものです。ここでは詳しく説明しません(特に、ブラウザー固有のレベル2 + Event登録については触れません)。

これが役立つかどうかを投稿してください。


1
これは機能しません。keydownとkeyup トリガーする多くのキーでkeypressがトリガーされません。また、すべてのブラウザーがキーダウンイベントを繰り返しトリガーするわけではありません。
Martijn、2011年

Quirksmodeが間違っています:quirksmode.org/dom/events/keys.html。しかし、私は自分の提案をテストしなかったので、それは主張しません。
FK82、2011年

そのページから引用:「ユーザーが矢印キーなどの特別なキーを押しても、ブラウザはkeypressイベントを発生させてはならない」。繰り返しについては、OperaとKonquerorが正しく行われていないと表示されます。
Martijn、2011年

2

押されたキーの1つがAlt / Crtl / Shiftの場合、この方法を使用できます。

document.body.addEventListener('keydown', keysDown(actions) );

function actions() {
   // do stuff here
}

// simultaneous pressing Alt + R
function keysDown (cb) {
  return function (zEvent) {
    if (zEvent.altKey &&  zEvent.code === "KeyR" ) {
      return cb()
    }
  }
}

2
    $(document).ready(function () {
        // using ascii 17 for ctrl, 18 for alt and 83 for "S"
        // ctr+alt+S
        var map = { 17: false, 18: false, 83: false };
        $(document).keyup(function (e) {
            if (e.keyCode in map) {
                map[e.keyCode] = true;
                if (map[17] && map[18] && map[83]) {
                    // Write your own code here, what  you want to do
                    map[17] = false;
                    map[18] = false;
                    map[83] = false;
                }
            }
            else {
                // if u press any other key apart from that "map" will reset.
                map[17] = false;
                map[18] = false;
                map[83] = false;
            }
        });

    });

あなたの貢献をありがとう。コードを投稿するのではなく、説明を追加してください。
Tim Rutter、

2

これは普遍的な方法ではありませんが、場合によっては便利です。CTRL+ somethingまたはShift+ somethingまたはCTRL+ Shift+ somethingなどの組み合わせに役立ちます。

例:CTRL+ を使用してページを印刷する場合P、最初に押されたキーのCTRL後には常にが続きPます。CTRL+ SCTRL+ Uおよび他の組み合わせと同じです。

document.addEventListener('keydown',function(e){
      
    //SHIFT + something
    if(e.shiftKey){
        switch(e.code){

            case 'KeyS':
                console.log('Shift + S');
                break;

        }
    }

    //CTRL + SHIFT + something
    if(e.ctrlKey && e.shiftKey){
        switch(e.code){

            case 'KeyS':
                console.log('CTRL + Shift + S');
                break;

        }
    }

});


1
case 65: //A
jp = 1;
setTimeout("jp = 0;", 100);

if(pj > 0) {
ABFunction();
pj = 0;
}
break;

case 66: //B
pj = 1;
setTimeout("pj = 0;", 100);

if(jp > 0) {
ABFunction();
jp = 0;
}
break;

最善の方法ではないことは知っています。


-1
Easiest, and most Effective Method

//check key press
    function loop(){
        //>>key<< can be any string representing a letter eg: "a", "b", "ctrl",
        if(map[*key*]==true){
         //do something
        }
        //multiple keys
        if(map["x"]==true&&map["ctrl"]==true){
         console.log("x, and ctrl are being held down together")
        }
    }

//>>>variable which will hold all key information<<
    var map={}

//Key Event Listeners
    window.addEventListener("keydown", btnd, true);
    window.addEventListener("keyup", btnu, true);

    //Handle button down
      function btnd(e) {
      map[e.key] = true;
      }

    //Handle Button up
      function btnu(e) {
      map[e.key] = false;
      }

//>>>If you want to see the state of every Key on the Keybaord<<<
    setInterval(() => {
                for (var x in map) {
                    log += "|" + x + "=" + map[x];
                }
                console.log(log);
                log = "";
            }, 300);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.