Solucionado (ver solução)
Solucionado
(ver solução)
3
respostas

Adicionar Click Event em APP de Piano

Boa Noite pessoal, to precisando de uma luz para poder refatorar um codigo que fiz e não consigo de maneira alguma fazer de forma mais eficiente.

Interface do Programa de PianoBasicamente, cada letra toca um som ao ser pressionada as teclas correspondentes. Fiz a implementação dessa funcionalidade dbuenas, e tentei adicionar tambem para poder reconhecer o click do mouse em cima das teclas, oque tambem consegui, mas nessa parte meu codigo ficou muito grande e repetitivo, gostaria de uma ideia de refatoração. Tentei utilizar arrays, objetos, for, etc, mas não consegui aplicar a logica necessaria.

Vou deixar o link do gitHub para esse projeto já que não consegui anexar os arquivos.

https://github.com/GusttavoCDN/Drum-Kit

3 respostas

Olá, Gustavo, tudo bem?

Desde já parabéns pela iniciativa de fazer seu próprio projeto!! É uma excelente forma de pôr em prática seus conhecimentos e aprender ainda mais. Gostei muito do seu projeto e obrigado por trazer sua dúvida até a gente! Continue assim! :)

Então vamos lá? Eu pensei em uma lógica que acho que você vai curtir!

Ajuste


Antes, vamos ajeitar só uma coisa. Na sua função para o evento do teclado, você usou a propriedade keyCode do event, mas ela já está depreciada! Como pode ver aqui: KeyboardEvent.keyCode

function playSound(event) {
    const audio = document.querySelector(`audio[data-key="${event.keyCode}"]`);
    const key = document.querySelector(`div[data-key="${event.keyCode}"]`);
    if (!audio) return;

    key.classList.add('playing');
    audio.currentTime = 0;
    audio.play();

    /*setTimeout(function(){
        key.classList.remove("playing")
    }, 200)*/
}

A própria documentação recomenda agora o uso da propriedade code, então vamos utilizá-la, certo? Troque as duas primeiras linhas do seu código da função playSound pelas seguintes:

    const audio = document.querySelector(`audio[data-key="${event.code}"]`);
    const key = document.querySelector(`div[data-key="${event.code}"]`);

Mas a propriedade code retorna valores diferentes da keyCode! Em vez de "65" para a letra A, por exemplo, teremos "KeyA". Mas isso é fácil de contornar ajeitando os data-key do seu HTML:

<div class="keys">
        <div data-key="KeyA" class="key a_div">
            <kbd class="a_letter">A</kbd>
            <span class="sound a_sound">clap</span>
        </div>
        <div data-key="KeyS" class="key s_div">
            <kbd class="s_letter">S</kbd>
            <span class="sound s_sound">hihat</span>
        </div>
        <div data-key="KeyD" class="key d_div">
            <kbd class="d_letter">D</kbd>
            <span class="sound d_sound">kick</span>
        </div>
        <div data-key="KeyF" class="key div">
            <kbd class="f_letter">F</kbd>
            <span class="sound f_sound">openhat</span>
        </div>
        <div data-key="KeyG" class="key g_div">
            <kbd class="g_letter">G</kbd>
            <span class="sound g_sound">boom</span>
        </div>
        <div data-key="KeyH" class="key h_div">
            <kbd class="h_letter">H</kbd>
            <span class="sound h_sound">ride</span>
        </div>
        <div data-key="KeyJ" class="key J_div">
            <kbd class="j_letter">J</kbd>
            <span class="sound j_sound">snare</span>
        </div>
        <div data-key="KeyK" class="key k_div">
            <kbd class="k_letter">K</kbd>
            <span class="sound k_sound">tom</span>
        </div>
        <div data-key="KeyL" class="key l_div">
            <kbd class="l_letter">L</kbd>
            <span class="sound l_sound">tink</span>
        </div>
    </div>

    <audio data-key="KeyA" src="sounds/clap.wav"></audio>
    <audio data-key="KeyS" src="sounds/hihat.wav"></audio>
    <audio data-key="KeyD" src="sounds/kick.wav"></audio>
    <audio data-key="KeyF" src="sounds/openhat.wav"></audio>
    <audio data-key="KeyG" src="sounds/boom.wav"></audio>
    <audio data-key="KeyH" src="sounds/ride.wav"></audio>
    <audio data-key="KeyJ" src="sounds/snare.wav"></audio>
    <audio data-key="KeyK" src="sounds/tom.wav"></audio>
    <audio data-key="KeyL" src="sounds/tink.wav"></audio>

