Texto longo, leia o resumo
Aqui está um segredo: talvez você não precise de eventos scroll
no seu próximo app. Usando um
IntersectionObserver
,
mostro como acionar um evento personalizado quando os elementos position:sticky
são fixados ou quando param de ser fixados. Tudo isso sem o
uso de listeners de rolagem. Há até uma demonstração incrível para provar isso:
Apresentação do evento sticky-change
Uma das limitações práticas do uso da posição fixa do CSS é que ela não fornece um indicador de plataforma para saber quando a propriedade está ativa. Em outras palavras, não há um evento para saber quando um elemento se torna fixo ou quando ele deixa de ser fixo.
Confira o exemplo a seguir, que fixa um Não seria bom se o navegador informasse quando os elementos atingem essa marca?
Aparentemente, não sou o único
que pensa assim. Um indicador para Com esses casos de uso em mente, criamos um objetivo final: criar um evento que
é acionado quando um elemento A demonstração usa
esse evento para cabeçalhos com uma sombra projetada quando eles são fixados. Ele também atualiza o
novo título na parte de cima da página. Vamos esclarecer alguns termos para que eu possa me referir a esses nomes
ao longo do restante da postagem: Para saber qual cabeçalho entra no "modo fixo", precisamos de uma maneira de determinar
o deslocamento do contêiner de rolagem. Isso nos daria uma maneira
de calcular o cabeçalho que está sendo mostrado. No entanto, isso fica bastante
complicado sem eventos Sem os eventos de rolagem, perdemos a capacidade de realizar cálculos relacionados ao layout nos cabeçalhos. Em vez de eventos Precisamos de duas sentinelas para cobrir quatro casos de rolagem para cima e para baixo: É útil conferir um screencast das etapas 1 a 4 na ordem em que elas acontecem: Os sentinelas ficam na parte de cima e de baixo de cada seção.
Os observadores de interseção observam de forma assíncrona as mudanças na interseção de
um elemento de destino e a viewport do documento ou um contêiner pai. No nosso caso,
observamos interseções com um contêiner pai.
O molho mágico é Primeiro, configurei observadores para as sentinelas de cabeçalho e rodapé: Em seguida, adicionei um observador para disparar quando os elementos O observador é configurado com O processo é semelhante para a sentinela inferior ( O observador é configurado com Por fim, há meus dois utilitários para acionar o evento personalizado Pronto! Criamos um evento personalizado quando os elementos com Muitas vezes, me pergunto se o Na verdade, não. O que precisávamos era uma maneira de observar as mudanças de estilo em um elemento DOM.
Infelizmente, não há nada nas APIs da plataforma da Web que permita
monitorar mudanças de estilo. Um No futuro, uma extensão
Style Mutation Observer
para os Mutation Observers pode ser útil para observar mudanças nos
estilos computados de um elemento.
Exceto em caso de indicação contrária, o conteúdo desta página é licenciado de acordo com a Licença de atribuição 4.0 do Creative Commons, e as amostras de código são licenciadas de acordo com a Licença Apache 2.0. Para mais detalhes, consulte as políticas do site do Google Developers. Java é uma marca registrada da Oracle e/ou afiliadas. Última atualização 2017-09-19 UTC..sticky {
position: sticky;
top: 10px;
}
position:sticky
pode desbloquear vários casos de uso:
position:sticky
se torna fixo. Vamos chamar de evento
sticky-change
:document.addEventListener('sticky-change', e => {
const header = e.detail.target; // header became sticky or stopped sticking.
const sticking = e.detail.stuck; // true when header is sticky.
header.classList.toggle('shadow', sticking); // add drop shadow when sticking.
document.querySelector('.who-is-sticking').textContent = header.textContent;
});
Efeitos de rolagem sem eventos de rolagem?
position:sticky
.position:sticky
é aplicado ao elemento.scroll
:) O outro problema é que
position:sticky
remove o elemento do layout quando ele é fixado.Como adicionar um DOM fictício para determinar a posição de rolagem
scroll
, vamos usar um IntersectionObserver
para
determinar quando os cabeçalhos entram e saem do modo fixo. Adicionar dois nós
(também conhecidos como sentinelas) em cada seção fixa, um na parte de cima e outro
na parte de baixo, vai funcionar como pontos de passagem para descobrir a posição de rolagem. À medida que esses
marcadores entram e saem do contêiner, a visibilidade deles muda e
o Intersection Observer dispara um callback.
O CSS
.sticky_sentinel--top
fica na parte de cima do cabeçalho, enquanto
.sticky_sentinel--bottom
fica na parte de baixo da seção:
:root {
--default-padding: 16px;
--header-height: 80px;
}
.sticky {
position: sticky;
top: 10px; /* adjust sentinel height/positioning based on this position. */
height: var(--header-height);
padding: 0 var(--default-padding);
}
.sticky_sentinel {
position: absolute;
left: 0;
right: 0; /* needs dimensions */
visibility: hidden;
}
.sticky_sentinel--top {
/* Adjust the height and top values based on your on your sticky top position.
e.g. make the height bigger and adjust the top so observeHeaders()'s
IntersectionObserver fires as soon as the bottom of the sentinel crosses the
top of the intersection container. */
height: 40px;
top: -24px;
}
.sticky_sentinel--bottom {
/* Height should match the top of the header when it's at the bottom of the
intersection container. */
height: calc(var(--header-height) + var(--default-padding));
bottom: 0;
}
Como configurar os observadores de interseção
IntersectionObserver
. Cada sentinela recebe um
IntersectionObserver
para observar a visibilidade da interseção no
contêiner de rolagem. Quando uma sentinela rola para a janela de visualização visível, sabemos
que um cabeçalho foi fixado ou deixou de ser fixado. Da mesma forma, quando uma sentinela sai
da janela de visualização./**
* Notifies when elements w/ the `sticky` class begin to stick or stop sticking.
* Note: the elements should be children of `container`.
* @param {!Element} container
*/
function observeStickyHeaderChanges(container) {
observeHeaders(container);
observeFooters(container);
}
observeStickyHeaderChanges(document.querySelector('#scroll-container'));
.sticky_sentinel--top
passassem
pela parte de cima do contêiner de rolagem (em qualquer direção).
A função observeHeaders
cria as sentinelas principais e as adiciona a
cada seção. O observador calcula a interseção da sentinela com
a parte de cima do contêiner e decide se ela está entrando ou saindo da viewport. Essas
informações determinam se o cabeçalho da seção está fixo ou não./**
* Sets up an intersection observer to notify when elements with the class
* `.sticky_sentinel--top` become visible/invisible at the top of the container.
* @param {!Element} container
*/
function observeHeaders(container) {
const observer = new IntersectionObserver((records, observer) => {
for (const record of records) {
const targetInfo = record.boundingClientRect;
const stickyTarget = record.target.parentElement.querySelector('.sticky');
const rootBoundsInfo = record.rootBounds;
// Started sticking.
if (targetInfo.bottom < rootBoundsInfo.top) {
fireEvent(true, stickyTarget);
}
// Stopped sticking.
if (targetInfo.bottom >= rootBoundsInfo.top &&
targetInfo.bottom < rootBoundsInfo.bottom) {
fireEvent(false, stickyTarget);
}
}
}, {threshold: [0], root: container});
// Add the top sentinels to each section and attach an observer.
const sentinels = addSentinels(container, 'sticky_sentinel--top');
sentinels.forEach(el => observer.observe(el));
}
threshold: [0]
para que o callback seja acionado assim
que a sentinela ficar visível..sticky_sentinel--bottom
).
Um segundo observador é criado para disparar quando os rodapés passam pela parte de baixo
do contêiner de rolagem. A função observeFooters
cria os
nós sentinela e os anexa a cada seção. O observador calcula a
interseção da sentinela com a parte de baixo do contêiner e decide se ela está
entrando ou saindo. Essas informações determinam se o cabeçalho da seção está
fixado ou não./**
* Sets up an intersection observer to notify when elements with the class
* `.sticky_sentinel--bottom` become visible/invisible at the bottom of the
* container.
* @param {!Element} container
*/
function observeFooters(container) {
const observer = new IntersectionObserver((records, observer) => {
for (const record of records) {
const targetInfo = record.boundingClientRect;
const stickyTarget = record.target.parentElement.querySelector('.sticky');
const rootBoundsInfo = record.rootBounds;
const ratio = record.intersectionRatio;
// Started sticking.
if (targetInfo.bottom > rootBoundsInfo.top && ratio === 1) {
fireEvent(true, stickyTarget);
}
// Stopped sticking.
if (targetInfo.top < rootBoundsInfo.top &&
targetInfo.bottom < rootBoundsInfo.bottom) {
fireEvent(false, stickyTarget);
}
}
}, {threshold: [1], root: container});
// Add the bottom sentinels to each section and attach an observer.
const sentinels = addSentinels(container, 'sticky_sentinel--bottom');
sentinels.forEach(el => observer.observe(el));
}
threshold: [1]
para que o callback seja acionado quando
todo o nó estiver visível.sticky-change
e gerar as sentinelas:/**
* @param {!Element} container
* @param {string} className
*/
function addSentinels(container, className) {
return Array.from(container.querySelectorAll('.sticky')).map(el => {
const sentinel = document.createElement('div');
sentinel.classList.add('sticky_sentinel', className);
return el.parentElement.appendChild(sentinel);
});
}
/**
* Dispatches the `sticky-event` custom event on the target element.
* @param {boolean} stuck True if `target` is sticky.
* @param {!Element} target Element to fire the event on.
*/
function fireEvent(stuck, target) {
const e = new CustomEvent('sticky-change', {detail: {stuck, target}});
document.dispatchEvent(e);
}
Demonstração final
position:sticky
são
fixados e adicionamos efeitos de rolagem sem o uso de eventos scroll
.Conclusão
IntersectionObserver
seria uma ferramenta útil para substituir alguns dos padrões de interface baseados em eventos scroll
que
foram desenvolvidos ao longo dos anos. A resposta é sim e não. A semântica
da API IntersectionObserver
dificulta o uso para tudo. Mas, como
mostrei aqui, você pode usá-lo para algumas técnicas interessantes.Outra forma de detectar mudanças de estilo?
MutationObserver
seria uma primeira escolha lógica, mas isso não funciona na
maioria dos casos. Por exemplo, na demonstração, receberíamos um callback quando a classe sticky
fosse adicionada a um elemento, mas não quando o estilo computado do elemento mudasse.
A classe sticky
já foi declarada no carregamento da página.position: sticky
.