Escrevendo uma função de flexibilização; uma história ligeiramente interessante

Devo dizer, desde logo, que não existe um ponto real para esta publicação no blog. Eu tinha uma coisa a fazer, eu fiz o assunto e estava moderadamente satisfeito com o resultado. Eu gosto de ler esses contos de outros desenvolvedores, então eu pensei que eu compartilharia minha história.

Emparelhamento de música recomendado para esta publicação: o novo álbum Awolnation.

Versão curta

Mover algo suavemente de um lugar para outro é algo que eu preciso fazer na maioria dos sites em que trabalho. Na maioria das vezes não é mais do que deslizar a página para uma posição ou deslizar um menu de navegação.

O tamanho e a complexidade das soluções pré-embaladas pareciam estar fora de proporção com a simplicidade da tarefa. Então eu fiz o meu próprio.

Aqui está o resultado em npm e GitHub e CodePen .

Versão longa

De volta quando esta história ocorreu pela primeira vez, eu assumi que as funções de flexibilização eram bastante pesadas em matemática e estava feliz em deixá-las nas mãos de pessoas mais inteligentes do que eu.

No começo, usei jQuery e sua função animate() . Tenho vergonha de dizer que, em um site, carreguei 60 KB de jQuery para nenhum outro propósito que rola a página com facilidade.

Então npm tornou-se uma coisa e eu usei tudo o que apareceu nos resultados da pesquisa npm para "facilitar".

Então eu aprendi que o desempenho da web era uma coisa que os usuários se preocupavam, então eu corri algumas tentativas de tempo e lançou um "eek" envergonhado.

Aqui estão os tempos de carregamento de um site com o qual estou trabalhando com um pacote típico de flexibilização e a casa feita uma que eu revelarei muito lentamente nesta publicação.

A mediana é de cinco corridas cada

Eu não sei sobre você, mas eu considero adicionar ~ 100ms ao meu tempo de carregamento um negócio bastante grande. Lembre-se de que a Amazon calculou que adicionar 100ms custou 1% nas vendas.

(Seja ou não 100ms importante para você, deve ser um fator da receita do seu site. Se você receber 20 hits por dia em um site sobre técnicas de meditação, então, 100 ms provavelmente não é nada para se preocupar – se alguma coisa é uma boa prática.)

Ah e 20 KB – em um site de 150 KB – apenas para percorrer a página é ultrajante.

Deixe-me ser claro, porém, não é tanto a culpa dos pacotes, eles são construídos para fazer muito mais. A questão é que o código que eu preciso é de apenas algumas centenas de bytes, mas vem inextrincável com dezenas de milhares de bytes de outros códigos feitos para outras pessoas. Se eu usasse um pacote como este, seria minha própria falha que meu tempo de carga aumentasse.

Começando: uma transição linear

Eu sei que minha função de flexibilização vai precisar de pelo menos três parâmetros: um valor inicial, um valor final e uma duração. Eu acho que isso é um bom começo:

Um dia, AI irá preencher automaticamente esse comentário "fazer algo" e todos podemos ir para casa

Isso vai precisar de algum tipo de loop que execute uma operação uma e outra vez desde o valor de início até o valor final. Eu usarei requestAnimationFrame para isso para que cada loop do código seja executado uma vez por quadro.

Primeiro, um guia de animação de um único parágrafo para levar todos a velocidade. A tela em que seu rosto está apontado atualmente é provavelmente 60Hz, o que significa que ele atualiza 60 vezes a cada segundo. Essa é uma característica do hardware real. Os sistemas operacionais e os navegadores não calculam constantemente a cor de cada pixel na tela, eles "apenas" fazem isso quando precisam, o que é 60 vezes por segundo. Ou uma vez a cada 16 milissegundos. Isso é chamado de quadro ou quadro de animação e requestAnimationFrame diz explicitamente ao navegador, faça isso funcionar para a próxima vez que você está prestes a atualizar os pixels na tela. Ao chamá-lo novamente e novamente, você criou um loop que só é executado uma vez por cada vez que o navegador atualiza a tela, sendo assim o trabalho mínimo para a taxa de quadros máxima.

No trecho acima, tenho uma função ( step ) para fazer o trabalho real, que eu chamo repetidamente (passando-o para requestAnimationFrame ) desde que alguma condição seja verdadeira. Isso é mais ou menos como um while loop (com o mesmo risco de um ciclo infinito se você tem algo errado).

Eu incremento currentValue em cada loop, então eu posso ter certeza de que eventualmente será mais do que endValue e que irá quebrar o loop.

Quero que esta função seja utilizável em todos os tipos de situações. Então, ao invés de ter qualquer lógica de atualização embutida na função, vou passar apenas um retorno de chamada que será chamado em cada loop / step / tick / whatever. Então, o "quê" da flexibilização é deixado ao código que chama a função.

