Aguarde...

25 de maio de 2023

Detectando se um evento foi acionado por um usuário ou por JavaScript

Detectando se um evento foi acionado por um usuário ou por JavaScript

Em um projeto recente, finalmente encontrei uma solução para um problema com o qual me deparei várias vezes: ao ouvir eventos em JavaScript, como posso saber se um evento foi acionado diretamente por um usuário ou por meu código?

Eu estava aprimorando um  video elemento para executar uma ação especial sempre que um usuário reproduzisse o vídeo:

const myVideo = document.querySelector('#my-video');

myVideo.addEventListener('play', () => {
  doSomething();
});Linguagem do código: JavaScript ( javascript )

Isso funcionou muito bem! Quando um usuário reproduziu o vídeo, meu código foi capaz de responder. Eu estava pronto para encerrar o dia e ir sentar no jardim, mas havia outras melhorias que precisávamos fazer.

Eu precisava ser capaz de reproduzir programaticamente o vídeo quando um usuário executava determinadas ações. Eu liguei isso:

function playVideo() {
  // If the video's already playing, do nothing
  if (!myVideo.paused) {
    return;
  }

  myVideo.play().catch((error) => console.warn(error));
}Linguagem do código: JavaScript ( javascript )

Isso também parecia estar funcionando muito bem! Peguei meu chapéu de sol para sair … mas então percebi que havia um problema. Quando a playfunção foi chamada, ela acionou meu ouvinte de eventos. Mas eu só queria executar meu retorno de chamada quando o usuário reproduzisse o vídeo manualmente, não quando meu código acionasse a playfunção.

Como eu poderia diferenciar um usuário reproduzindo o vídeo do meu código chamando a playfunção de vídeo?

Becos-sem-saída

Eu já estive aqui antes e sabia que esse era um problema complicado. O JavaScript não fornece uma maneira fácil de distinguir eventos acionados por um usuário ou acionados por código. Comecei a pesquisar, conversar com colegas e experimentar. A princípio, tudo o que encontrei foram becos sem saída.

Se você não estiver interessado nas tentativas que não funcionaram, pode pular para a solução de trabalho.

Eventos confiáveis

A primeira parada no meu Dead End World Tour™ foi a event.isTrusted propriedade. MDN diz o seguinte sobre esta propriedade:

isTrustedpropriedade somente leitura da Event interface é um valor booleano que é truequando o evento foi gerado por uma ação do usuário e falsequando o evento foi criado ou modificado por um script ou despachado via EventTarget.dispatchEvent().

Isso soa como exatamente o que eu precisava! Posso usá-lo para saber se o evento foi gerado pela ação do usuário! Mas, meu teste contou outra história… isTrustedfoi truese um usuário pressionou o botão “Play” ou meu código foi executadomyVideo.play()

Olhar para a especificação oficial deixou isso mais claro:

isTrusted é uma conveniência que indica se um evento é despachado pelo agente do usuário (ao contrário de usar dispatchEvent()).

isTrustedé apenas falsese o evento foi despachado usando dispatchEventou uma função semelhante.

Existem outras propriedades de evento que poderíamos usar?

Algumas postagens do Stack Overflow sugeriram a verificação de propriedades especiais que estariam presentes para eventos acionados pelo usuário. Por exemplo, eventos de ponteiro têm propriedades screenXscreenYque informam onde ocorreu o clique. Se forem ambos, 0você pode ter certeza de que não foi um evento de clique acionado pelo usuário.

Infelizmente, não consegui encontrar nenhuma propriedade semelhante para usar no playevento.

Poderíamos usar um evento de clique em vez disso?

Isso levantou uma questão óbvia. Em vez disso, poderíamos nos conectar a eventos de clique? Isso também não deu certo. O videoelemento incorpora o widget do reprodutor de vídeo do navegador, que captura eventos de clique, o que significa que eles não aparecem no meu ouvinte de eventos.

Poderíamos construir um player de vídeo personalizado?

Quer dizer… eu acho…

Em teoria, poderíamos ter construído nossa própria IU de player de vídeo personalizada e ter maior controle sobre a experiência. Mas isso teria aumentado muito a complexidade do desenvolvimento, além de exigir que os usuários baixassem mais JavaScript. Também precisaríamos reproduzir todas as funcionalidades do navegador ou correríamos o risco de apresentar problemas de acessibilidade.

Esta pode ter sido a solução certa para outro projeto, mas parecia um exagero aqui.

A solução (hacky)

Eu finalmente encontrei uma postagem do Stack Overflow de Ankit Chaudhary que me apontou na direção certa. Não há nada embutido no JavaScript para nos ajudar a saber se um evento foi acionado por um usuário, mas podemos adicionar lógica para acompanhar isso nós mesmos:

const myVideo = document.querySelector('#my-video');

let videoPlayedByCode = false;

function playVideo() {
  // If the video's already playing, do nothing
  if (!myVideo.paused) {
    return;
  }

  // Record that the video playing was triggered by code
  videoPlayedByCode = true;
  myVideo.play().catch((error) => console.warn(error));
}

myVideo.addEventListener('play', () => {
  // If this event was triggered by code, return early and don't
  // perform our actions.
  if (videoPlayedByCode) {
    // But make sure to reset this variable for the next 
    // time the video plays.
    videoPlayedByCode = false;
    return;
  }

  doSomething();
});
Linguagem do código: JavaScript ( javascript )

Isso pode ser um pouco confuso à primeira vista. Aqui estão alguns cenários diferentes e como esse código lidaria com isso.

Um evento acionado pelo usuário:

  1. Um usuário pressiona “Play”.
  2. Nosso playouvinte de eventos é acionado.
  3. videoPlayedByCodeé falseassim que nosso ouvinte passa a responder à ação do usuário.

Um evento acionado por código:

  1. Nosso código chama a playVideo()função.
  2. Esta função define videoPlayedByCodetrue reproduz o vídeo.
  3. Nosso playouvinte de eventos é acionado.
  4. videoPlayedByCodeé truepara que nosso ouvinte saiba que não foi uma ação acionada pelo usuário.
  5. videoPlayedByCodeé redefinido para falso.

Tente reproduzir o vídeo abaixo usando o botão de reprodução integrado do navegador e o botão de reprodução personalizado para ver como a demonstração responde.

Como você pode ver, o navegador não oferece muita ajuda ao diferenciar eventos acionados pelo usuário e eventos acionados por código, mas com um pouco de JavaScript personalizado você mesmo pode acompanhar. Já me deparei com essa situação algumas vezes e estou feliz por finalmente ter uma solução. Espero que isso ajude você se você se deparar com um desafio semelhante.

Postado em BlogTags:
Escreva um comentário