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 play
funçã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 play
função.
Como eu poderia diferenciar um usuário reproduzindo o vídeo do meu código chamando a play
funçã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:
A
isTrusted
propriedade somente leitura daEvent
interface é um valor booleano que étrue
quando o evento foi gerado por uma ação do usuário efalse
quando o evento foi criado ou modificado por um script ou despachado viaEventTarget.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… isTrusted
foi true
se 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 usardispatchEvent()
).
isTrusted
é apenas false
se o evento foi despachado usando dispatchEvent
ou 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 screenX
e screenY
que informam onde ocorreu o clique. Se forem ambos, 0
você 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 play
evento.
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 video
elemento 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:
- Um usuário pressiona “Play”.
- Nosso
play
ouvinte de eventos é acionado. videoPlayedByCode
éfalse
assim que nosso ouvinte passa a responder à ação do usuário.
Um evento acionado por código:
- Nosso código chama a
playVideo()
função. - Esta função define
videoPlayedByCode
etrue
reproduz o vídeo. - Nosso
play
ouvinte de eventos é acionado. videoPlayedByCode
étrue
para que nosso ouvinte saiba que não foi uma ação acionada pelo usuário.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.