Introdução
Eaí galera! Nesses tempos de crise mundial acabei me perdendo um pouco na agenda de artigos que tinha planejado em escrever.
Espero que a maioria de vocês esteja em casa, seguros e aproveitando o tempo para aprender coisas novas ou melhorar suas habilidades.
Felizmente as coisas já estão voltando ao normal por aqui e estou conseguindo me organizar melhor.
Nesse artigo, quero mostrar para vocês como criar um componente bastante útil, e que uso em quase todos os sites que faço, vou ensinar a criar um timer.
O que é um Timer?
Um timer (ou temporizador) é um objeto usado para rodar uma função a cada intervalo X de tempo.
Antes de começar
Primeiro, quero deixar claro para o que esse timer deve ser usado e para o que ele não deve ser usado.
Quando usar
Use para controlar componentes como slideshows, carousel, rodar animações, etc. Resumindo, lugares em que você precise controlar apresentação, onde o timer serve para te auxiliar nesse objetivo.
Quando não usar
Não use esse timer se você depende que o tempo seja preciso.
Por que o Timer não é preciso?
Como o JavaScript no navegador é executado em uma única thread, cada bloco de código espera o bloco anterior, causando um atraso de alguns milissegundos.
Ou seja, para cada execução da função do seu timer, o JavaScript vai aguardar um pouquinho antes de executar a próxima função. Esse tempo depende do que a sua função faz, do intervalo de execução e do poder de processamento da CPU do usuário.
Com o passar do tempo, esses milissegundos vão acumulando e começam a fazer uma diferença enorme no resultado final.
Em detalhes
Esse JSFiddle demonstra a diferença entre as funções setTimeout
e setInterval
, que são as duas funções que controlam tempo de execução no JavaScript.
Se você esperar alguns minutos vai ver que o número de vezes que cada função é chamada vai se diferenciando muito.
De qualquer maneira, nenhuma das duas mantém a precisão de uma chamada por segundo, como é esperado.
Esse artigo do John Resig, criador da jQuery, explica com mais detalhes como isso funciona no caso de se interessar.
Criando o nosso Timer
Primeiro vamos declarar o objeto que vai receber uma função callback e o tempo de duração do timer, que vou chamar de delay.
1function Timer(callback, delay) {}
Note que estamos criando um objeto, e não uma função. Se você ainda não dominou muito bem o conceito de objeto em JavaScript, dá uma olhada nessa documentação.
Instanciando o nosso Timer
Agora vamos inicializar o timer, assim você pode ir testando os métodos que iremos implementar:
1function Timer(callback, delay) {} 2 3var timer = new Timer(function () { 4 console.log("Chamou a função!") 5}, 3000)
Método resume()
Vamos começar pela função que inicia o timer. A primeira coisa a fazer é usar a função setTimeout
presente na web API dos navegadores.
Essa função recebe como parâmetros uma função e o tempo que se deve esperar para executar tal função, e retorna um ID, que vamos guardar para usar mais tarde.
Mais informações sobre a setTimeout
você encontra na documentação.
1function Timer(callback, delay) { 2 var timerId 3 4 var resume = function () { 5 timerId = window.setTimeout(function () { 6 resume() 7 callback() 8 }, delay) 9 } 10} 11 12var timer = new Timer(function () { 13 console.log("Chamou a função!") 14}, 3000)
Aqui estamos criando uma função recursiva - função que chama ela mesma - nomeada resume()
.
Ou seja, a cada intervalo de tempo, ou delay
, a função resume()
chama a si mesma e em seguida a nossa função de callback()
, fazendo com que essa seja executada infinitamente, a cada intervalo.
Porém, no momento ainda não está funcionando, precisamos fazer com que a função resume()
seja um método do objeto e então chamá-la.
1function Timer(callback, delay) { 2 var timerId 3 4 var resume = function () { 5 timerId = window.setTimeout(function () { 6 resume() 7 callback() 8 }, delay) 9 } 10 this.resume = resume 11} 12 13var timer = new Timer(function () { 14 console.log("Chamou a função!") 15}, 3000) 16timer.resume()
Note que foram adicionadas as linhas:
this.resume = resume
que faz com que a funçãoresume
deixe de ser privada para ser pública, podendo ser invocada pelo objeto.timer.resume()
que efetivamente chama o métodoresume()
, iniciando o nosso timer, nesse ponto, você deveria ver no seu console uma mensagem sendo exibida a cada 3 segundos.
Método pause()
Muito bem, agora vamos complicar um pouquinho e adicionar o método pause()
:
1function Timer(callback, delay) { 2 var timerId 3 4 this.pause = function () { 5 window.clearTimeout(timerId) 6 } 7 8 var resume = function () { 9 timerId = window.setTimeout(function () { 10 resume() 11 callback() 12 }, delay) 13 } 14 this.resume = resume 15} 16 17var timer = new Timer(function () { 18 console.log("Chamou a função!") 19}, 3000) 20timer.resume()
Problema 1
A primeira a coisa a fazer no método pause é... parar tudo! Por mais surpreendente que possa parecer, é isso que temos de fazer.
Então nós temos o nosso timeout rodando na função resume()
, certo? Pra pará-lo precisamos removê-lo completamente, e fazemos isso usando a função clearTimeout()
da web API.
Ela recebe como parâmetro exatamente o ID retornado do timeout que desejamos parar.
Assim, resolvemos o primeiro problema, paramos o nosso timer.
Problema 2
Beleza, mas ainda temos o seguinte problema: se chamamos a função resume()
novamente, ela não vai começar de onde parou, mas vai criar um novo timeout iniciando do nosso delay de novo!
Não é isso o que queremos, a gente quer que o timer volte a rodar de onde parou.
Para isso, vamos precisar adicionar algumas variáveis novas e fazer algumas modificações no método resume()
e pause()
:
1function Timer(callback, delay) { 2 var timerId 3 var start 4 var remaining = delay 5 6 this.pause = function () { 7 window.clearTimeout(timerId) 8 remaining -= new Date() - start 9 } 10 11 var resume = function () { 12 start = new Date() 13 timerId = window.setTimeout(function () { 14 remaining = delay 15 resume() 16 callback() 17 }, remaining) 18 } 19 this.resume = resume 20} 21 22var timer = new Timer(function () { 23 console.log("Chamou a função!") 24}, 3000) 25timer.resume()
Calma calma... eu sei que foram várias modificações. Vamos passar por cada uma delas pra você entender direitinho:
Novas variáveis
start
vai ser usada para marcar quando a funçãoresume()
foi chamada.remaining
vai ser usada para marcar o tempo que falta para o timer terminar de rodar.
Alterações em resume()
- Adicionamos a linha
start = new Date()
que justamente guarda a hora, minuto, segundo e milissegundo que a funçãoresume()
foi chamada. - Substituímos a variável
delay
da chamada desetTimeout
pela variávelremaining
, isso porque queremos que ao chamarresume()
depois de chamarmospause()
, o novo timeout tenha o tenha o tempo que falta e não recomece do tempo total. - Finalmente, adicionamos a linha
remaining = delay
dentro do timeout, porque assim que ele acabar, o novo timeout deve começar do tempo total novamente.
Eu sei que é muita informação, tente pensar em como o algoritmo está funcionando e leia de novo se achar necessário.
Alterações em pause()
- Adicionamos a linha
remaining -= new Date() - start
que justamente faz a conta do tempo restante, subtraindo do tempo restante a diferença entre a hora atual -new Date()
- da hora em que chamamosresume()
, ou seja, a variávelstart
.
Agora sim, quando chamamos pause()
e resume()
novamente, o timer volta a funcionar a partir de onde parou.
Método reset()
Finalmente, adicionamos o último método:
1function Timer(callback, delay) { 2 var timerId 3 var start 4 var remaining = delay 5 6 this.pause = function () { 7 window.clearTimeout(timerId) 8 remaining -= new Date() - start 9 } 10 11 var resume = function () { 12 start = new Date() 13 timerId = window.setTimeout(function () { 14 remaining = delay 15 resume() 16 callback() 17 }, remaining) 18 } 19 this.resume = resume 20 21 this.reset = function () { 22 remaining = delay 23 } 24} 25 26var timer = new Timer(function () { 27 console.log("Chamou a função!") 28}, 3000) 29timer.resume()
Tudo o que ele faz é resetar a variável de tempo restante.
Tenha em mente que esse método por si só não reinicia o timer, ele apenas reseta.
Porém, com esses 3 métodos é possível reiniciar o timer só chamando timer.pause()
, timer.reset()
e timer.resume()
nessa ordem.
Exemplo prático
Para demonstrar melhor como esse timer funciona, fiz esse pen:
Como eu disse lá no começo, esse objeto é muito útil para slideshows e carousels, porque se quisermos fazer eles avançarem sozinhos é só chamar o método que faz avançar de dentro do timer, ganhando outras funcionalidades para controlar melhor esses componentes com pause()
e resume()
.
Conclusão
Espero que tenha gostado do post. Claro que você pode modificar esse código e melhorá-lo de acordo com as suas necessidades, nem sempre precisamos de todos os métodos ou usá-los da mesma forma. Porém, é assim que costumo usar nos meus componentes já tem algum tempo.
É isso aí galera, fique em casa e até o próximo artigo!