Seu código continuará funcionando e é até mais legível, não é? Bem, agora vamos refatorar a parte dos eventos de clique!

solução!

Refatoração dos eventos de clique


Sua lógica foi: adicionar um evento de click no próprio body da página e verificar se o target era um dos elementos que deveria tocar o áudio quando fosse clicado. Mas para deixar dinâmico, iremos na verdade adicionar um evento de clique próprio para cada uma das divs .key, ok? Assim poderemos aproveitar algumas informações da própria div. Ah, e não vamos mais precisar do código a partir da linha 28 do seu arquivo script.js do github.

Em seguida, vamos nos aproveitar do seu código que já seleciona as keys:

const keys = Array.from(document.querySelectorAll('.key'));

E podemos adicionar o seguinte código:

keys.forEach(function (key, keyPosition) {
    key.addEventListener('click', function () {
        console.log('Clicou na div key!');
        // Falta código para tocar o áudio
    });
});

Você já pode testar e ver que imprimimos "Clicou na div key!" no console sempre que clicamos em qualquer das divs. Além disso, não precisamos nos preocupar em também adicionar o evento de clique para os filhos das divs .key, graças ao Event bubbling do javascript, que faz com que o evento seja acionado tanto pelo próprio elemento quanto pelos seus filhos.

Note também que eu passei dois parâmetros para a função anônima: key e keyPosition. Por padrão, podemos passar dois parâmetros na função que usamos dentro do forEach, sendo que nesse caso key representa o elemento atual que estamos iterando e keyPosition é a posição do elemento atual também.

Por fim, agora vamos nos aproveitar dessa sua linha de código:

const audios = Array.from(document.querySelectorAll('audio'))

E perceba que os elementos desse array audios têm as mesmas posições correspondentes que os elementos do array keys. Vamos tirar vantagem disso e retornar para o código do forEach, substituindo pelo seguinte:

keys.forEach(function (key, keyPosition) {
    key.addEventListener('click', function () {
        console.log('Clicou na div key!');
        key.classList.add('playing');
        audios[keyPosition].currentTime = 0
        audios[keyPosition].play()
    });
});

Ou seja, se clicarmos na primeira div .key, que tem posição 0, também tocaremos o áudio da posição 0, e assim por diante! Conseguimos reduzir 50 linhas de código com apenas esse trecho! Em caso de dúvida, aqui está o código JS completo:

function removeTransition(event) {
    if (event.propertyName !== 'transform') return;
    event.target.classList.remove('playing');
}

function playSound(event) {
    const audio = document.querySelector(`audio[data-key="${event.code}"]`);
    const key = document.querySelector(`div[data-key="${event.code}"]`);

    if (!audio) return;

    key.classList.add('playing');
    audio.currentTime = 0;
    audio.play();

    /*setTimeout(function(){
        key.classList.remove("playing")
    }, 200)*/
}

const audios = Array.from(document.querySelectorAll('audio'))
const keys = Array.from(document.querySelectorAll('.key'));

keys.forEach(key => key.addEventListener('transitionend', removeTransition));

window.addEventListener('keydown', playSound);

keys.forEach(function (key, keyPosition) {
    key.addEventListener('click', function () {
        console.log('Clicou na div key!');
        key.classList.add('playing');
        audios[keyPosition].currentTime = 0
        audios[keyPosition].play()
    });
});

É isso, espero ter te ajudado! Você está indo muito bem e até aprendi novas informações com o seu código. Continue assim e bons estudos!

Haha, tão simples. Muito obrigado cara, me deu uma luz depois de ver isso. Seguindo como base a sua implementação eu consegui implementar de uma maneira um pouco diferente, apenas para tentar deixar um pouco mais legivel.

keys.forEach(key => key.addEventListener('click', function(event){
    clickPlay(key.dataset.key) 

    function clickPlay(keycode) {
        const audio = document.querySelector(`audio[data-key="${keycode}"]`)
        const key = document.querySelector(`.key[data-key="${keycode}"]`)

        key.classList.add('playing');
        audio.currentTime = 0;
        audio.play();
    }
}))

Você me ajudou a entender melhor o forEach, já havia o visto, mas ainda não entendia muito bem como usar ele para manipular arrays. Os exemplos para explicar esses métodos em documentaçãoes como a do Mozilla mesmo tambem sempre usam arrows functions, oque tambem me buga um pouco kkkk Mas gastando a cabeça um pouco acho ter entendi melhor agora.

E obrigado tambem por sinalizar a questão do KeyCode.