Estudos de caso da API do Intersection Observer



Bom dia amigos

Visão geral


A API Intersection Observer (IOA) permite que um aplicativo observe de forma assíncrona a interseção de um elemento (destino) com seu pai (raiz) ou viewport (viewport). Em outras palavras, essa API fornece uma chamada para uma função específica sempre que o elemento de destino se cruza com a raiz ou a viewport.

Exemplos de uso:

  • Carregamento preguiçoso ou preguiçoso de imagens
  • rolagem de páginas sem fim
  • receber informações sobre a visibilidade da publicidade com o objetivo de calcular o custo das impressões
  • iniciar um processo ou animação no campo de visão do usuário


Para começar a trabalhar com o IOA, você precisa usar o construtor para criar um objeto observador com dois parâmetros - uma função de retorno de chamada e configurações:

// 
let options = {
    root: document.querySelector('.scroll-list'),
    rootMargin: '5px',
    threshold: 0.5
}

//   
let callback = function(entries, observer){
    ...
}

// 
let observer = new IntersectionObserver(callback, options)

Definições:

  • root - um elemento que atua como uma viewport para o destino (ancestral do elemento de destino ou nulo para a viewport)
  • rootMargin - margens em torno da raiz (margem em CSS, por padrão todas as margens são 0)
  • threshold - um número ou uma matriz de números indicando a porcentagem aceitável de interseção de destino e raiz

Em seguida, o elemento de destino é criado, que o observador observa:

let target = document.querySelector('.list-item')
observer.observe(target)

A chamada de retorno de chamada retorna um objeto que contém registros de alterações que ocorreram com o elemento de destino:

let callback = (entries, observer) => {
    entries.forEach(entry => {
        // entry () - 
        //   entry.boundingClientRect
        //   entry.intersectionRatio
        //   entry.intersectionRect
        //   entry.isIntersecting
        //   entry.rootBounds
        //   entry.target
        //   entry.time
    })
}

A rede está cheia de informações sobre a teoria, mas bastante material sobre a prática do uso de IOA. Decidi preencher um pouco essa lacuna.

Exemplos


Carregamento de imagem lenta (atrasada)


Tarefa: carregue (mostre) imagens enquanto o usuário rola a página.

O código:

//    
window.onload = () => {
    //  
    const options = {
        //    -  
        root: null,
        //  
        rootMargin: '0px',
        //   -  
        threshold: 0.5
    }

    //  
    const observer = new IntersectionObserver((entries, observer) => {
        //   - 
        entries.forEach(entry => {
            //    
            if (entry.isIntersecting) {
                const lazyImg = entry.target
                //     -   
                console.log(lazyImg)
                //   
                lazyImg.style.background = 'deepskyblue'
                //  
                observer.unobserve(lazyImg)
            }
        })
    }, options)

    //       img  
    const arr = document.querySelectorAll('img')
    arr.forEach(i => {
        observer.observe(i)
    })
}

Resultado: o


plano de fundo do contêiner fora da janela de exibição é branco.



Quando você cruza a área de visualização pela metade, o fundo muda para azul celeste.

Codepen

→  Github

Substituição de imagem


Tarefa: altere a imagem do espaço reservado para o original quando o usuário rolar a página.

O código:

window.onload = () => {
    const observer = new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                console.log(entry)
                //        "data-src"
                entry.target.src = entry.target.dataset.src
                observer.unobserve(entry.target)
            }
        })
    }, { threshold: 0.5 })

    document.querySelectorAll('img').forEach(img => observer.observe(img))
}

Resultado:


a primeira imagem é carregada porque está na área de visualização. O segundo é um espaço reservado.



À medida que você avança, o espaço reservado é substituído pela imagem original.

Codepen

→  Github

Alterar plano de fundo do contêiner


Tarefa: alterar o plano de fundo do contêiner quando o usuário rolar a página para lá e para trás.

O código:

window.addEventListener('load', event => {
    let box = document.querySelector('div')
    // ratio -   
    let prevRatio = 0.0

    let observer = new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
            let curRatio = entry.intersectionRatio
            
            //      -  -
            //           (  )
           //      
            curRatio > prevRatio ? entry.target.style.background = `rgba(40,40,190,${curRatio})` : entry.target.style.background = `rgba(190,40,40,${curRatio})`

            prevRatio = curRatio
        })
    }, {
        threshold: buildThresholdList()
    })

    observer.observe(box)
    
    //    
    //      20 ,   
    function buildThresholdList() {
        let thresholds = []
        let steps = 20

        for (let i = 1.0; i <= steps; i++) {
            let ratio = i / steps
            thresholds.push(ratio)
        }
        return thresholds
    }
})

