[解決済み】JavaScriptで複数のキーが同時に押されたかどうかを検出するには?
質問
JavaScriptのゲームエンジンを開発しようとしているのですが、このような問題に遭遇しました。
- を押すと スペース キャラクターがジャンプする。
- を押すと → キャラクターが右に移動する。
問題は、右を押しているときに、スペースを押すと、キャラクターがジャンプして、動かなくなることです。
を使っています。
keydown
関数を使用して、押されたキーを取得します。一度に複数のキーが押されているかどうかを確認するにはどうしたらよいでしょうか。
どのように解決するのですか?
注)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つまたは複数のキーを一度にチェックすることができる。
簡単に説明すると、例えば、あなたが
A
と
B
を起動し、それぞれ
keydown
を設定するイベントです。
map[e.keyCode]
の値を
e.type == keydown
と評価されます。
真
または
虚偽
. これで、両方の
map[65]
と
map[66]
が設定されます。
true
. を離すと
A
は、その
keyup
イベントが発生すると、同じロジックで
map[65]
(A)となり、現在は
偽
が、しかし
map[66]
(B) はまだ "down"(キーアップイベントをトリガーしていない)のままです。
真
.
は
map
の配列は、両方のイベントを通して、次のようになります。
// keydown A
// keydown B
[
65:true,
66:true
]
// keyup A
// keydown B
[
65:false,
66:true
]
今できることは2つあります。
A)
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のボディの後に来ます。 最適化のヒント : 有名なウェブサイトの多くは、scriptタグを の後に bodyタグの最適化。これは、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');
}
}
(編集終了)
この例では Ctrl シフト A , Ctrl シフト B と Ctrl シフト C
ただそれだけのことです :)
注意事項
キーコードの管理
一般的なルールとして、コード、特にキーコード(例えば、"key "コード)のようなものを文書化することは良い習慣です。
// CTRL+ENTER
を覚えておくことができます。
また、キーコードはドキュメントと同じ順番に並べる必要があります(
CTRL+ENTER => map[17] && map[13]
NOT
map[13] && map[17]
). こうすることで、コードを編集する際に混乱することがなくなります。
if-else チェーンの問題
異なる量のコンボをチェックする場合(例えば Ctrl シフト Alt 入力 と Ctrl 入力 )、小さいコンボを入れる の後に というように、大きいコンボと小さいコンボが似ている場合、小さいコンボが大きいコンボを上書きします。例
// 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: "キーを押していないのに、このキーコンボが起動し続ける".This key combo keeps activating even though I'm not pressing the keys"
アラートなど、メインウィンドウからフォーカスを奪うものを扱う場合は、「アラート」の文字列の中に
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
ブラウザの既定値
私が見つけた困ったことを、解決策を含めて紹介します。
問題:通常、ブラウザはキーコンボに対するデフォルトのアクションを持っているため (
Ctrl
D
は、ブックマーク・ウィンドウをアクティブにします。
Ctrl
シフト
C
はmaxthonでskynoteを有効にします)、また、以下のように
return false
の後に
map = []
そのため、あなたのサイトのユーザーは、quot;Duplicate File"関数が
Ctrl
D
は、代わりにそのページをブックマークします。
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 false
または
e.preventDefault()
event.keyCode
は非推奨
ユーザー
ショーン・ビエイラ
がコメントで指摘されました。
event.keyCode
は非推奨です。
そこで、優れた代替案を提示した。
event.key
のように、押されたキーの文字列表現を返します。
"a"
に対して
A
または
"Shift"
に対して
シフト
.
を料理してきました。 ツール という文字列を調べます。
element.onevent
対
element.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リンク ):
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 とします)、そしてキーコンボ
Ctrl+5
. 両方の
Ctrl
と
5
がダウンしている場合、渡されたコールバック関数(この場合は、その関数に
"FIVE "
が呼び出されます。このコールバックには
print_5
で、これを削除するには、単純に
input_txt.unwatch("print_5");
デタッチする場合
input_txt
から
txt
要素を使用します。
input_txt.detach();
こうすることで、ガベージコレクションはオブジェクトを拾うことができます (
input_txt
) が捨てられた場合、古いゾンビイベントリスナーが残ることはありません。
このクラスが何を返し、どんな引数を期待するのかがわかるように、C/Javaスタイルで表示されています。
<ブロッククオートBoolean key_down (String key);
戻り値
true
もし
key
はダウン、それ以外は偽。
Boolean keys_down (String key1, String key2, ...);
戻り値
true
もしすべてのキーが
key1 .. keyN
はダウン、それ以外は偽。
void watch (String name, Function callback, String key1, String key2, ...);
をすべて押すと、quot;watchpoint" を作成します。
keyN
はコールバックを起動します。
void unwatch (String name);
ウォッチポイントをその名前から削除します。
void clear (void);
キーダウンキャッシュをワイプします。と同じです。
map = {}
上記
void detach (void);
をデタッチします。
ev_kdown
と
ev_kup
リスナーを親要素から安全に取り除くことができるようになります。
2017-12-02更新 githubに公開してほしいというご要望にお応えして 要旨 .
2018-07-21更新 しばらく宣言型スタイルのプログラミングで遊んでいましたが、今はこのやり方が個人的に一番好きです。 フィドル , ペーストビン
一般的には、現実的に必要なケース(ctrl、alt、shift)で動作しますが、例えば、打つ必要がある場合は、以下のようになります。
a+w
を同時に使用することで、複数のキーを使用した検索を行うことができます。
こうあってほしい 徹底的な説明による回答 ミニブログが役に立ったようです :)
関連
-
Vue+ElementUIによる大規模なフォームの処理例
-
Vueのクラススタイルの使い方の詳細
-
[解決済み】 Uncaught TypeError : undefined のプロパティ 'replace' を読み取れない In Grid
-
[解決済み] JavaScriptで文字列が部分文字列を含むかどうかを確認する方法は?
-
[解決済み] あるJavaScriptファイルを他のJavaScriptファイルにインクルードするにはどうすればよいですか?
-
[解決済み] JavaScript で配列に値が含まれているかどうかを確認するにはどうすればよいですか?
-
[解決済み] JavaScriptでタイムスタンプを取得する方法は?
-
[解決済み] JavaScriptのオブジェクトが空であることをテストするにはどうすればよいですか?
-
[解決済み] JavaScriptでJSONをきれいに印刷する
-
[解決済み】JavaScriptで文字列の出現箇所をすべて置換する方法
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
jsを使った簡単な照明スイッチのコード
-
vueのプロジェクトでモックを使用する方法を知っていますか?
-
[解決済み】最大呼び出しスタックサイズ超過エラー
-
[解決済み】ERROR エラーです。スイッチのname属性が指定されていないフォームコントロールの値アクセッサがない
-
[解決済み】 env: node: macにそのようなファイルやディレクトリはありません
-
[解決済み】(Google Map API) Geocodeは以下の理由で成功しませんでした。REQUEST_DENIED
-
[解決済み】リクエストに失敗していないのに、「TypeError: failed to fetch」が表示される。
-
[解決済み】ReactJSでエラー発生 Uncaught TypeError: Super expression は null か関数でなければならず、undefined ではありません。
-
JavaScriptのStringに関する共通メソッド
-
JavaScriptのgetElementById、getElementsByTagNam、getElementsByClassNameの違いと使い方