99

Concurrent visitors over the last 30 minutes. (~)

Peak concurrent visitors -
Yesterday: 0 / Today: 0

JS Intersection Observer: Observing Element Visibility

JS Intersection Observer: Observing Element Visibility

# Overview

Intersection Observer watches the intersection between the browser viewport and a target element, and lets you tell whether the element is inside the viewport or not, or more simply, whether it's currently visible on the user's screen.

Because it runs asynchronously, you can use it without the problems that come with event-based element observation such as scroll, like poor rendering performance or events firing over and over.

Has each element entered the screen?

You initialize an observer with an instance (io) created by new IntersectionObserver(), and specify the target to observe (Element).
The constructor takes 2 arguments (callback, options).

JS
1
2
const io = new IntersectionObserver(callback, options) // Initialize the observer io.observe(element) // Register the target (element) to observe

# Callback

When a target is registered or its visibility (whether it's visible or not) changes, the observer runs the callback.
The callback takes 2 arguments (entries, observer).

JS
1
2
3
const options = {} const io = new IntersectionObserver((entries, observer) => {}, options) io.observe(element)

# entries

entries is an array of IntersectionObserverEntry instances, which represent the targets observed with the .observe() method.
IntersectionObserverEntry contains the following readonly properties.

  • boundingClientRect: The rectangle information of the target (DOMRectReadOnly)
  • intersectionRect: Information about the target's intersecting area (DOMRectReadOnly)
  • intersectionRatio: The percentage of the target's intersecting area (the ratio of the intersectionRect area to the boundingClientRect area, Number)
  • isIntersecting: The target's intersection state (Boolean)
  • rootBounds: The rectangle information of the specified root element (DOMRectReadOnly)
  • target: The target element (Element)
  • time: Information about when the change occurred (DOMHighResTimeStamp)
JS
1
2
3
4
5
6
7
8
9
const io = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { console.log(entry) // entry is 'IntersectionObserverEntry' }) }, options) io.observe(element1) // Target 1 io.observe(element2) // Target 2 // ...

intersection observer entry object

# entry.boundingClientRect

Returns the rectangle information of the target (DOMRectReadOnly).
You can get the same value with Element.getBoundingClientRect(). (Calling getBoundingClientRect causes a reflow.)

intersection observer boundingClientRect
intersection observer DOM rect
intersection observer DOM rect

# entry.intersectionRect

Returns the rectangle information (DOMRectReadOnly) of the area where the target intersects (overlaps) the root element.

intersection observer intersectionRect

# entry.intersectionRatio

Returns how much the target intersects (overlaps) the root element, as a number between 0.0 and 1.0.
This is the ratio of the intersectionRect area to the boundingClientRect area.

# entry.isIntersecting

A value (Boolean) that indicates whether the target is entering the intersecting state with the root element (true) or leaving it (false).

intersection observer isIntersecting

# entry.rootBounds

Returns the rectangle information of the root element (DOMRectReadOnly).
This value changes with the rootMargin option, and if you didn't declare a separate root element (the root option), it returns null.

# entry.target

Returns the target (Element).

# entry.time

Returns a DOMHighResTimeStamp that indicates when the intersection state changed, relative to the time the document was created.

# observer

References the instance whose callback is running.

JS
1
2
3
4
5
const io = new IntersectionObserver((entries, observer) => { console.log(observer) }, options) io.observe(element)

intersection observer observer

# Options

You can use a few options to set the observation range.

# root

Specifies the element object (root element) to use instead of the viewport when checking the target's visibility.
It must be an ancestor of the target, and if you don't specify it or set it to null, the browser's viewport is used by default.
The default value is null.

JS
1
2
3
const io = new IntersectionObserver(callback, { root: document.getElementById('my-viewport') })

intersection observer root

# rootMargin

You can grow or shrink the root's range with margins.
Like CSS margin, you can set the margins in 4 forms, using px or %.
The default value is 0px 0px 0px 0px, and you must include the units.

  • TOP, RIGHT, BOTTOM, LEFT / e.g. 10px 0px 30px 0px
  • TOP, (LEFT, RIGHT), BOTTOM / e.g. 10px 0px 30px
  • (TOP, BOTTOM), (LEFT, RIGHT) / e.g. 30px 0px
  • (TOP, BOTTOM, LEFT, RIGHT) / e.g. 30px
JS
1
2
3
const io = new IntersectionObserver(callback, { rootMargin: '200px 0px' })

intersection observer rootMargin
intersection observer rootMargin
intersection observer rootMargin

# threshold

Sets, as a percentage, how much of the target must be visible for the observer to run.
The default value is [0] (an Array), but you can also write a single Number value.

  • 0: The observer runs the moment the target's edge pixel crosses the root's range (when the target's visibility is 0%).
  • 0.3: The observer runs when the target's visibility is 30%.
  • [0, 0.3, 1]: The observer runs when the target's visibility reaches 0%, 30%, and 100%.
JS
1
2
3
const io = new IntersectionObserver(callback, { threshold: 0.3 // or `threshold: [0.3]` })

intersection observer threshold
intersection observer threshold
intersection observer threshold

# Methods

# observe

Starts observing the target element.

JS
1
2
3
4
5
6
7
8
9
10
const io1 = new IntersectionObserver(callback, options) const io2 = new IntersectionObserver(callback, options) const div = document.querySelector('div') const li = document.querySelector('li') const h2 = document.querySelector('h2') io1.observe(div) // Observe the DIV element io2.observe(li) // Observe the LI element io2.observe(h2) // Observe the h2 element

# unobserve

Stops observing the target element.
You must pass the one target element you want to stop observing as an argument.
However, if you pass a target element that the IntersectionObserver instance isn't observing, nothing happens.

JS
1
2
3
4
5
6
7
8
9
10
11
const io1 = new IntersectionObserver(callback, options) const io2 = new IntersectionObserver(callback, options) // ... io1.observe(div) io2.observe(li) io2.observe(h2) io1.unobserve(h2) // nothing.. io2.unobserve(h2) // Stop observing the H2 element

Since the callback's second argument observer references the instance, you can also write it like this.

JS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const io1 = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { // When visibility changes, the callback runs for all targets, // so skip the target if its intersection state is false (not visible). if (!entry.isIntersecting) { return } // Runs when the target's intersection state is true (visible). // ... // Handle the code above (once), then stop observing observer.unobserve(entry.target) }) }, options)

# disconnect

Stops observing all elements that the IntersectionObserver instance is observing.

JS
1
2
3
4
5
6
7
8
9
10
const io1 = new IntersectionObserver(callback, options) const io2 = new IntersectionObserver(callback, options) // ... io1.observe(div) io2.observe(li) io2.observe(h2) io2.disconnect() // Stop observing all elements io2 is observing (LI, H2)

# Support

Each front-end framework has libraries that make Intersection Observer easier to use.
Here is some simple usage for each; see each library's documentation for details.

# React

https://www.npmjs.com/package/react-intersection-observer

The useInView hook returns ref and inView, which you can use.
Attach ref to the target element you want to observe.
inView is reactive data that represents the target's intersection state (Boolean).

BASH
1
npm i react-intersection-observer
TSX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { useEffect } from 'react' import { useInView } from 'react-intersection-observer' export default function Component() { const { ref, inView } = useInView({ rootMargin: '200px 0px', threshold: 0.3 }) useEffect(() => { if (inView) { console.log('Intersecting!') } }, [inView]) return ( <section ref={ref}> <h2>Hello Intersection Observer!</h2> <p>See me? {inView ? 'Yes' : 'No'}</p> </section> ) }

# Vue

https://vueuse.org/core/useIntersectionObserver/

useIntersectionObserver takes the target, a callback, and options as arguments, in that order.

BASH
1
npm i @vueuse/core
VUE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<script setup lang="ts"> import { ref } from 'vue' import { useIntersectionObserver } from '@vueuse/core' const target = ref<HTMLElement | null>(null) const inView = ref(false) useIntersectionObserver( target, ([{ isIntersecting }]) => { inView.value = isIntersecting console.log('Intersecting!') }, { rootMargin: '200px 0px', threshold: 0.3 } ) </script> <template> <section ref="target"> <h2>Hello Intersection Observer!</h2> <p>See me? {{ inView ? 'Yes' : 'No' }}</p> </section> </template>

# Svelte

https://github.com/metonym/svelte-intersection-observer

Use the IntersectionObserver component to connect the target reference and the intersection state data.
Use the reactive statement ($:) to handle what happens based on the intersection state.

BASH
1
npm i svelte-intersection-observer
SVELTE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script lang="ts"> import IntersectionObserver from 'svelte-intersection-observer' let element: HTMLElement let intersecting: boolean $: if (intersecting) { console.log('Intersecting!') } </script> <IntersectionObserver bind:intersecting {element} rootMargin="200px 0px" threshold={0.3}> <section bind:this={element}> <h2>Hello Intersection Observer!</h2> <p>See me? {intersecting ? 'Yes' : 'No'}</p> </section> </IntersectionObserver>

# polyfill

An official library is available so that you can use the IntersectionObserver API even in browsers that don't support it (MSIE).
There's nothing to configure; you just import the module.

BASH
1
npm i intersection-observer
JS
1
2
3
4
5
6
7
8
9
10
11
// For IE import 'intersection-observer' // Use it the same way. const io = new IntersectionObserver(callback, options) const els = document.querySelectorAll('element') // For IE Array.prototype.slice.call(els).forEach(el => { io.observe(el) })

Or you can use it globally like this.

HTML
1
<script src="https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver"></script>

# Examples

# Infinite scroll

set Network tab in Chrome