4 truques que nos ajudaram a otimizar o frontend

Belas animações há muito se estabelecem nas tendências de design. Os designers de interface do usuário fazem carrosséis, downloads, animações de menu e outras decorações elaboradas, e os desenvolvedores de front-end os traduzem em código. Mas o site não deve apenas ter uma boa aparência, mas também funcionar rapidamente.


Um frontend moderno deve otimizar seu código. Isso é especialmente verdadeiro para produtos nos quais a maioria do público acessa o site a partir de dispositivos móveis. Alguns métodos de animação ficam atrasados, mesmo no Chrome, nos melhores computadores, mas devem funcionar sem problemas em um smartphone comum.


Nossos desenvolvedores usaram um grande número de técnicas que ajudaram a otimizar o site e acelerar o trabalho. Eu colecionei 4 dos mais interessantes deles. Compartilhamos conhecimentos úteis para iniciantes e profissionais, além de fornecer links para tutoriais úteis.



1. Animação no SCSS


O site tem muita animação. Precisamos do navegador para reproduzi-lo com uma taxa de quadros estável de 60 qps. Em CSS puro, isso é difícil, portanto usamos o SCSS.


Para criar os controles deslizantes, usamos a biblioteca Swiper . Para um controle deslizante horizontal, o uso desta biblioteca é justificado, pois precisamos fornecer suporte ao svayp do usuário. Mas para um carrossel sem fim vertical, o Swiper não é necessário, não há interação do usuário. Portanto, queríamos repetir a mesma funcionalidade usando apenas recursos CSS.


A primeira e mais importante condição ao trabalhar com animação CSS é usar apenas as propriedades de transformação e opacidade. Os navegadores podem otimizar independentemente a animação dessas propriedades e produzir 60 fps estáveis. No entanto, o uso de @keyframes sozinho não é possível gravar animações diferentes para elementos diferentes, e animar cada elemento individualmente em CSS puro consome muito tempo. Como escrever rapidamente a animação que precisamos? Escolhemos o SCSS, o dialeto SASS - uma extensão CSS mais funcional.


Vamos analisar o uso do SCSS usando o exemplo do nosso controle deslizante vertical.



À nossa disposição, há um contêiner cuja altura é igual à altura dos três elementos do carrossel. No interior, há outro recipiente contendo todos os elementos do carrossel.


<div class="b-vertical-carousel-slider"> <div class="vertical-carousel-slider-wrapper slider-items"> <div class="vertical-carousel-slider-item"></div> <div class="vertical-carousel-slider-item"></div> <div class="vertical-carousel-slider-item"></div> <div class="vertical-carousel-slider-item"></div> <div class="vertical-carousel-slider-item"></div> </div> </div> 

Removemos a visibilidade dos elementos do contêiner que vão além e definimos a altura dos blocos.


 .b-vertical-carousel-slider { position: relative; overflow: hidden; height: $itemHeight * 3; .vertical-carousel-slider-item { height: $itemHeight; } } 

O cálculo da animação muda apenas se o número de elementos no carrossel mudar. Em seguida, escrevemos um mixin que recebe um parâmetro de entrada - $itemCount


 @mixin verticalSlideAnimation($itemCount) { } 

