Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using it's URL and the proper URL extention.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <nav id="navbar">
  <header id="nav-header">
    <h1>IntersectionObserver API</h1>
  </header>
  <ul class="nav-list">
    <li><a class="nav-link" href="#introduction">Introduction</a></li>
    <li><a class="nav-link" href="#api">API</a></li>
    <li><a class="nav-link" href="#intersection_observer_api">Intersection Observer API</a></li>
    <li><a class="nav-link" href="#concepts_and_usage">Concepts and Usage</a></li>
    <li><a class="nav-link" href="#creating_an_intersection_observer">Creating an Intersection Observer</a></li>
    <li><a class="nav-link" href="#how_intersection_is_calculated">How Intersection is calculated</a></li>
    <li><a class="nav-link" href="#intersection_change_callbacks">Intersection change callbacks</a></li>
    <li><a class="nav-link" href="#interfaces">Interfaces</a></li>
    <li><a class="nav-link" href="#a_simple_example">A Simple Example</a></li>
    <li><a class="nav-link" href="#specifications">Specifications</a></li>
    <li><a class="nav-link" href="#browser_compatibility">Browser Compatibility</a></li>
  </ul>
</nav>
<main id="main-doc">
  <section id="introduction" class="main-section">
    <header>
      <h2>Introduction</h2>
    </header>
    <h3><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Introduction" target="_blank" title="MDN Web Docs JavaScript Guide">What you should already know</a></h3>
    <p>This guide assumes you have the following basic background:</p>
    <ul>
        <li>A general understanding of the Internet and the World Wide Web (WWW).</li>
        <li>Good working knowledge of HyperText Markup Language (HTML).</li>
        <li>Some programming experience. If you are new to programming, try one of the tutorials linked on the main page about JavaScript.</li>
      </ul>
    <h3><a href="https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web/JavaScript_basics" target="_blank" title="MDN Web Docs JavaScript Basics">JavaScript Basics</a></h3>
    <p>JavaScript is a powerful programming language that can add interactivity to a website. It was invented by Brendan Eich (co-founder of the Mozilla project, the Mozilla Foundation, and the Mozilla Corporation).</p>
    <p>JavaScript is versatile and beginner-friendly. With more experience, you'll be able to create games, animated 2D and 3D graphics, comprehensive database-driven apps, and much more!</p>
    <p>JavaScript itself is relatively compact, yet very flexible. Developers have written a variety of tools on top of the core JavaScript language, unlocking a vast amount of functionality with minimum effort. These include:</p>
    <ul>
      <li>Browser Application Programming Interfaces (APIs) built into web browsers, providing functionality such as dynamically creating HTML and setting CSS styles; collecting and manipulating a video stream from a user's webcam, or generating 3D graphics and audio samples.</li>
      <li>Third-party APIs that allow developers to incorporate functionality in sites from other content providers, such as Twitter or Facebook.</li>
      <li>Third-party frameworks and libraries that you can apply to HTML to accelerate the work of building sites and applications.</li>
    </ul>
  </section>
  <section id="api" class="main-section">
    <header>
      <h2><a href="https://developer.mozilla.org/en-US/docs/Glossary/API" target="_blank" title="MDN Web Docs Glossary API">API</a></h2>
    </header>
    <p>An API (Application Programming Interface) is a set of features and rules that exist inside a software program (the application) enabling interaction with it through software - as opposed to a human user interface. The API can be seen as a simple contract (the interface) between the application offering it and other items, such as third party software or hardware.</p>
    <p>In Web development, an API is generally a set of code features (e.g. methods, properties, events, and URLs) that a developer can use in their apps for interacting with components of a user's web browser, or other software/hardware on the user's computer, or third party websites and services.</p>
  </section>
  <section id="intersection_observer_api" class="main-section">
    <header>
      <h2><a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API" target="_blank" title="MDN Web Docs Intersection Observer API">Intersection Observer API</a></h2>
    </header>
    <p>The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport.</p>
    <p>Historically, detecting visibility of an element, or the relative visibility of two elements in relation to each other, has been a difficult task for which solutions have been unreliable and prone to causing the browser and the sites the user is accessing to become sluggish. As the web has matured, the need for this kind of information has grown. Intersection information is needed for many reasons, such as:</p>
    <ul>
      <li>Lazy-loading of images or other content as a page is scrolled.</li>
      <li>Implementing "infinite scrolling" web sites, where more and more content is loaded and rendered as you scroll, so that the user doesn't have to flip through pages.</li>
      <li>Reporting of visibility of advertisements in order to calculate ad revenues.</li>
      <li>Deciding whether or not to perform tasks or animation processes based on whether or not the user will see the result.</li>
    </ul>
    <p>Implementing intersection detection in the past involved event handlers and loops calling methods like Element.getBoundingClientRect() to build up the needed information for every element affected. Since all this code runs on the main thread, even one of these can cause performance problems. When a site is loaded with these tests, things can get downright ugly.</p>
    <p>Consider a web page that uses infinite scrolling. It uses a vendor-provided library to manage the advertisements placed periodically throughout the page, has animated graphics here and there, and uses a custom library that draws notification boxes and the like. Each of these has its own intersection detection routines, all running on the main thread. The author of the web site may not even realize this is happening, since they may know very little about the inner workings of the two libraries they are using. As the user scrolls the page, these intersection detection routines are firing constantly during the scroll handling code, resulting in an experience that leaves the user frustrated with the browser, the web site, and their computer.</p>
    <p>The Intersection Observer API lets code register a callback function that is executed whenever an element they wish to monitor enters or exits another element (or the viewport), or when the amount by which the two intersect changes by a requested amount. This way, sites no longer need to do anything on the main thread to watch for this kind of element intersection, and the browser is free to optimize the management of intersections as it sees fit.</p>
    <p>One thing the Intersection Observer API can't tell you: the exact number of pixels that overlap or specifically which ones they are; however, it covers the much more common use case of "If they intersect by somewhere around N%, I need to do something."</p>
  </section>
  <section id="concepts_and_usage" class="main-section">
    <header>
      <h2><a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#intersection_observer_concepts_and_usage" target="_blank" title="MDN Web Docs Intersection Observer Concepts and Usage">Concepts and Usage</a></h2>
    </header>
    <p>The Intersection Observer API allows you to configure a callback that is called when either of these circumstances occur:</p>
    <ul>
      <li>A <strong>target</strong> element intersects either the device's viewport or a specified element. That specified element is called the <strong>root</strong> element or root for the purposes of the Intersection Observer API.</li>
      <li>The first time the observer is initially asked to watch a target element.</li>
    </ul>
    <p>Typically, you'll want to watch for intersection changes with regard to the target element's closest scrollable ancestor, or, if the target element isn't a descendant of a scrollable element, the device's viewport. To watch for intersection relative to the device's viewport, specify null for root option. Keep reading for a more detailed explanation about intersection observer options.</p>
    <p>Whether you're using the viewport or some other element as the root, the API works the same way, executing a callback function you provide whenever the visibility of the target element changes so that it crosses desired amounts of intersection with the root.</p>
    <p>The degree of intersection between the target element and its root is the <strong>intersection ratio</strong>. This is a representation of the percentage of the target element which is visible as a value between 0.0 and 1.0.</p>
  </section>
  <section id="creating_an_intersection_observer" class="main-section">
    <header>
      <h2><a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#creating_an_intersection_observer" target="_blank" title="MDN Web Docs Creating an Intersection Observer">Creating an Intersection Observer</a></h2>
    </header>
    <p>Create the intersection observer by calling its constructor and passing it a callback function to be run whenever a threshold is crossed in one direction or the other:</p>
    <div class="codeExample jsCode">
      <code>
  let options = {
    root: document.querySelector('#scrollArea'),
    rootMargin: '0px',
    threshold: 1.0
  }

  let observer = new IntersectionObserver(callback, options);
      </code>
    </div>
    <p>A threshold of 1.0 means that when 100% of the target is visible within the element specified by the root option, the callback is invoked.</p>
    <h3>Intersection observer options</h3>
    <p>The options object passed into the IntersectionObserver() constructor let you control the circumstances under which the observer's callback is invoked. It has the following fields:</p>
    <p class="subHeader">root</p>
    <p>The element that is used as the viewport for checking visibility of the target. Must be the ancestor of the target. Defaults to the browser viewport if not specified or if null.</p>
    <p class="subHeader">rootMargin</p>
    <p>Margin around the root. Can have values similar to the CSS margin property, e.g. "10px 20px 30px 40px" (top, right, bottom, left). The values can be percentages. This set of values serves to grow or shrink each side of the root element's bounding box before computing intersections. Defaults to all zeros.</p>
    <p class="subHeader">threshold</p>
    <p>Either a single number or an array of numbers which indicate at what percentage of the target's visibility the observer's callback should be executed. If you only want to detect when visibility passes the 50% mark, you can use a value of 0.5. If you want the callback to run every time visibility passes another 25%, you would specify the array [0, 0.25, 0.5, 0.75, 1]. The default is 0 (meaning as soon as even one pixel is visible, the callback will be run). A value of 1.0 means that the threshold isn't considered passed until every pixel is visible.</p>
    <h3>Targeting an element to be observed</h3>
    <p>Once you have created the observer, you need to give it a target element to watch:</p>
    <div class="codeExample jsCode">
      <code>
  let target = document.querySelector('#listItem');
  observer.observe(target);

  <span class="comments">// the callback we setup for the observer will be executed now for the first time</span>
  <span class="comments">// it waits until we assign a target to our observer (even if the target is currently not visable)</span>
      </code>
    </div>
    <p>Whenever the target meets a threshold specified for the IntersectionObserver, the callback is invoked. The callback receives a list of IntersectionObserverEntry objects and the observer:</p>
    <div class="codeExample jsCode">
      <code>
  let callback = (entries, observer) => {
    entries.forEach(entry => {
      <span class="comments">// Each entry describes an intersection change for one observed</span>
      <span class="comments">// target element:</span>
      <span class="comments">// entry.boundingClientRect</span>
      <span class="comments">// entry.intersectionRatio</span>
      <span class="comments">// entry.intersectionRect</span>
      <span class="comments">// entry.isIntersecting</span>
      <span class="comments">// entry.rootBounds</span>
      <span class="comments">// entry.target</span>
      <span class="comments">// entry.time</span>
    });
  };
      </code>
    </div>
    <p>The list of entries received by the callback includes one entry for each target which reported a change in its intersection status. Check the value of the isIntersecting property to see if the entry represents an element that currently intersects with the root.</p>
    <p>Be aware that your callback is executed on the main thread. It should operate as quickly as possible; if anything time-consuming needs to be done, use Window.requestIdleCallback().</p>
    <p>Also, note that if you specified the root option, the target must be a descendant of the root element.</p>
  </section>
  <section id="how_intersection_is_calculated" class="main-section">
    <header>
      <h2><a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#how_intersection_is_calculated" target="_blank" title="MDN Web Docs How Intersection is Calculated">How Intersection is Calculated</a></h2>
    </header>
    <p>All areas considered by the Intersection Observer API are rectangles; elements which are irregularly shaped are considered as occupying the smallest rectangle which encloses all of the element's parts. Similarly, if the visible portion of an element is not rectangular, the element's intersection rectangle is construed to be the smallest rectangle that contains all the visible portions of the element.</p>
    <p>It's useful to understand a bit about how the various properties provided by IntersectionObserverEntry describe an intersection.</p>
    <h3>The Intersection root and root margin</h3>
    <p>Before we can track the intersection of an element with a container, we need to know what that container is. That container is the <strong>intersection root</strong>, or <strong>root element</strong>. This can be either a specific element in the document which is an ancestor of the element to be observed, or null to use the document's viewport as the container.</p>
    <p>The <strong>root intersection rectangle</strong> is the rectangle used to check against the target or targets. This rectangle is determined like this:</p>
    <ul>
      <li>If the intersection root is the implicit root (that is, the top-level Document), the root intersection rectangle is the viewport's rectangle.</li>
      <li>If the intersection root has an overflow clip, the root intersection rectangle is the root element's content area.</li>
      <li>Otherwise, the root intersection rectangle is the intersection root's bounding client rectangle (as returned by calling getBoundingClientRect() on it).</li>
    </ul>
    <p>The root intersection rectangle can be adjusted further by setting the root margin, rootMargin, when creating the IntersectionObserver. The values in rootMargin define offsets added to each side of the intersection root's bounding box to create the final intersection root bounds (which are disclosed in IntersectionObserverEntry.rootBounds when the callback is executed).</p>
    <h3>Thresholds</h3>
    <p>Rather than reporting every infinitesimal change in how much a target element is visible, the Intersection Observer API uses thresholds. When you create an observer, you can provide one or more numeric values representing percentages of the target element which are visible. Then, the API only reports changes to visibility which cross these thresholds.</p>
    <p>For example, if you want to be informed every time a target's visibility passes backward or forward through each 25% mark, you would specify the array [0, 0.25, 0.5, 0.75, 1] as the list of thresholds when creating the observer.</p>
    <p>When the callback is invoked, it receives a list of IntersectionObserverEntry objects, one for each observed target which has had the degree to which it intersects the root change such that the amount exposed crosses over one of the thresholds, in either direction.</p>
    <p>You can see if the target currently intersects the root by looking at the entry's isIntersecting property; if its value is true, the target is at least partially intersecting the root element or document. This lets you determine whether the entry represents a transition from the elements intersecting to no longer intersecting or a transition from not intersecting to intersecting.</p>
    <p>Note that it's possible to have a non-zero intersection rectangle, which can happen if the intersection is exactly along the boundary between the two or the area of boundingClientRect is zero. This state of the target and root sharing a boundary line is not considered enough to be considered transitioning into an intersecting state.</p>
    <h3>Clipping and the Intersection rectangle</h3>
    <p>The browser computes the final intersection rectangle as follows; this is all done for you, but it can be helpful to understand these steps in order to better grasp exactly when intersections will occur.</p>
    <ol>
      <li>The target element's bounding rectangle (that is, the smallest rectangle that fully encloses the bounding boxes of every component that makes up the element) is obtained by calling getBoundingClientRect() on the target. This is the largest the intersection rectangle may be. The remaining steps will remove any portions that don't intersect.</li>
      <li>Starting at the target's immediate parent block and moving outward, each containing block's clipping (if any) is applied to the intersection rectangle. A block's clipping is determined based on the intersection of the two blocks and the clipping mode (if any) specified by the overflow property. Setting overflow to anything but visible causes clipping to occur.</li>
      <li>If one of the containing elements is the root of a nested browsing context (such as the document contained in an &ltiframe&gt, the intersection rectangle is clipped to the containing context's viewport, and recursion upward through the containers continues with the container's containing block. So if the top level of an &ltiframe&gt is reached, the intersection rectangle is clipped to the frame's viewport, then the frame's parent element is the next block recursed through toward the intersection root.</li>
      <li>When recursion upward reaches the intersection root, the resulting rectangle is mapped to the intersection root's coordinate space.</li>
      <li>The resulting rectangle is then updated by intersecting it with the root intersection rectangle.</li>
      <li>This rectangle is, finally, mapped to the coordinate space of the target's document.</li>
    </ol>
  </section>
  <section id="intersection_change_callbacks" class="main-section">
    <header>
      <h2><a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#intersection_change_callbacks" target="_blank" title="MDN Web Docs Intersection Change Callbacks">Intersection Change Callbacks</a></h2>
    </header>
    <p>When the amount of a target element which is visible within the root element crosses one of the visibility thresholds, the IntersectionObserver object's callback is executed. The callback receives as input an array of all of IntersectionObserverEntry objects, one for each threshold which was crossed, and a reference to the IntersectionObserver object itself.</p>
    <p>Each entry in the list of thresholds is an IntersectionObserverEntry object describing one threshold that was crossed; that is, each entry describes how much of a given element is intersecting with the root element, whether or not the element is considered to be intersecting or not, and the direction in which the transition occurred.</p>
    <p>The code snippet below shows a callback which keeps a counter of how many times elements transition from not intersecting the root to intersecting by at least 75%. For a threshold value of 0.0 (default) the callback is called approximately upon transition of the boolean value of isIntersecting. The snippet thus first checks that the transition is a positive one, then determines whether intersectionRatio is above 75%, in which case it increments the counter.</p>
    <div class="codeExample jsCode">
      <code>
  intersectionCallback(entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        let elem = entry.target;
      
        if (entry.intersectionRatio >= 0.75) {
          intersectionCounter++;
        }
      }
    });
  }
      </code>
    </div>
  </section>
  <section id="interfaces" class="main-section">
    <header>
      <h2>Interfaces</h2>
    </header>
    <h3><a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver" target="_blank" title="MDN Web Docs IntersectionObserver">IntersectionObserver</a></h3>
    <p>The primary interface for the Intersection Observer API. Provides methods for creating and managing an observer which can watch any number of target elements for the same intersection configuration. Each observer can asynchronously observe changes in the intersection between one or more target elements and a shared ancestor element or with their top-level Document's viewport. The ancestor or viewport is referred to as the root.</p>
    <h3><a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry" target="_blank" title="MDN Web Docs IntersectionObserverEntry">IntersectionObserverEntry</a></h3>
    <p>Describes the intersection between the target element and its root container at a specific moment of transition. Objects of this type can only be obtained in two ways: as an input to your IntersectionObserver callback, or by calling IntersectionObserver.takeRecords().</p>
  </section>
  <section id="a_simple_example" class="main-section">
    <header>
      <h2><a href="" target="_blank" title="MDN Web Docs Example">A Simple Example</a></h2>
    </header>
    <p>This simple example causes a target element to change its color and transparency as it becomes more or less visible. At Timing element visibility with the Intersection Observer API, you can find a more extensive example showing how to time how long a set of elements (such as ads) are visible to the user and to react to that information by recording statistics or by updating elements..</p>
    <h3>HTML</h3>
    <p>The HTML for this example is very short, with a primary element which is the box that we'll be targeting (with the creative ID "box") and some contents within the box.</p>
    <div class="codeExample markupCode">
      <code>
  &ltdiv class="extra"&gt
    &ltdiv id="box"&gt
      &ltdiv class="verticle"&gt
        Welcome to &ltstrong&gtThe Box!&lt/strong&gt
      &lt/div&gt
    &lt/div&gt
  &lt/div&gt
      </code>
    </div>
    <br>
    <h3>CSS</h3>
    <p>The CSS isn't terribly important for the purposes of this example; it lays out the element and establishes that the background-color and border attributes can participate in CSS transitions, which we'll use to affect the changes to the element as it becomes more or less obscured.</p>
    <div class="codeExample styleCode">
      <code>
  #box {
    background-color: rgba(40, 40, 190, 255);
    border: 4px solid rgb(20, 20, 120);
    transition: background-color 1s, border 1s;
    width: 350px;
    height: 350px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
  }

  .vertical {
    color: white;
    font: 32px "Arial";
  }

  .extra {
    width: 350px;
    height: 350px;
    margin-top: 10px;
    border: 4px solid rgb(20, 20, 120);
    text-align: center;
    padding: 20px;
  }
      </code>
    </div>
    <h3>JavaScript</h3>
    <p>Finally, let's take a look at the JavaScript code that uses the Intersection Observer API to make things happen.</p>
    <h4>Setting up</h4>
    <p>First, we need to prepare some variables and install the observer.</p>
    <div class="codeExample jsCode">
      <code>
  const numSteps = 20.0;

  let boxElement;
  let prevRatio = 0.0;
  let increasingColor = "rgba(40, 40, 190, ratio)";
  let decreasingColor = "rgba(190, 40, 40, ratio)";

  <span class="comments">// Set things up</span>
  window.addEventListener("load", (event) => {
    boxElement = document.querySelector("#box");

    createObserver();
  }, false);
      </code>
    </div>
    <p>The constants and variables we set up here are:</p>
    <p class="subHeader">numSteps</p>
    <p>A constant which indicates how many thresholds we want to have between a visibility ratio of 0.0 and 1.0.</p>
    <p class="subHeader">prevRatio</p>
    <p>This variable will be used to record what the visibility ratio was the last time a threshold was crossed; this will let us figure out whether the target element is becoming more or less visible.</p>
    <p class="subHeader">increasingColor</p>
    <p>A string defining a color we'll apply to the target element when the visibility ratio is increasing. The word "ratio" in this string will be replaced with the target's current visibility ratio, so that the element not only changes color but also becomes increasingly opaque as it becomes less obscured.</p>
    <p class="subHeader">decreasingColor</p>
    <p>Similarly, this is a string defining a color we'll apply when the visibility ratio is decreasing.</p>
    <p>We call Window.addEventListener() to start listening for the load event; once the page has finished loading, we get a reference to the element with the ID "box" using querySelector(), then call the createObserver() method we'll create in a moment to handle building and installing the intersection observer.</p>
    <h3>Creating the intersection observer</h3>
    <p>The createObserver() method is called once page load is complete to handle actually creating the new IntersectionObserver and starting the process of observing the target element.</p>
    <div class="codeExample jsCode">
      <code>
  function createObserver() {
    let observer;

    let options = {
      root: null,
      rootMargin: "0px",
      threshold: buildThresholdList()
    };

    observer = new IntersectionObserver(handleIntersect, options);
    observer.observe(boxElement);
  }
      </code>
    </div>
    <p>This begins by setting up an options object containing the settings for the observer. We want to watch for changes in visibility of the target element relative to the document's viewport, so root is null. We need no margin, so the margin offset, rootMargin, is specified as "0px". This causes the observer to watch for changes in the intersection between the target element's bounds and those of the viewport, without any added (or subtracted) space.</p>
    <p>The list of visibility ratio thresholds, threshold, is constructed by the function buildThresholdList(). The threshold list is built programmatically in this example since there are a number of them and the number is intended to be adjustable.</p>
    <p>Once options is ready, we create the new observer, calling the IntersectionObserver() constructor, specifying a function to be called when intersection crosses one of our thresholds, handleIntersect(), and our set of options. We then call observe() on the returned observer, passing into it the desired target element.</p>
    <p>We could opt to monitor multiple elements for visibility intersection changes with respect to the viewport by calling observer.observe() for each of those elements, if we wanted to do so.</p>
    <h3>Building the array of thresholds ratio</h3>
    <p>The buildThresholdList() function, which builds the list of thresholds, looks like this:</p>
    <div class="codeExample jsCode">
      <code>
  function buildThresholdList() {
    let thresholds = [];
    let numSteps = 20;

    for (let i=1.0; i&lt=numSteps; i++) {
      let ratio = i/numSteps;
      thresholds.push(ratio);
    }

    thresholds.push(0);
    return thresholds;
  }
      </code>
    </div>
    <p>This builds the array of thresholds—each of which is a ratio between 0.0 and 1.0, by pushing the value i/numSteps onto the thresholds array for each integer i between 1 and numSteps. It also pushes 0 to include that value.</p>
    <p>We could, of course, hard-code the array of thresholds into our code, and often that's what you'll end up doing. But this example leaves room for adding configuration controls to adjust the granularity, for example.</p>
    <h3>Handling intersection changes</h3>
    <p>When the browser detects that the target element (in our case, the one with the ID "box") has been unveiled or obscured such that its visibility ratio crosses one of the thresholds in our list, it calls our handler function, handleIntersect():</p>
    <div class="codeExample jsCode">
      <code>
  function handleIntersect(entries, observer) {
    entries.forEach((entry) => {
      if (entry.intersectionRatio > prevRatio) {
        entry.target.style.backgroundColor = increasingColor.replace("ratio", entry.intersectionRatio);
      } else {
        entry.target.style.backgroundColor = decreasingColor.replace("ratio", entry.intersectionRatio);
      }

      prevRatio = entry.intersectionRatio;
    });
  }
      </code>
    </div>
    <p>For each IntersectionObserverEntry in the list entries, we look to see if the entry's intersectionRatio is going up; if it is, we set the target's background-color to the string in increasingColor (remember, it's "rgba(40, 40, 190, ratio)"), replaces the word "ratio" with the entry's intersectionRatio. The result: not only does the color get changed, but the transparency of the target element changes, too; as the intersection ratio goes down, the background color's alpha value goes down with it, resulting in an element that's more transparent.</p>
    <p>Similarly, if the intersectionRatio is going down, we use the string decreasingColor and replace the word "ratio" in that with the intersectionRatio before setting the target element's background-color.</p>
    <p>Finally, in order to track whether the intersection ratio is going up or down, we remember the current ratio in the variable prevRatio.</p>
    <h3>Result</h3>
    <p>Below is the resulting content. Scroll this page up and down and notice how the appearance of the box changes as you do so.</p>
    <div class="extra">
      <div id="box">
        <div class="vertical">
          Welcome to <strong>The Box!</strong>
        </div>
      </div>
    </div>
    <p>There's an even more extensive example at <a href="" target="_blank">Timing element visibility with the Intersection Observer API</a>.</p>
  </section>
  <section id="specifications" class="main-section">
    <header>
      <h2><a href="" target="_blank" title="MDN Web Docs Specifications Intersection Observer">Specifications</a></h2>
    </header>
    <p>W3C Working Draft for <a href="">IntersectionObserver</a></p>
  </section>
  <section id="browser_compatibility" class="main-section">
    <header>
      <h2><a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#browser_compatibility" target="_blank" title="Browser Compatibility">Browser Compatibility</a></h2>
    </header>
      <p class="tableHeader">Desktop Browsers</p>
    <div class="table-container">
    <table id="desktopBrowser">
      <thead>
        <tr>
          <th class="emptyCell"> </th>
          <th>Chrome</th>
          <th>Edge</th>
          <th>Firefox</th>
          <th>Internet Explorer</th>
          <th>Opera</th>
          <th>Safari</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td class="title">IntersectionObserver</td>
          <td>51</td>
          <td>15</td>
          <td>55</td>
          <td class="noSupport">No</td>
          <td>38</td>
          <td>12.1</td>
        </tr>
        <tr>
          <td class="title">IntersectionObserver() constructor</td>
          <td>51</td>
          <td>15</td>
          <td>55*</td>
          <td class="noSupport">No</td>
          <td>38</td>
          <td>12.1</td>
        </tr>
        <tr>
          <td class="title">options.root parameter can be a Document</td>
          <td>81</td>
          <td>81</td>
          <td>76</td>
          <td class="noSupport">No</td>
          <td>68</td>
          <td class="noSupport">No</td>
        </tr>
        <tr>
          <td class="title">disconnect</td>
          <td>51</td>
          <td>15*</td>
          <td>55</td>
          <td class="noSupport">No</td>
          <td>38</td>
          <td>12.1</td>
        </tr>
        <tr>
          <td class="title">observe</td>
          <td>51</td>
          <td>15</td>
          <td>55</td>
          <td class="noSupport">No</td>
          <td>38</td>
          <td>12.1</td>
        </tr>
        <tr>
          <td class="title">root</td>
          <td>51</td>
          <td>15</td>
          <td>55</td>
          <td class="noSupport">No</td>
          <td>38</td>
          <td>12.1</td>
        </tr>
        <tr>
          <td class="title">rootMargin</td>
          <td>51</td>
          <td>15</td>
          <td>55</td>
          <td class="noSupport">No</td>
          <td>38</td>
          <td>12.1*</td>
        </tr>
        <tr>
          <td class="title">takeRecords</td>
          <td>51</td>
          <td>15*</td>
          <td>55</td>
          <td class="noSupport">No</td>
          <td>38</td>
          <td>12.1</td>
        </tr>
        <tr>
          <td class="title">thresholds</td>
          <td>51</td>
          <td>15</td>
          <td>55</td>
          <td class="noSupport">No</td>
          <td>38</td>
          <td>12.1</td>
        </tr>
        <tr>
          <td class="title">unobserve</td>
          <td>51</td>
          <td>15*</td>
          <td>55</td>
          <td class="noSupport">No</td>
          <td>38</td>
          <td>12.1</td>
        </tr>
      </tbody>
    </table>
    </div>
    <br >
      <p class="tableHeader">Mobile Browsers</p>
    <div class="table-container">
    <table id="mobileBrowser">
      <thead>
        <tr>
          <th class="emptyCell"></th>
          <th>WebView Android</th>
          <th>Chrome Android</th>
          <th>Firefox for Android</th>
          <th>Opera Android</th>
          <th>Safari on iOS</th>
          <th>Samsung Internet</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td class="title">IntersectionObserver</td>
          <td>51</td>
          <td>51</td>
          <td>55</td>
          <td>41</td>
          <td>12.2</td>
          <td>5.0</td>
        </tr>
        <tr>
          <td class="title">IntersectionObserver() constructor</td>
          <td>51</td>
          <td>51</td>
          <td>55</td>
          <td>41</td>
          <td>12.2</td>
          <td>5.0</td>
        </tr>
        <tr>
          <td class="title">options.root parameter can be a Document</td>
          <td>81</td>
          <td>81</td>
          <td class="noSupport">No</td>
          <td>58</td>
          <td class="noSupport">No</td>
          <td>13.0</td>
        </tr>
        <tr>
          <td class="title">disconnect</td>
          <td>51</td>
          <td>51</td>
          <td>55</td>
          <td>41</td>
          <td>12.2</td>
          <td>5.0</td>
        </tr>
        <tr>
          <td class="title">observe</td>
          <td>51</td>
          <td>51</td>
          <td>55</td>
          <td>41</td>
          <td>12.2</td>
          <td>5.0</td>
        </tr>
        <tr>
          <td class="title">root</td>
          <td>51</td>
          <td>51</td>
          <td>55</td>
          <td>41</td>
          <td>12.2</td>
          <td>5.0</td>
        </tr>
        <tr>
          <td class="title">rootMargin</td>
          <td>51</td>
          <td>51</td>
          <td>55</td>
          <td>41</td>
          <td>12.2*</td>
          <td>5.0</td>
        </tr>
        <tr>
          <td class="title">takeRecords</td>
          <td>51</td>
          <td>51</td>
          <td>55</td>
          <td>41</td>
          <td>12.2</td>
          <td>5.0</td>
        </tr>
        <tr>
          <td class="title">thresholds</td>
          <td>51</td>
          <td>51</td>
          <td>55</td>
          <td>41</td>
          <td>12.2</td>
          <td>5.0</td>
        </tr>
        <tr>
          <td class="title">unobserve</td>
          <td>51</td>
          <td>51</td>
          <td>55</td>
          <td>41</td>
          <td>12.2</td>
          <td>5.0</td>
        </tr>
      </tbody>
    </table>
    </div>
  </section>
</main>

<footer>
  <h2>Reference:</h2>
  <p>All the documentation in this page is take from <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript" target="_blank" title="MDN Web Docs JavaScript">MDN Web Docs JavaScript</a></p>
</footer>
              
            
!

CSS

              
                body {
  font-family: arial;
}

#navbar {
  box-sizing: border-box;
  position: relative;
  top: 0;
  left: 0;
  width: 100%;
  height: 20rem;
  overflow-y: scroll;
  letter-spacing: 1px;
  background-color: white;
  z-index: 5;
  border-bottom: 1px solid black;
}
#nav-header {
  position: sticky;
  top: 0;
  background-color: white;
}
#nav-header h1 {
  margin: 0;
  min-width: 300px;
  padding: 1em 0;
  color: white;
  text-align: center;
  font-size: 1.25rem;
  border: 1px solid black;
  background-color: green;
} 
.nav-list {
  width: 100%;
  list-style: none;
  padding: 0;
  margin: 0;
}
.nav-link {
  display: block;
  padding: 1em;
  color: black;
  font-size: 1em;
  text-decoration: none;
  text-align: center;
  border: 1px solid black;
  border-top: none;
}
.nav-link:hover {
  color: green;
  font-weight: 700;
  background-color: whitesmoke;
}

