ilyachalov (ilyachalov) wrote,
ilyachalov
ilyachalov

Categories:

JavaScript: одновременное нажатие клавиш

Разбираю своё решение задачи «Отследить одновременное нажатие» к подразделу 3.4 «Клавиатура: keydown и keyup» второй части учебника по JavaScript.

Требуется написать функцию с названием runOnKeys. В постановке задачи сказано, что она должна запускать другую функцию (переданную ей в первом параметре func) при одновременном нажатии на клавиатуре нескольких клавиш (предполагается, что их количество может быть любым) с кодами, переданными ей в параметрах, начиная со второго. Приведен пример вызова этой функции:
runOnKeys(
  () => alert("Привет!"),
  "KeyQ",
  "KeyW"
);

Видимо, авторы задачи запутались в формулировке постановки задачи. На самом деле, функция runOnKeys будет запускаться на HTML-странице, но она не будет запускать переданную ей в первом параметре функцию func. Я это понял, когда обдумывал способы решения этой задачи.

Запуск функции runOnKeys должен придавать HTML-странице способность выполнять функцию func при одновременном нажатии заданных клавиш на клавиатуре. Собственно, запуск функции func будет производить пользователь, одновременно нажав на заданные клавиши на клавиатуре.

Как функция runOnKeys придаст HTML-странице (document) эту способность? Она должна повесить соответствующую функцию-обработчик на какие-то события, которые произойдут на HTML-странице. Так как одновременное нажатие на несколько клавиш на клавиатуре вызовет событие keydown, значит, функцию-обработчик следует вешать именно на это событие. Пишем код:
function runOnKeys() {

    // вешаем функцию-обработчик на событие "keydown" на HTML-странице
    document.addEventListener("keydown", function (event) {
        //...
    });

}

По идее, чтобы начать обработку одновременного нажатия нескольких клавиш на клавиатуре, нам нужно знать, когда последовательность нажатия нескольких клавиш закончится. Мы можем начать обработку после нажатия последней клавиши в последовательности. Но в функции-обработчике, повешенной на событие keydown, мы не можем это отследить, потому что эта функция-обработчик обрабатывает каждый раз только нажатие одной из клавиш в последовательности, она не имеет информации о том, последняя ли это клавиша в последовательности или нет.

Когда мы сможем понять, что последовательность одновременного нажатия клавиш окончилась? Когда пользователь начнет отпускать клавиши, то есть при генерации первого события keyup после одновременного нажатия клавиш. Значит, нам нужно отслеживать и отпускание клавиш, то есть повесить функцию-обработчик на событие keyup. Дополним код:
function runOnKeys() {

    // вешаем функцию-обработчик на событие "keydown" на HTML-странице
    document.addEventListener("keydown", function (event) {
        //...
    });

    // вешаем функцию-обработчик на событие "keyup" на HTML-странице
    document.addEventListener("keyup", function (event) {
        //...
    });

}

Мы можем при событии keydown запоминать нажатые клавиши, а при событии keyup обработать их. Для запоминания одновременно нажатых клавиш я решил использовать массив. Дополним код:
function runOnKeys() {

    let arrChars = [];                    // массив одновременно нажатых клавиш

    document.addEventListener("keydown", function (event) {
        arrChars.push(event.code);        // запоминаем код нажатой и пока еще не отпущенной клавиши
    });

    document.addEventListener("keyup", function (event) {
        if (arrChars.length == 0) return; // нечего обрабатывать, завершаем функцию

        // ...

        arrChars.length = 0;              // очистим массив одновременно нажатых клавиш
    });

}

Событий keyup после отпускания одновременно нажатых клавиш будет сгенерировано столько же, сколько было отпущенных клавиш. Обработку одновременного нажатия клавиш мы запустим при первом событии keyup, после чего очистим массив одновременно нажатых клавиш. Последующие события keyup после первого проигнорируем с помощью инструкции if (arrChars.length == 0) return;.