Resultado: o


fundo do contêiner muda de azul claro ...



para azul ...



para vermelho claro.

Codepen

→  Github

Trabalhar com vídeo


Tarefa: Pause o vídeo em execução e inicie-o novamente, dependendo do vídeo que caia na área de visualização.

O código:

window.onload = () => {
    let video = document.querySelector('video')

    let observer = new IntersectionObserver(() => {
        //   
        if (!video.paused) {
            //  
            video.pause()
        //      (   > 0)
        } else if(video.currentTime != 0) {
            //  
            video.play()
        }
    }, { threshold: 0.4 })

    observer.observe(video)
}

Resultado:



enquanto o vídeo está na área de visualização, ele é reproduzido.



Assim que o vídeo ultrapassa a área de visualização em mais de 40%, sua reprodução é interrompida. Se você atingir a área de visualização> 40% do vídeo, a reprodução será retomada.

Codepen

→  Github

Progresso da exibição da página


Tarefa: mostra o progresso da exibição da página enquanto o usuário rola a página.

O código:

//          
let p = document.querySelector('p')
// n -   
let n = 0

let observer = new IntersectionObserver((entries, observer) => {
    entries.forEach(entry => {
        if(entry.isIntersecting){
            // observer   div
            //       
            //     
            p.textContent = `${n++} div viewed`
            observer.unobserve(entry.target)
        }
    })
}, {threshold: 0.9})

document.querySelectorAll('div').forEach(div => observer.observe(div))

Resultado: a



página acabou de ser carregada, portanto ainda não examinamos nenhum contêiner.



Quando o final da página é alcançado, o parágrafo exibe informações sobre a exibição de 4 divs.

Codepen

→  Github

Rolagem sem fim


Tarefa: implemente uma lista interminável.

O código:

let ul = document.querySelector('ul')
let n = 1

//    
function createLi(){
    li = document.createElement('li')
    li.innerHTML = `${++n} item`
    ul.append(li)
}

//  ,        
//     
//         li
//       () 
let observer = new IntersectionObserver((entries, observer) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            createLi()
        }
        observer.unobserve(entry.target)
        observer.observe(document.querySelector('li:last-child'))
    })
}, {
    threshold: 1
})

observer.observe(document.querySelector('li'))

Resultado:



temos 12 itens de lista. O último item está fora da janela atual.



Quando você tenta chegar ao último elemento, um novo (último) elemento é criado, oculto ao usuário. E assim por diante até o infinito.

Codepen

→  Github

Redimensionando um filho ao redimensionar um pai


Tarefa: estabelecer a dependência do tamanho de um elemento em outro.

O código:

//      -   
//      
let info = document.querySelector('.info')
let parent = document.querySelector('.parent')
let child = document.querySelector('.child')
//     50px  
child.style.width = parent.offsetWidth - 50 + 'px'
//     
info.textContent = `child width: ${child.offsetWidth}px`

let options = {
    //      
    root: parent,
    threshold: 1
}

let observer = new IntersectionObserver((entries, observer) => {
    entries.forEach(entry => {
        //         50px
        if ((entry.target.parentElement.offsetWidth - entry.target.offsetWidth) < 50) {
            //     50px
            entry.target.style.width = entry.target.offsetWidth - 50 + 'px'
        }
    })
}, options)

observer.observe(child)

//  ,  ,       IOA
//       resize
window.addEventListener('resize', () => {
    info.textContent = `child width: ${child.offsetWidth}px`
    if ((parent.offsetWidth - child.offsetWidth) > 51) {
        child.style.width = child.offsetWidth + 50 + 'px'
    }
})

Resultado:



estado inicial.



Ao reduzir a largura do elemento pai, a largura do elemento filho diminui. Ao mesmo tempo, a distância entre eles quase sempre é igual a 50px ("quase" é devido à implementação do mecanismo inverso).

Codepen

→  Github

Trabalhar com animação


Tarefa: animar um objeto quando estiver visível.

O código:

//        
//     
//  - 
let observer = new IntersectionObserver((entries, observer) => {
    entries.forEach(entry => {
        entry.isIntersecting ? entry.target.classList.replace('to-left', 'to-right') : entry.target.classList.replace('to-right', 'to-left')
    })
}, {
    threshold: .5
})

observer.observe(document.querySelector('img'))

Resultado:



vemos parte da cabeça de Bart. Bart pressionou para o lado esquerdo da área de visualização.



Se mais de 50% de Bart cair na área de visualização, ele se moverá para o meio. Quando mais de 50% de Bart sai da área de visualização, ele volta à sua posição inicial.

Codepen

→  Github

Obrigado por sua atenção.

All Articles