#main-doc {
  width: 100%;
}
.main-section {
  padding: 0 .5em;
  font-size: 1.1rem;
  line-height: 1.4;
  margin-bottom: 5em;
}
.main-section h2, aside h2 {
  padding-top: .5em;
  font-size: 2em;
  letter-spacing: 1px;
}
.main-section h3 {
  font-size: 1.5em;
  letter-spacing: 1px;
  margin-bottom: -.5em;
}
.main-section h2 a, .main-section h3 a {
  text-decoration: none;
  color: black;
}
.main-section h2 a:hover, .main-section h3 a:hover {
  color: green;
  text-decoration: underline;
}
.main-section h4 {
  margin-top: 2em;
  font-size: 1.15em;
  margin-bottom: -1em;
}
.main-section p {
  padding-left: 1em;
}
.main-section ul, .main-section ol {
  padding: 0 3em;
}
.main-section li {
  margin-bottom: 1em;
}
.subHeader {
  font-weight:700;
  margin-bottom: -1em;
}

.codeExample {
  margin: 2.5em 0;
  white-space: pre;
  max-width: 1500px;
  overflow-x: scroll;
  line-height: 1.4;
  font-size: 1rem;
}
.jsCode {
  border: 1px solid yellow;
  border-left: 10px solid yellow;
  background-color: rgb(255, 255, 0, 0.1);
}
.styleCode {
  border: 1px solid rgb(35, 101, 199);
  border-left: 10px solid rgb(35, 101, 199);
  background-color: rgb(35, 101, 199, 0.1);
}
.markupCode {
  border: 1px solid rgb(255, 69, 0);
  border-left: 10px solid rgb(255, 69, 0);
  background-color: rgb(255, 69, 0, 0.1);
}
.comments {
  color: grey;
}