Я потестировал полученный код в браузере, отслеживая состояние массива одновременно нажатых клавиш с помощью инструкции console.log(arrChars);, и заметил, что при достаточно продолжительном нажатии клавиш генерируется повторное событие keydown, из-за чего в массиве одновременно нажатых клавиш некоторые клавиши многократно дублируются (этот момент был отмечен и в обсуждаемом учебнике). Эти повторы можно отбросить. Дополним код (я отметил изменения красным цветом):
function runOnKeys() {

    let arrChars = [];                    // массив одновременно нажатых клавиш

    document.addEventListener("keydown", function (event) {
        if (event.repeat) return;         // повторы не обрабатываем
        arrChars.push(event.code);        // запоминаем код нажатой и пока еще не отпущенной клавиши
    });

    document.addEventListener("keyup", function (event) {
        if (arrChars.length == 0) return; // нечего обрабатывать, завершаем функцию

        // ...

        arrChars.length = 0;              // очистим массив одновременно нажатых клавиш
    });

}

Осталось написать обработку одновременного нажатия клавиш. От нас требуется при одновременном нажатии заданных клавиш запустить заданный код. До сих пор я не рассматривал параметры нашей функции runOnKeys. Первым параметром с названием func, очевидно, будет заданный код. Чтобы прописать для функции runOnKeys множество параметров, начиная со второго, задающих определенную последовательность одновременно нажатых клавиш, мне пришлось вернуться к первой части обсуждаемого учебника и повторить подраздел 6.2 «Остаточные параметры и оператор расширения». Множество параметров с точно не определенным их количеством можно задать с помощью так называемых «остаточных параметров», используя многоточие. Дополним код (я отметил изменения красным цветом):
function runOnKeys(func, ...args) {

    let arrChars = [];                    // массив одновременно нажатых клавиш

    document.addEventListener("keydown", function (event) {
        if (event.repeat) return;         // повторы не обрабатываем
        arrChars.push(event.code);        // запоминаем код нажатой и пока еще не отпущенной клавиши
    });

    document.addEventListener("keyup", function (event) {
        if (arrChars.length == 0) return; // нечего обрабатывать, завершаем функцию

        // ...

        arrChars.length = 0;              // очистим массив одновременно нажатых клавиш
    });

}

Теперь нам нужно проверить, есть ли заданные клавиши среди одновременно нажатых. И, если заданные клавиши есть среди одновременно нажатых, запустим заданный код func. Дополним код (я отметил изменения красным цветом):
function runOnKeys(func, ...args) {

    let arrChars = [];                    // массив одновременно нажатых клавиш

    document.addEventListener("keydown", function (event) {
        if (event.repeat) return;         // повторы не обрабатываем
        arrChars.push(event.code);        // запоминаем код нажатой и пока еще не отпущенной клавиши
    });

    document.addEventListener("keyup", function (event) {
        if (arrChars.length == 0) return; // нечего обрабатывать, завершаем функцию

        let runFunc = true;
        for (let arg of args) {           // нажаты ли одновременно отслеживаемые клавиши
            if (!arrChars.includes(arg)) {
                runFunc = false;
                break;
            }
        }
        if (runFunc) func();              // если нажаты, запускаем заданный код

        arrChars.length = 0;              // очистим массив одновременно нажатых клавиш
    });

}

args — это массив кодов отслеживаемых клавиш. Я перебираю их в цикле и проверяю, есть ли эти коды в массиве одновременно нажатых клавиш arrChars с помощью метода includes. Если установлено, что какая-либо из отслеживаемых клавиш отсутствует в массиве одновременно нажатых клавиш, цикл прерывается, а этот факт запоминается в переменной runFunc. Заданный код func запускается (или не запускается) в зависимости от значения переменной runFunc.

Вот, собственно, и всё. Я протестировал этот код у себя в браузере, он работает. Авторы задачи решили ее несколько по-другому, хотя общие принципы те же. Вместо массива они используют множество значений Set, а обработку заданного кода умудрились вставить в функцию-обработчик события keydown. Очень интересное решение.
Tags: Образование, Программирование
Subscribe

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments