0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

keydownでキーボードショートカットを取得するUIの実装メモ(Mac⌘/Win Ctrl両対応)

0
Posted at

この記事は何

「ユーザーが実際に押したキーの組み合わせを取得して、ショートカットとして登録する」UI の実装メモです。keydown で拾うだけ……と思いきや、preventDefault 忘れや修飾キーの扱いで地味にハマります。Mac の と Windows の Ctrl を両対応させる方法も含めて、手元で試せる最小コードでまとめます。

ショートカット登録機能や、キーボード操作を受け付けるエディタ系 UI を作るときに必要になる知識です。コピペで動く粒度で書くので、そのまま土台に使えます。

最小の取得コード

取得モード(capturing)のあいだだけ keydown を拾い、押された組み合わせを pending に溜めます。

let capturing = false;
const pending = { mod: false, alt: false, shift: false, key: "" };

document.addEventListener("keydown", (e) => {
  if (!capturing) return;

  e.preventDefault(); // ① ブラウザ標準のショートカットを止める

  // ② 修飾キー単体は「まだ確定じゃない」ので待つ
  if (["Meta", "Control", "Alt", "Shift"].includes(e.key)) return;

  pending.mod = e.metaKey || e.ctrlKey; // ③ ⌘ と Ctrl を1つにまとめる
  pending.alt = e.altKey;
  pending.shift = e.shiftKey;
  pending.key = normalizeKey(e.key); // ④ e.key を表示用に正規化

  capturing = false; // 1つ取れたら終了
});

この4行コメント(①〜④)が、そのままハマりどころです。

ハマりどころ4つ

preventDefault() しないとブラウザに横取りされる

⌘ + S(保存)や ⌘ + W(タブを閉じる)は、ブラウザ/OS が先に反応します。取得モード中に e.preventDefault() を呼ばないと、登録する前に保存ダイアログが出たりタブが閉じたりします。取得モードのときだけ標準動作を止めるのがポイントです。

② 修飾キー単体の keydown を弾く

⌘ + C を押すとき、ユーザーはまず を押し下げます。この瞬間にも keydown が飛び、e.key"Meta" になります(Ctrl なら "Control")。修飾キーだけの段階では本体キーがまだ来ていないので、確定せずに return して次を待ちます。これを忘れると「⌘ だけのショートカット」が登録されてしまいます。

e.metaKey || e.ctrlKey で ⌘ と Ctrl を1つにまとめる

両対応の肝です。Mac で ⌘ を押すと e.metaKey、Windows で Ctrl を押すと e.ctrlKeytrue になります。これを OR で1つの論理修飾キー(mod)に畳むと、「どっちの環境で押されたか」を気にせず同じデータに落とせます。別々に持つと、後段で OS 分岐が増えていきます。

e.key は小文字・記号で来るので正規化する

e.key には押したキーの「文字」が入りますが、そのままでは表示に向かない値が来ます。

const normalizeKey = (k) => {
  if (k === " ") return "Space";
  const map = {
    ArrowLeft: "", ArrowRight: "", ArrowUp: "", ArrowDown: "",
    Backspace: "Delete",
  };
  if (map[k]) return map[k];
  return k.length === 1 ? k.toUpperCase() : k; // "a" → "A"、"F5" はそのまま
};

特にShift を押していない a キーは "a"(小文字)で来るので、表示を揃えるなら toUpperCase() が要ります。スペースは見えない " "、矢印キーは "ArrowLeft" のような長い名前で来るので記号に変換します。

表示する「文字」が欲しいなら e.key、WASD 操作のようにキーの物理位置が欲しいなら e.code(例 "KeyA")を使い分けます。今回は表示目的なので e.key + 正規化です。

取得結果を両 OS で表示する

pending をトークン配列に組み立て、表示のときだけ OS 辞書で記号に変換します。データは OS 非依存のまま持つのがコツです。

const tokensOf = (p) => {
  const t = [];
  if (p.mod) t.push("mod");
  if (p.alt) t.push("alt");
  if (p.shift) t.push("shift");
  if (p.key) t.push(p.key);
  return t; // 例: ["mod", "shift", "Z"]
};

const MAC = { mod: "", alt: "", shift: "" };
const WIN = { mod: "Ctrl", alt: "Alt", shift: "Shift" };

const render = (tokens, os) => {
  const map = os === "mac" ? MAC : WIN;
  return tokens.map((k) => map[k] ?? k).join(" + ");
};

render(["mod", "shift", "Z"], "mac"); // "⌘ + ⇧ + Z"
render(["mod", "shift", "Z"], "win"); // "Ctrl + Shift + Z"

取得(keydown)と表示(辞書変換)で同じトークン形式を共有しているので、間に変換層を挟まずに済みます。

まとめ

  • 取得モードのあいだだけ keydown を拾い、e.preventDefault() でブラウザの横取りを止める
  • e.key"Meta" / "Control" などの修飾キー単体は確定せず待つ
  • e.metaKey || e.ctrlKey⌘ と Ctrl を論理 mod に統合すると両対応がラクになる
  • e.key は小文字・記号で来るので normalizeKey で整える(位置が欲しいときは e.code
  • データは OS 非依存トークンで持ち、表示の瞬間だけ OS 辞書で記号化する

このアプローチで作ったツール(Photoshop・Illustrator・Figma・Office のショートカットをアプリ別に一覧し、★で自分専用のチートシートを作れる): ショートカットキー チートシート使い方ガイド
Web制作・SEOツール開発の技術情報サイト: CodeQuest.work

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?