#box {
  background-color: rgba(40, 40, 190, 255);
  border: 4px solid rgb(20, 20, 120);
  transition: background-color 1s, border 1s;
  margin: auto;
  max-width: 250px;
  height: 250px;
  max-height: 250px;
  display: flex;
  align-items: center;
  justify-content: center;
}         
.vertical {
  width: 80%;
  color: white;
  font: 32px "Arial";
}
.extra {
  display: flex;
  place-items: center;
  margin: 2em auto;
  max-width: 300px;
  max-height: 300px;
  border: 4px solid rgb(20, 20, 120);
  text-align: center;
  padding: 20px;
}

table {
  
  border-collapse: collapse;
}
.tableHeader {
  padding-left: 0;
  padding-bottom: 0;
  font-size: 1.75em;
  text-align: center;
  letter-spacing: 2px;
  margin-bottom: -.25em;
}
.table-container {
  position: relative;
  margin: 0;
  width: 100%;
  max-width: 725px;
  height: 20em;
  overflow-x: scroll;
  margin-bottom: 2em;
}
table thead {
  position: sticky;
  top: 0;
  background-color: white;
  border-bottom: 1px solid black;
}
table thead tr{
  height: 100px;
  padding-top: 0;
}
table thead tr th {
  min-width: 50px;
  max-width: 50px;
  padding-right: 40px;
  transform: rotatez(-90deg);
  font-size: .9em;
  font-weight: 300;
}
table tbody tr td {
  min-width: 50px;
  max-width: 50px;
  height: 40px;
  font-size: .75em;
  border: 1px solid black;
  text-align: center;
}
.title {
  min-width: 150px;
  max-width: 150px;
  font-size: .8em;
  text-align: left;
  padding: .25em;
  border-top: none;
  border-left: none;
}
.noSupport {
  background-color: rgb(255, 0, 0, 0.5);
}
.emptyCell {
  content: "";
}
aside {
  //margin-left: 400px;
  padding-left: 1em;
}
footer {
  //margin-left: 400px;
}