No mixin, gere um quadro-chave para cada elemento, defina o estado inicial para ele e use :nth-child determinar a animação para o elemento.


 for $i from * 1 through $itemCount { $animationName: carousel-item-#{$itemCount}-#{$i}; @keyframes #{$animationName} { 0% { transform: translate3d(0, 0, 0) scale(.95); } } .vertical-carousel-slider-item:nchild(#{$i}) { animation: $stepDuration * $itemCount $animation ease infinite; } } 

Além disso, durante a animação, apenas moveremos os elementos ao longo do eixo y e alteraremos a escala do elemento no centro.


Estados do elemento carrossel:


  1. Paz
  2. Deslocamento Y
  3. Deslocamento Y
  4. Deslocamento Y com diminuição

Cada item $itemCount vezes, aumentará uma vez e diminuirá uma vez durante o movimento. Portanto, geraremos e calcularemos a animação para cada um dos movimentos para cima.


 @keyframes #{$animationName} { 0% { transform: translate3d(0, 0, 0) scale(.95); } @for $j from 0 through $itemCount { $isFocusedStep: $i == $j + 2; $isNotPrevStep: $i != $j + 1; $offset: 100% / $itemCount * ($animationTime / $stepDuration); @if ($isFocusedStep) { #{getPercentForStep($j, $itemCount, $offset)} { transform: getTranslate($j - 1) scale(.95); } #{getPercentForStep($j, $itemCount)} { transform: getTranslate($j) scale(1); } #{getPercentForStep($j + 1, $itemCount, $offset)} { transform: getTranslate($j) scale(1); } #{getPercentForStep($j + 1, $itemCount)} { transform: getTranslate($j + 1) scale(.95); } } @else if ($isNotPrevStep) { #{getPercentForStep($j, $itemCount, $offset)} { transform: getTranslate($j - 1) scale(.95); } #{getPercentForStep($j, $itemCount)} { transform: getTranslate($j) scale(.95); } } } } 

Resta definir algumas variáveis ​​e funções:


  • $animationTime - tempo de interpolação de movimento
  • $stepDuration - tempo total de execução de uma etapa da animação ( $animationTime + tempo de descanso do carrossel)
  • getPercentForStep($step, $itemCount, $offset) - uma função que retorna em porcentagem o ponto extremo de um dos estados.
  • getTranslate($step) - retorna a conversão dependendo da etapa da animação

Implementações de funções de exemplo:


 @function getPercentForStep($step, $count, $offset: 0) { @return 100% * $step / $count - $offset; } @function getTranslate($step) { @return translate3d(0, -100% * $step, 0); } 

Temos um carrossel de trabalho com um elemento crescente no meio. Resta fazer uma sombra sob os elementos crescentes. Inicialmente, cada elemento do carrossel tinha um pseudo-elemento: depois, que por sua vez tinha uma sombra. Para não animar a propriedade shadow, usamos a propriedade opacidade, ou seja, apenas mostrou e escondeu a sombra.


Porém, na nova implementação de uma solução desse tipo, você precisa gerar muitos quadros-chave adicionais para cada pseudo-elemento. Decidimos fazer isso com mais facilidade: haverá um bloco com sombra e ele ocupará espaço exatamente abaixo do elemento do meio do carrossel.


Adicione uma div que seja responsável pela sombra.


 <div class="b-vertical-carousel-slider"> <div class="vertical-carousel-slider-wrapper"> <div class="vertical-carousel-slider-item"></div> <div class="vertical-carousel-slider-item"></div> <div class="vertical-carousel-slider-item"></div> <div class="vertical-carousel-slider-item"></div> <div class="vertical-carousel-slider-item"></div> </div> <div class="vertical-carousel-slider-shadow"></div> </div> 

Nós estilizamos e adicionamos animações.


 @keyframes shadowAnimation { 0% { opacity: 1; } 80% { opacity: 1; } 90% { opacity: 0; } 100% { opacity: 1; } } .vertical-carousel-slider-shadow { top: $itemHeight; left: 0; right: 0; height: $itemHeight; animation: $stepDuration shadowAnimation ease infinite; } 

Nesse caso, você não precisa gerar e pensar em uma maneira de animar a sombra sob cada elemento, apenas animamos um bloco, ele possui dois estados - é visível e oculto


Como resultado, temos um carrossel CSS puro que anima com apenas propriedades bem otimizadas. Isso permite que o navegador use a aceleração de hardware para renderização. Daí o lucro tangível comparado à animação JS:


  1. Ao rolar páginas com animações em dispositivos fracos, os quadros eram claramente ignorados nas animações JS, reduzindo o FPS para 15-20. As animações CSS melhoraram claramente as coisas. Nesses dispositivos, esse número era de pelo menos 50-55 FPS.
  2. Nos livramos do trabalho de um módulo de terceiros onde não era necessário.
  3. A animação será reproduzida mesmo quando o JS estiver desativado

A animação JS deve ser usada se for necessário um controle rigoroso sobre cada quadro: pausa, reprodução reversa, retrocesso, reação às ações do usuário. Em outros casos, recomendamos o uso de CSS puro.


Links úteis



2. Usando a API Intersection Observer


A animação que o visitante do site não vê é reproduzida em algum lugar fora da vista e carrega a CPU. Usando o Intersection Observer, determinamos que tipo de animação está visível na tela no momento e apenas a reproduzimos.


Todos os truques do último parágrafo podem ser combinados com o Intersection Observer. Essa ferramenta ajuda a não carregar o navegador com animação que o visitante do site não vê. Anteriormente, “ouvintes” de eventos com uso intensivo de recursos eram usados ​​para entender se um visitante estava olhando para um elemento animado e isso não produzia um “escape” forte. A diferença entre usar animação fora da janela de exibição e usar ouvintes era mínima. A API do Intersection Observer requer menos recursos e ajuda a reproduzir apenas a animação que é visível para o visitante.


No nosso site, a animação é ativada apenas quando um elemento aparece na janela de exibição. Se não o fizéssemos, as páginas seriam sobrecarregadas com a execução constante de animações em loop que permaneciam fora da vista. A API Intersection Observer permite monitorar a interseção do elemento com o pai ou com o escopo do documento.


Exemplo de implementação


Como exemplo, mostramos como otimizar a animação em JS. A idéia é simples - a animação é reproduzida enquanto o elemento animado está na janela de exibição. Para implementação, usamos a API Intersection Observer.


Adicionar manipulação de classe com is-paused aos estilos


 .b-vertical-carousel-slider.is-paused { .vertical-carousel-slider-wrapper { .vertical-carousel-slider-item { animation-play-state: paused; } } .vertical-carousel-slider-shadow { animation-play-state: paused; } } 

I.e. quando essa classe aparecer, a animação será pausada.


Agora descrevemos a lógica de adicionar e remover esta classe


 if (window.IntersectionObserver) { const el = document.querySelector('.b-vertical-carousel-slider'); const observer = new IntersectionObserver(intersectionObserverCallback); observer.observe(el); } 

Aqui, criamos uma instância de IntersectionObserver, especificada a função intersectionObserverCallback , que funcionará quando a visibilidade for alterada.


Agora defina intersectionObserverCallback


 function intersectionObserverCallback(entries){ if (entries[0].intersectionRatio === undefined) { return; } helperDOM.toggleClass(el, 'is-paused', entries[0].intersectionRatio <= 0); }; 

Agora a animação é reproduzida apenas nos elementos visíveis. Assim que o elemento desaparece do campo de visão, a animação é pausada. Quando o visitante retornar a ele, a reprodução continuará.


Links úteis



3. renderização SVG


Um grande número de imagens ou o uso de sprites causa frisos e atrasos nos primeiros segundos após o carregamento. A incorporação de SVG no código da página ajuda a tornar visualmente o carregamento mais suave.


Quando selecionamos métodos para trabalhar com imagens, tínhamos duas opções de otimização: incorporar SVG em HTML ou usar sprites. Nós decidimos incorporar. Incorporamos o código XML de cada imagem diretamente no código HTML das páginas. Isso aumenta um pouco o tamanho, mas o SVG é servido imediatamente em linha com o documento.


Muitos desenvolvedores continuam usando sprites SVG. Qual é a essência do método: uma matriz de imagens (por exemplo, ícones) é coletada em uma tela de imagem grande, chamada de sprite. Quando você precisa mostrar um ícone específico, um sprite é chamado, após o qual são dadas as coordenadas da peça específica em que está localizado. Isso é feito há muito tempo, mesmo na primeira versão do HTTP. Sprites ajudaram a armazenar em cache agressivamente o arquivo e reduzir o número de solicitações do servidor. Isso foi importante porque muitos pedidos simultâneos desaceleraram o navegador. O uso de sprites SVG é uma muleta típica com a qual você negligencia a lógica de trabalhar para economizar recursos. Agora, o número de solicitações não é tão importante, por isso recomendamos a incorporação.


Antes de tudo, afeta positivamente o desempenho do ponto de vista do visitante. Ele vê como os ícones carregam instantaneamente e não sofre os primeiros segundos após o carregamento da página. Ao usar imagens sprite ou PNG, a página que está sendo carregada diminui um pouco. Isso é especialmente sentido se o visitante rolar imediatamente a página carregada - o FPS cairá para 5-15 em dispositivos não superiores. A incorporação de SVG em HTML ajuda a reduzir a latência da página (subjetiva, do ponto de vista do cliente) e a se livrar de frisos e pulos de quadros durante o carregamento.


4. Armazenamento em cache usando o Service Worker e o cache HTTP


Não faz sentido recarregar uma página que não tenha sido alterada e usar o tráfego de visitantes. Existem muitas estratégias de cache, decidimos pela combinação mais eficiente.


Otimizar é o uso não apenas da CPU / GPU, mas também da rede. Os dispositivos móveis são uma limitação não apenas em termos de recursos, mas também em termos de velocidade e tráfego da Internet. O cache nos ajudou aqui. Ele permite salvar respostas às solicitações HTTP e usá-las sem receber uma resposta do servidor novamente.


Quando consideramos uma estratégia de armazenamento em cache, optamos por usar o Service Worker e o HTTP Cache ao mesmo tempo. Vamos começar com o primeiro e mais avançado. O Service Worker é um arquivo js que pode controlar sua página ou arquivo, interceptar e modificar solicitações, além de armazenar em cache programaticamente solicitações. Ele atua como um proxy entre o site e o servidor e determina seu comportamento offline. Tudo isso é feito na "frente", sem conectar um "back-end".


O Operador de Serviço tem uma tremenda variabilidade. Podemos programar o comportamento como quisermos. Por exemplo, sabemos que um visitante que foi para a página número 1, com uma probabilidade de 90%, irá para a página número 2. Pedimos ao SW para carregar a segunda página com o segundo plano quando o visitante ainda estiver na primeira. Quando ele acessa, a página carrega instantaneamente. Pode ser usado para diferentes tarefas:


  • Sincronização de dados em segundo plano
  • Calculadoras offline
  • Modelo personalizado
  • Reação a uma hora e data específicas.

Os arquivos do Service Worker podem ser criados em diferentes serviços. Recomendamos a caixa de trabalho . É bastante simples e permite criar um conjunto de regras pelas quais o armazenamento em cache é realizado, por exemplo, precache.


Os funcionários do serviço não oferecem suporte a todos os navegadores, como IE, Safari ou Chrome, anteriores à versão 40.0. Se o dispositivo não puder trabalhar com ele, seguirá as regras de cache do cache HTTP. Adicionamos os seguintes cabeçalhos HTTP às páginas:


 cache-control: no-cache last-modified: Mon, 06 May 2019 04:26:29 GMT 

Nesse caso, o navegador adiciona respostas às solicitações ao repositório, mas a cada solicitação subsequente envia um cabeçalho para verificar se há alterações.


 if-modified-since: Mon, 06 May 2019 04:26:29 GMT 

Caso não ocorram alterações, o navegador recebe uma resposta com o código 304 Não modificado e usa o conteúdo armazenado no cache. Se forem feitas alterações no documento, a resposta será retornada com o código 200 e uma nova resposta será gravada no armazenamento do navegador.


Um pouco mais tarde, alteramos o método de armazenamento em cache e verificamos a quantidade de hash no nome do arquivo. Ele garante que o recurso permaneça exclusivo. Portanto, poderíamos armazenar em cache agressivamente o conteúdo. O seguinte cabeçalho foi adicionado em resposta:


 Cache-control:max-age=31536000, immutable 

A idade máxima indica o tempo máximo de armazenamento em cache, no nosso caso, é de 1 ano. Um valor imutável significa que essa resposta não precisa ser verificada quanto a alterações.


Links úteis



Essas não são todas as maneiras de otimizar o site. Isso poderia adicionar uma rejeição ao bootstrap, reduzindo o número de elementos e eventos do DOM e muito mais. Mas essas são as dicas que nos ajudaram a tornar o site rápido e responsivo, apesar da grande quantidade de animação.


Convidamos você para nossa equipe


Estamos sempre procurando especialistas legais em nosso escritório em São Petersburgo para nossas tarefas ambiciosas: desenvolvedores, testadores, designers. Abaixo há vagas - junte-se a nós.


Source: https://habr.com/ru/post/pt455700/


All Articles