Aguarde...

16 de fevereiro de 2024

Descarregando JavaScript com propriedades personalizadas

Descarregando JavaScript com propriedades personalizadas

Às vezes, um cliente de desenvolvimento web não sabe exatamente o que quer. Tentar adivinhar é difícil e dar um palpite é arriscado. Quando eles têm uma ideia vaga, ofereço-lhes os meios para criarem o que desejam para si próprios – quando descobrirem precisamente o que é isso.

Este foi o caso recentemente, onde um cliente queria algum tipo de animação baseada em rolagem, mas não conseguimos definir a natureza exata dessa animação. Tudo o que pude aconselhar do ponto de vista criativo foi que não deveria ser muito intrusivo ou desorientador.

Dado que as animações baseadas em rolagem baseadas em CSS ainda estão atrás de vários sinalizadores, configurei meu cliente com um pequeno IntersectionObserver script. Este pequeno módulo, chamado CreateObserver, é importado para o rodapé da página e inicializado com alguns argumentos.

<script type="module">
  import { createObserver } from 'static/js/createObserver.js';
  
  const observerFunction = entries => {
    entries.forEach(entry => {
      // do something each time a threshold is crossed
    });
  }
  
  createObserver('.section', 5, observerFunction);
</script>
  • .section: o seletor que representa os elementos cuja visibilidade deve ser observada (“entradas” na IntersectionObserverlinguagem)
  • 5: o número de limites (proporções de visibilidade) tratados pelo observador. 5cria a matriz[0, 0.2, 0.4, 0.8, 1]
  • observerFunction: A função de tratamento/retorno de chamada é acionada sempre que um limite é ultrapassado

Isso é bastante útil como solução geral, já que praticamente qualquer coisa pode ser feita dentro da sua observerFunctionfunção. Com isso, eu poderia equipar meu cliente com os meios para começar a brincar com efeitos visuais baseados em rolagem – e sem que eles tivessem que pensar muito sobre como IntersectionObserverrealmente funciona.

Mas, quando comecei a escrever exemplos de implementações da função de retorno de chamada, tudo começou a parecer excessivamente JavaScript. De README:

const opacityFade = entries => {
  entries.forEach(entry => {
    let ratio = entry.intersectionRatio;
    entry.target.style.opacity = ratio;
  });
}

O que foi dito acima está correto , eu acho, especialmente porque a opacitypropriedade é mapeada diretamente para a proporção de interseção ( 0.5intersecção? 0.5opacidade).

Mas fica mais complexo (e com menos desempenho) quando você precisa forçar o valor limite:

const leftOffset = entries => {
  entries.forEach(entry => {
    let ratio = entry.intersectionRatio;
    let max = -20;
    let offset = max * (1 - ratio);
    entry.target.style.insetInlineStart = `${offset}rem`;
  });
}

E isso se você quiser apenas estilizar o próprio elemento de entrada do observador. E se você realmente quiser afetar um elemento descendente? Mais consultas DOM.

const leftOffset = entries => {
  entries.forEach(entry => {
    let ratio = entry.intersectionRatio;
    let max = 20;
    let offset = max * (1 - ratio);
    let descendant = entry.target.querySelector('.some-descendant');
    descendant.style.insetInlineStart = `${offset}rem`;
  });
}

Quando você começa a escrever JavaScript, isso o atrai para sua própria maneira de pensar e é fácil esquecer que outras tecnologias estão presentes. Embora IntersectionObserverseja (atualmente) crítico na criação desses efeitos baseados em rolagem, tudo o que realmente queremos dele é a proporção .

É inteiramente o CSS que cria os efeitos em cima dessa proporção, então é dentro do CSS que esse trabalho deve ser feito. Até porque alguém bem versado em CSS e menos familiarizado com JavaScript seria mais capaz de criar e adaptar os efeitos.

Classicamente – a web agora é tão antiga que você pode se referir a eras “clássicas” dentro dela – a maneira de descarregar o estilo JavaScript para CSS é através do gerenciamento de classes. Com um IntersectionObservervalor limite de 1.0, a visibilidade é considerada binária (totalmente visível ou não) e você pode simplesmente alternar uma única classe usando a isIntersectingpropriedade.

entry.target.classList.toggle('is-intersecting', entry.isIntersecting)

Com um conjunto de limites (usando a sintaxe de array), múltiplas classes teriam que ser gerenciadas. Algo horrível como isto:


.intersecting-none {
  transform: translateX(-20rem);
}

.intersecting-one-fifth {
  transform: translateX(-16rem);
}

.intersecting-two-fifths {
  transform: translateX(-12rem);
}

.intersecting-three-fifths {
  transform: translateX(-8rem);
}

.intersecting-four-fifths {
  transform: translateX(-4rem);
}

.intersecting-full {
  transform: translateX(0);
}

(Observação: para evitar que essas etapas sejam difíceis , você pode adicionar um transitionpara a propriedade transform.)

Isso é muito CSS para uma animação baseada em rolagem, para um elemento. Pior que isso, se alguém decidir alterar o número de limites no JavaScript, ele — ou outra pessoa — terá que reescrever todas essas classes no CSS.

Em vez disso, podemos armazenar a proporção de interseção diretamente em uma propriedade personalizada!

style="--intersecting: 0.2"

Isso disponibiliza a proporção diretamente no CSS e pode ser usada em cálculos de estilo:

opacity: var(--intersecting);
transform: translateX(calc(-20rem * (1 - var(--intersecting))));

O número de limites agora produz diretamente uma resolução para a animação/efeito: a propriedade personalizada é atualizada quantas vezes o retorno de chamada JavaScript for acionado. CSS é reativo a essas atualizações por padrão.

E como os valores das propriedades personalizadas são herdados, só precisamos colocar/atualizar o valor na entrada pai e ele fica disponível para todos os elementos descendentes.

let ratio = entry.intersectionRatio;
entry.target.style.setProperty('--intersecting', ratio);

Na verdade, as funções de retorno de chamada personalizadas não são necessárias. Com apenas a proporção disponível diretamente em nosso CSS, praticamente tudo e qualquer outra coisa pode ser codificada apenas com CSS. Que é como deveria ser.

Com classes, podemos enviar valores estáticos CSS , mas com propriedades personalizadas podemos enviar valores dinâmicos , o que é uma grande mudança na forma como podemos estilizar o estado. Isso é algo que é verdade há algum tempo – e é extremamente bem suportado – mas às vezes é preciso resolver um pequeno problema do mundo real para que você aprecie o valor dele.

Aqui está a IntersectionObserversolução geral como um módulo, caso você possa usá-lo:

const buildThresholds = steps => {
  let thresholds = [0];
  for (let i = 1.0; i <= steps; i++) {
    let ratio = i / steps;
    thresholds.push(ratio);
  }
  return thresholds;
}

const createObserver = (selector, steps) => {
  if (!IntersectionObserver in window) {
    return;
  }

  let options = {
    root: null,
    rootMargin: '0px',
    threshold: buildThresholds(steps)
  };

  let observer = new IntersectionObserver(entries => {
    entries.forEach(entry => {
      let ratio = entry.intersectionRatio;
      entry.target.style.setProperty('--intersecting', ratio);
        });
  }, options);

  let nodes = [...document.querySelectorAll(selector)];

  nodes.forEach(node => {
    observer.observe(node);
  });
}

export { createObserver }
Postado em BlogTags:
Escreva um comentário