@media (min-width: 400px) {
  .extra {
    max-width: 380px;
    height: 380px;
  }
  #box {
    max-width: 350px;
    max-height: 350px; 
  }
}
@media (min-width: 700px) {
  .extra {
    max-width: 450px;
    max-height: 450px;
  }
  #box {
    min-width: 425px;
    height: 425px; 
  }
  .table-container {
    margin: auto;
  }
}
@media (min-width: 800px) {
  #navbar {
    position: fixed;
    top: 0;
    left: 0;
    width: 300px;
    height: auto;
    overflow: hidden;
    border-bottom: none;
  }
  #nav-header h1 {
    max-width: 300px;
    font-size: 1.25em;
  }
  .nav-list {
  }
  .nav-link {
    max-width: 300px;
    font-size: .9em;
  }
  #main-doc {
    margin-left: 300px;
    width: 60%;
  }
  .table-container {
    height: auto;
  }
  footer {
    margin-left: 300px;
  }
}
@media (min-width: 1024px) {
  .table-container {
    overflow: hidden;
  }
  #main-doc {
    width: 68%;
  }
}
@media (min-width: 1200px) {
  #navbar {
  min-width: 400px;
  }
  #nav-header h1 {
    max-width: 400px;
    font-size: 1.75em;
  }
  .nav-link {
    max-width: 400px;
    font-size: 1.2em;
  }
  #main-doc {
    margin-left: 400px;
  }
  .main-section {
    padding: 0 1em;
  }
  .main-section h2 {
    font-size: 2em;
  }
  .tableHeader {
    font-size: 2em;
  }
  footer {
    margin-left: 400px;
    padding: 2em;
  }
}
@media (min-width: 1500px) {
  #main-doc {
    width: 73%;
  }
  .main-section {
    padding: 0 2em;
  }
  .main-section h2 {
    font-size: 2.5em;
  }
  .codeExample {
    overflow-x: hidden;
  }
  .tableHeader {
    font-size: 2.5em;
  }
  .table-container {
    max-width: 1000px;
  }
  table thead {
    position: static;
  }
  table thead tr{
    height: 150px;
  }
  table thead tr th {
    min-width: 75px;
    max-width: 75px;
    font-size: 1.2em;
  }
  table tbody tr td {
    min-width: 75px;
    max-width: 75px;
    height: 50px;
    font-size: 1em;
  }
  .title {
    min-width: 250px;
    max-width: 250px;
    font-size: 1em;
    padding: .5em;
  }
} 
@media (min-width: 1900px) {
  #main-doc {
    width: 1450px;
  }
}
              
            
!

JS

              
                const numSteps = 20.0;

  let boxElement;
  let prevRatio = 0.0;
  let increasingColor = "rgba(40, 40, 190, ratio)";
  let decreasingColor = "rgba(190, 40, 40, ratio)";

  // Set things up
  window.addEventListener("load", (event) => {
    boxElement = document.querySelector("#box");

    createObserver();
  }, false);

function createObserver() {
    let observer;

    let options = {
      root: null,
      rootMargin: "0px",
      threshold: buildThresholdList()
    };

    observer = new IntersectionObserver(handleIntersect, options);
    observer.observe(boxElement);
  }

function buildThresholdList() {
    let thresholds = [];
    let numSteps = 20;

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

    thresholds.push(0);
    return thresholds;
  }

function handleIntersect(entries, observer) {
    entries.forEach((entry) => {
      if (entry.intersectionRatio > prevRatio) {
        entry.target.style.backgroundColor = increasingColor.replace("ratio", entry.intersectionRatio);
      } else {
        entry.target.style.backgroundColor = decreasingColor.replace("ratio", entry.intersectionRatio);
      }

      prevRatio = entry.intersectionRatio;
    });
  }
              
            
!
999px

Console