Eu tenho uma regra pessoal (apenas uma) que, se uma função possui mais de três parâmetros, uso "parâmetros nomeados". Então eu posso acompanhar o que estou passando, e facilita o gerenciamento de parâmetros opcionais (uma vez que o usuário não precisa passar nos valores null espaço reservado para satisfazer a ordem dos parâmetros).

Aqui estão cinco pontos de bala sobre o snippet de código abaixo dos pontos de bala:

  • A função agora possui um único parâmetro, um objeto. Esse objeto pode ter as propriedades startValue , endValue , durationMs e onStep .
  • O retorno de chamada onStep que é chamado para cada etapa com o valor atual.
  • Estou usando sintaxe de desestruturação de objetos para descompactar essas propriedades em variáveis.
  • Estou usando a sintaxe de parâmetro padrão ( = ) para definir alguns padrões. (O Dr. Axel descreveu os parâmetros nomeados e padrão dois anos antes de começar a aprender JavaScript, mas de alguma forma eles ainda se sentem como O futuro.)
  • Quando o 'loop' terminou, ligue onStep uma última vez com o endValue . Isso garante que a coisa termine onde você pediu, mesmo que haja problemas de arredondamento.

Os leitores com olhos de águia verão a maior falha. Eu vou chegar mais tarde.

Agora eu tenho uma função que eu posso chamar assim:

Com certeza, isso custam valores entre 1.000 e 2.000

Observe que não passei em durationMs , portanto, será padrão para 200 milissegundos. Uma duração bonita, sem sentido.

Se eu estivesse me sentindo frisky eu poderia imprimir esses valores como um gráfico bonito no console:

Dá-me:

Eu finalmente encontrei um uso para formatação de console

Infelizmente, a função de flexibilização acima está agora na prisão do código para quebrar a primeira lei de Newton.

Facilitando as coisas difíceis

Para parecer mais natural, quero minha transição para simular uma pequena inércia e impulso; Eu quero que ele tenha a mesma física que deslizar uma cerveja ao longo de uma barra (física normal, não física de Ted Danson ).

Especificamente, eu quero que ele se mova lentamente no início, mais rápido no meio, e, gradualmente, fica paralisado no final.

Uma vez que o tempo para cada passo é fixo (~ 16ms), e a velocidade é uma ilusão perpetrada pelo nosso sistema visual, podemos focar unicamente na distância movida em cada etapa.

Um exemplo: digamos que eu quero deslizar algo por 100 pixels em mais de 100 etapas. No exemplo linear, cada passo seria uma distância de 1 pixel.

O eixo dos e é a distância, ou a velocidade.

Para obter um efeito de atenuação, preciso ajustar cada uma dessas etapas para que elas sejam números menores em cada uma das extremidades.

Como a sorte teria, o JavaScript tem uma função incorporada que me dará exatamente esse comportamento.

Math.sin() é a função da qual eu falo. Se eu passar em zero , eu obtenho zero . Quando estou a meio da animação, eu passo na metade do PI e vou pegar um. Quando faço 100% com a animação, passo 100% de PI e irei ter zero novamente.

Então eu posso modificar minha função linear e simplesmente multiplicar cada passo por Math.sin(progress * Math.PI) .

Estrondo.

Mas há um problema. No momento em que o meu pequeno que tenha movido cada um desses passos, ele só viajou cerca de 63.657 pixels. Isso não é o suficiente. Não é suficientemente longe.

Eu sei o que você está pensando: David, basta multiplicar cada passo pela metade do PI!

Embora eu aprecie o conselho que eu imaginei que você me desse, e isso realmente faz todos os passos adicionar até 100, eu realmente gostaria um pouco mais de aceleração e desaceleração.

Então, respeitosamente demorarei seu conselho e, em vez disso, quadrado, depois dobre cada um desses valores.

Tenho uma confissão a fazer (me perdoa, pai, porque eu tenho Math.): Eu só consegui isso funcionando porque eu peguei um caminhão de peyote e subi uma abeto, onde um salmão feito de eletricidade nadou no córrego do meu alma e me disse o que significa fazer.

Agora, falei dessas barras como se representassem pixels, em um cenário onde algo precisava mover 100 pixels, mais de 100 etapas. Essa foi uma (provavelmente desnecessária) simplificação da realidade de que estes são realmente multiplicadores de cada etapa. Eles não precisam realmente adicionar a 100, ou 100% da distância, e geralmente não haverá exatamente 100 deles.

A parte importante é que depois que o multiplicador é aplicado a cada etapa – de modo que os passos do meio são maiores e o início e o final são menores – a soma de todas as etapas é igual à distância total que a coisa precisa para viajar.

Agora, para transformar essa teoria em JavaScript …

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *