JS30. Задание 1 JavaScript Drum Kit

Демо: https://js3001.github.io/
Код: https://github.com/js3001/js3001.github.io

Будет вот такая страничка, на которой клавишами на клавиатуре, а по моей задумке ещё и мышкой можно играть мелодию. То есть при нажатии на каждую кнопку или соответствующую кнопке клавишу должен появляться свой звук.

HTML5 (спасибо ему) ещё в 2012 году (может и раньше) разрешил добавлять звуки на страницу при помощи тега audio

Тестовая страничка, состоит из кнопки и одного звукового файла

<button id="button">Play</button>

<audio src="sound/ride.wav" id="audio"></audio>

Научим её издавать звук при клике по кнопке. Для этого используем js

Нашли кнопку и звуковой файл:

var button = document.getElementById("button");
var audio = document.getElementById("audio");

И добавили обработчик события клик

button.addEventListener("click", function() {
    audio.play();
});

Всё хорошо, но не очень. Если кликнуть на кнопку несколько раз подряд, замечаем, что звук появляется не при каждом нажатии, приходится несколько раз кликнуть на кнопку, чтобы она зазвучала
Решение нашлось неожиданно.
Я вообще искала способ остановить звук, чтобы при клике по другой кнопке эта замолчала. И нашлось свойство audio.currentTime = 0; которое устанавливает проигрывание в начало. Оказалось, что именно его не хватает, чтобы кнопка запускала звук при каждом клике.

button.addEventListener("click", function() {
    audio.currentTime = 0;
    audio.play();
});

Дополним код возможностью вызывать звук кликом на клавишу на клавиатуре

window.addEventListener("keydown", function(event) {
    if(event.keyCode == 65) {
        audio.currentTime = 0;
        audio.play();
    }
});

Магическое число event.keyCode == 65 - это код клавиши, кликом по которой вызывается звук. Узнать код клавиши можно по ссылке http://keycod.es/.

С одной кнопкой разобрались.

Здесь, конечно, остался вопрос что делать с двумя идентичными кодами, один из которых вызывается кликом мышки, а другой нажатием на клавишу. Вроде бы как хочется их объединить. Но тогда слишком сложным станет условие. Надо бы у кого-нибудь об этом спросить.

UPD:  уже знаю ответ. Чтобы избежать дублирования кода, нужно было сделать функцию для проигрывания звука, а потом вызывать её при клике по кнопке и при нажатии на клавишу.

Усложним задачу. Пусть теперь будут две кнопки и два звука. При клике по каждой из них должен появляться соответствующий звук.

Разметка

    <button id="buttonA">Play A</button>
    <button id="buttonS">Play S</button>

    <audio src="sound/ride.wav" id="audioA"></audio>
    <audio src="sound/clap.wav" id="audioS"></audio>

Код

var buttonA = document.getElementById("buttonA");
var buttonS = document.getElementById("buttonS");
var audioA = document.getElementById("audioA");
var audioS = document.getElementById("audioS");

buttonA.addEventListener("click", function() {
    audioA.currentTime = 0;
    audioA.play();
});

buttonS.addEventListener("click", function() {
    audioS.currentTime = 0;
    audioS.play();
});

window.addEventListener("keydown", function(event) {
    if(event.keyCode == 65) {
        audioA.currentTime = 0;
        audioA.play();
    }
});

window.addEventListener("keydown", function(event) {
    if(event.keyCode == 83) {
        audioS.currentTime = 0;
        audioS.play();
    }
});

Здесь есть сомнения в том, не нужно ли при клике по следующей кнопке останавливать все остальные звуки через audio.currentTime = 0; Хоть вроде бы и так играет без проблем.

Ок. Работает.
Получили прекрасный образец индусского кода.
Дело в том, что у индусов (в смысле, типичных индусов, а не тех, которые работают в Microsoft) оплата шла в зависимости от количества строчек кода, вот они и писали длииииннный код в котором много строчек.

В нашем коде на каждую кнопку приходится 12 строчек, и ещё плюс стилизация. Так что на 9 кнопок придётся 150-200 строчек кода. Для индусов хорошо, для нас - нет.

Нужно думать как сократить код.
В разметке автор явно предполагал такую возможность, у него data-key кнопки совпадает с data-key аудио и с кодом клавиши

<div data-key="65" class="key">A</div>
<audio data-key="65" src="sounds/clap.wav"></audio>

Да. Но вот как это сделать я не знаю (
С data-атрибутами я так и не разобралась.

UPD. Разобралась. Получить значение data-атрибута можно так:

var dataKey = this.dataset.key;

 Почти то же самое, что и в коде ниже, когда пишу

var id = this.id;

Зато, когда переделала data-key в id, решение нашлось:

var button = document.querySelectorAll("button");
var audio = document.querySelectorAll("audio");

for(var i = 0; i < button.length; i++) {
    button[i].addEventListener("click", function() {
        var id = this.id;
        for(var j = 0; j < button.length; j++) {
            if(audio[j].id == id) {
                audio[j].currentTime = 0;
                audio[j].play();
            }
        }
    });
}


window.addEventListener("keydown", function(event) {
    var keyCode = event.keyCode;
    for(var j = 0; j < button.length; j++) {
        if(audio[j].id == keyCode) {
            audio[j].currentTime = 0;
            audio[j].play();
        }
    }
});

Осталось добавить подсветку кнопок при клике, для этого добавляем им класс sound

Ну вот, как-то так

var button = document.querySelectorAll("button");
var audio = document.querySelectorAll("audio");

function clear() {
    for(var i = 0; i < button.length; i++) {
        if(button[i].classList.contains("sound")) {
            button[i].classList.remove("sound");
        }
    }
}

for(var i = 0; i < button.length; i++) {
    button[i].addEventListener("click", function() {
        clear();
        var id = this.id;
        this.classList.add("sound");
        for(var j = 0; j < button.length; j++) {
            if(audio[j].id == id) {
                audio[j].currentTime = 0;
                audio[j].play();
            }
        }
    });
}


window.addEventListener("keydown", function(event) {
    var keyCode = event.keyCode;
   
    for(var i = 0; i < button.length; i++) {
        if(button[i].id == keyCode) {
            clear();
            button[i].classList.add("sound");
        }
    }
   
    for(var j = 0; j < button.length; j++) {
        if(audio[j].id == keyCode) {
            audio[j].currentTime = 0;
            audio[j].play();
        }
    }
});


Интересно, будет ли работать?
Удивительно, но работает!
Ура.

var button = document.querySelectorAll(".key");
var audio = document.querySelectorAll("audio");

function clear() {
    for(var i = 0; i < button.length; i++) {
        if(button[i].classList.contains("playing")) {
            button[i].classList.remove("playing");
        }
    }
}

for(var i = 0; i < button.length; i++) {
    button[i].addEventListener("click", function() {
        clear();
        var id = this.id;
        this.classList.add("playing");
        for(var j = 0; j < button.length; j++) {
            if(audio[j].id == id) {
                audio[j].currentTime = 0;
                audio[j].play();
            }
        }
    });
}


window.addEventListener("keydown", function(event) {
    var keyCode = event.keyCode;

    for(var i = 0; i < button.length; i++) {
        if(button[i].id == keyCode) {
            clear();
            button[i].classList.add("playing");
        }
    }

    for(var j = 0; j < button.length; j++) {
        if(audio[j].id == keyCode) {
            audio[j].currentTime = 0;
            audio[j].play();
        }
    }
});