Intersection API

  • This is a list

  • siamo quasi

  • eccolo

Intersection Observer API

The Intersection Observer is a web platform API, included in JS and without running in the main thread.

it asynchronously observe intersections between the target elements and the user viewpoint root:

//viewpoint set in options and target set in observe()
//IntersectionObs config can't be changed once set but can observe multiple
let base = useRef()
let target = useRef()
  
let interObs = useRef();
useEffect(()=>{

  let options={
    root: base.current, //Document/Element bounding-box used as viewpoint
    rootmargin: "0px",  //offset margins applied to viewpoint intersections
    //interception area percentages that trigger the callback.
    threshold: [...Array(100).keys()].map(x => x / 100) 
  }
  
  //The argument being the observe() target elements
  //of which only some are entries[0].isIntersecting
  function entered(entries){
    console.log( entries[0] )
  }

  interObs.current = new IntersectionObserver(entered, options)
  interObs.current.observe(target.current)
}, [])

We need the IntersectionObserver() object and the target to unobserve() :

//remember to declare both outside useEffect()
//If used twice it returns an error, and won't work in <strictMode/>
//We use useRef() to keep the intersectionObserver() value after animations
function annulla(){
  interObs.current.unobserve(target.current) 
}

We observe() loop when there are multiple intersection targets.

let sections = document.querySelectorAll(".sticky-container")

sections.forEach((element)=> {
  observer.observe(element)
})

We can use rootMargin to create a 0px top intersect for the root.

//used to intersect a sticky top element without scroll or height 
let options={
  root: contain.current,
  rootMargin: '0px 0px -100% 0px'
}

The entries of the callback function will be the current inView from the root, with true/false isIntersect based on the (options).

The toggle() method force argument only adds/removes on true/false. We can add a scroll() event with the callback function, but it won't return any target data.

//We forEach() the entries to toggle() first
function passed(entries){
  root.current.addEventListener('scroll', inside)

  entries.forEach((element)=>{
    element.target.classList.toggle('active', element.isIntersecting)
  })
}

//To add a scroll-like intersection trigger we use an array of small thresholds
options:{
  threshold: [...Array(100).keys()].map((x)=> x/100)
}
IntersectionRatio, boundingClientRect and intersectionRect

The intersectionRatio entry prop is the target intersection percentage to the root.

The boundingRect prop is the target position relative to the entire root, its top becomes negative once it's scrolled over, with the formula bottom - top = height.

The intercectRect is the rectangle area where the target and root viewpoint overlay, it's 0 when no intersect, its bottom-top returns the current intersect height of the target, and the top prop stops updating once it has been intersected and no longer visible (unlike boundingRect)

//ratio< 1 is equal to entry.isIntersecting, where we add/remove the scroll()
//On the X axis, boundingRect right-left = width
//We compare intersectRect to detect the target's position

let ratio = entry[0].intersectionRatio
const boundingRect = entry[0].boundingClientRect;
const intersectionRect = entry[0].intersectionRect;

if( ratio=== 0 ){
  contain.current.removeEventListener('scroll', vediamo);
}else if( ratio < 1 ){
  boundingRect.top < intersectionRect.top ? "on top":"on bottom" 
}else{
  stato.current.innerText = "inside"
}

We can modify both the root and target without needing to change the intersectObserver().

interceptionObserver() animated sections

This is how we animate intersectionObserver() nav-items.

Instead of the href/id scroll, we can manually scrollInView the section elements using offsetTop.

//This won't move the window page scroll in any way
//The offsetTop requires position-absolute on the scroll to be precise 

function mosso(event){
  event.preventDefault()

  let container = event.target

  let adesso = document.querySelector(`section#${container.classList[0]}`);

  let moment = adesso.offsetTop + 10
  adesso.parentNode.scrollTop = moment
}

The absolute scroll area won't return any height to its relative parent container, so we use document.offsetHeight for design's sake.

//Use margins on the relative to not modify the absolute offsetHeight
let sopra = useRef()
let sotto = useRef()

sopra.current.style.height = sotto.current.offsetHeight + "px"

<div ref={sopra} className="position-relative d-flex justify-content-center">

  <div ref={sotto} className="position-absolute row mx-0 col-8">
  </div>
</div>

We cache IntersectionObserver ID querySelect() outside the intersect function for easier manipulation and access.

//The forEach observer target 
let finalmente = document.querySelectorAll(".stratos")

//The cache outside the intersectionObserver function
let navElems = {}
finalmente.forEach((navId) => {
  navElems[navId.id] = document.querySelector(`.nav-item.${navId.id}`)
})

//So when it's time to edit the DOM element we 
elements[entry.target.id].classList.add("active")
//document.querySelector(`.nav-item.${entry.target.id }`).classList.add("active")

Intersection CSS style animation

Check this webpage:

React js version

We animate the sections with toggle() css keyframes.

//Unlike useSpring() it won't animate on removal.
entry.target.classList.toggle("active", entry.isIntersecting)

.zone .row.active{
  animation: example 0.5s;
}

@keyframes example{
  0% {transform: scale(0);}
  100% {transform: scale(1);}
}

We can access tag attributes or modify the entry.target.style directly.

<div class="imagi pure-g" data-color="#f1bace">
entry.target.style.backgroundColor = entry.target.getAttribute("data-color");;

//We check for a single className
if(entry.isIntersecting){
  entry.target.classList.value.includes("panini") ? 
    entry.target.style.opacity = 1 : entry.target.style.opacity = 0
}

1

We apply the Intersection Observer API on the <Parallax> root, to limit the intersection to the scroll window of its layers, and not the entire page.

//We can getElementById() it from a <ParallaxLayer> component.

useEffect(()=>{
  let window= document.getElementById("questo")

  let options= {
    root: window,
    rootMargin: "0px 0px -100% 0px",
    threshold: 0,
  }
}

<Parallax pages={3.3} className="meno" ref={ultimo} id="questo">
  <ParallaxLayer offset={0} style={{ backgroundColor: "lightskyblue" }}>
    <Secondo/>
  </ParallaxLayer>
  ...
</Parallax>

Last updated

Was this helpful?