Intersection Observer

Intersection Observer intersection observer link

Full documentation: https://webawesome.com/docs/components/intersection-observer

<wa-intersection-observer> Stable Since 2.0

Tracks immediate child elements and fires events as they move in and out of view. Useful for lazy loading, scroll-triggered animations, and viewport-aware interactions.

This component leverages the IntersectionObserver API to track when its direct children enter or leave a designated root element. The wa-intersect event fires whenever elements cross the visibility threshold.

<div id="intersection__overview">
  <wa-intersection-observer threshold="1" intersect-class="visible">
    <div class="box"><wa-icon name="lightbulb"></wa-icon></div>
  </wa-intersection-observer>
</div>

<small>Scroll to see the element intersect at 100% visibility</small>

<style>
  /* Container styles */
  #intersection__overview {
    display: flex;
    flex-direction: column;
    gap: 2rem;
    height: 300px;
    border: solid 2px var(--wa-color-surface-border);
    padding: 1rem;
    overflow-y: auto;

    /* Spacers to demonstrate scrolling */
    &::before {
      content: '';
      height: 260px;
      flex-shrink: 0;
    }

    &::after {
      content: '';
      height: 260px;
      flex-shrink: 0;
    }

    /* Box styles */
    .box {
      flex-shrink: 0;
      width: 120px;
      height: 120px;
      background-color: var(--wa-color-neutral-fill-normal);
      color: var(--wa-color-neutral-10);
      display: flex;
      align-items: center;
      justify-content: center;
      margin-inline: auto;
      transition: all 50ms cubic-bezier(0.68, -0.55, 0.265, 1.55);

      wa-icon {
        font-size: 3rem;
        stroke-width: 1px;
      }

      &.visible {
        background-color: var(--wa-color-brand-60);
        color: white;
      }
    }

    + small {
      display: block;
      text-align: center;
      margin-block-start: 1rem;
    }
  }
</style>

Keep in mind that only direct children of the host element are monitored. Nested elements won’t trigger intersection events.

Usage Examples usage examples link

Adding Observable Content adding observable content link

The intersection observer tracks only its direct children. The component uses display: contents styling, which makes it seamless to integrate with flex and grid layouts from a parent container.

<div style="display: flex; flex-direction: column;">
  <wa-intersection-observer>
    <div class="box">Box 1</div>
    <div class="box">Box 2</div>
    <div class="box">Box 3</div>
  </wa-intersection-observer>
</div>

The component tracks elements as they enter and exit the root element (viewport by default) and emits the wa-intersect event on state changes. The event provides event.detail.entry, an IntersectionObserverEntry object with intersection details.

You can identify the triggering element through entry.target. Check entry.isIntersecting to determine if an element is entering or exiting the viewport.

observer.addEventListener('wa-intersect', event => {
  const entry = event.detail.entry;

  if (entry.isIntersecting) {
    console.log('Element entered viewport:', entry.target);
  } else {
    console.log('Element left viewport:', entry.target);
  }
});

Setting a Custom Root Element setting a custom root element link

You can observe intersections within a specific container by assigning the root attribute to the root element’s ID. Apply rootMargin with the root-margin attribute to expand or contract the observation area.

<div id="scroll-container">
  <wa-intersection-observer root="scroll-container" root-margin="50px 0px"> ... </wa-intersection-observer>
</div>

Configuring Multiple Thresholds configuring multiple thresholds link

Track different visibility percentages by providing multiple threshold values as a space-separated list.

<wa-intersection-observer threshold="0 0.25 0.5 0.75 1"> ... </wa-intersection-observer>

Applying Classes on Intersect applying classes on intersect link

The intersect-class attribute automatically toggles the specified class on direct children when they become visible. This enables pure CSS styling without JavaScript event handlers.

<div id="intersection__classes">
  <wa-intersection-observer threshold="0.5" intersect-class="visible" root="intersection__classes">
    <div class="box fade">Fade In</div>
    <div class="box slide">Slide In</div>
    <div class="box scale">Scale & Rotate</div>
    <div class="box bounce">Bounce</div>
  </wa-intersection-observer>
</div>

<small>Scroll to see elements transition at 50% visibility</small>

<style>
  /* Container styles */
  #intersection__classes {
    display: flex;
    flex-direction: column;
    gap: 2rem;
    height: 300px;
    border: solid 2px var(--wa-color-surface-border);
    padding: 1rem;
    overflow-y: auto;

    /* Spacers to demonstrate scrolling */
    &::before {
      content: '';
      height: 260px;
      flex-shrink: 0;
    }

    &::after {
      content: '';
      height: 260px;
      flex-shrink: 0;
    }

    + small {
      display: block;
      text-align: center;
      margin-block-start: 1rem;
    }

    /* Shared box styles */
    .box {
      flex-shrink: 0;
      width: 120px;
      height: 120px;
      display: flex;
      align-items: center;
      justify-content: center;
      text-align: center;
      color: white;
      opacity: 0;
      padding: 2rem;
      margin-inline: auto;

      /* Fade */
      &.fade {
        background: var(--wa-color-brand-fill-loud);
        color: var(--wa-color-brand-on-loud);
        transform: translateY(30px);
        transition: all 0.6s ease;

        &.visible {
          opacity: 1;
          transform: translateY(0);
        }
      }

      /* Slide */
      &.slide {
        background: var(--wa-color-brand-fill-loud);
        color: var(--wa-color-brand-on-loud);
        transform: translateX(-50px);
        transition: all 0.5s ease;

        &.visible {
          opacity: 1;
          transform: translateX(0);
        }
      }

      /* Scale */
      &.scale {
        background: var(--wa-color-brand-fill-loud);
        color: var(--wa-color-brand-on-loud);
        transform: scale(0.6) rotate(-15deg);
        transition: all 0.7s cubic-bezier(0.175, 0.885, 0.32, 1.275);

        &.visible {
          opacity: 1;
          transform: scale(1) rotate(0deg);
        }
      }

      /* Bounce In and Out */
      &.bounce {
        background: var(--wa-color-brand-fill-loud);
        color: var(--wa-color-brand-on-loud);
        opacity: 0;
        transform: scale(0.8);
        transition: none;

        &.visible {
          opacity: 1;
          transform: scale(1);
          animation: bounceIn 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55) forwards;
        }

        &:not(.visible) {
          animation: bounceOut 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55) forwards;
        }
      }
    }
  }

  @keyframes bounceIn {
    0% {
      transform: scale(0.8);
    }
    40% {
      transform: scale(1.08);
    }
    65% {
      transform: scale(0.98);
    }
    80% {
      transform: scale(1.02);
    }
    90% {
      transform: scale(0.99);
    }
    100% {
      transform: scale(1);
    }
  }

  @keyframes bounceOut {
    0% {
      transform: scale(1);
      opacity: 1;
    }
    20% {
      transform: scale(1.02);
      opacity: 1;
    }
    40% {
      transform: scale(0.98);
      opacity: 0.8;
    }
    60% {
      transform: scale(1.05);
      opacity: 0.6;
    }
    80% {
      transform: scale(0.95);
      opacity: 0.3;
    }
    100% {
      transform: scale(0.8);
      opacity: 0;
    }
  }
</style>

Importing importing link

If you’re using the autoloader or a hosted project, components load on demand — no manual import needed. To cherry-pick a component manually, use one of the following snippets.

**CDN**

Import this component directly from the CDN:

import 'https://ka-f.webawesome.com/webawesome@3.7.0/components/intersection-observer/intersection-observer.js';

**npm**

After installing Web Awesome via npm, import this component:

import '@awesome.me/webawesome/dist/components/intersection-observer/intersection-observer.js';

**Self-Hosted**

If you’re self-hosting Web Awesome, import this component from your server:

import './webawesome/dist/components/intersection-observer/intersection-observer.js';

**React**

To import this component for React 18 or below, use the following code:

import WaIntersectionObserver from '@awesome.me/webawesome/dist/react/intersection-observer/index.js';

Slots slots link

Learn more about using slots.

Name Description
(default) Elements to track. Only immediate children of the host are monitored.

Attributes & Properties attributes properties link

Learn more about attributes and properties.

Name Description Reflects  
`css` `CSSResultGroup \| undefined` One or more CSSResultGroup to include in the component’s shadow root. Host styles are automatically prepended. Type Default styles    
`disabled` disabled `boolean` Deactivates the intersection observer functionality. Type Default false    
`intersectClass` intersect-class `string` CSS class applied to elements during intersection. Automatically removed when elements leave the viewport, enabling pure CSS styling based on visibility state. Type Default ‘’    
`once` once `boolean` If enabled, observation ceases after initial intersection. Type Default false    
`root` root `string \| null` Element ID to define the viewport boundaries for tracked targets. Type Default null    
`rootMargin` root-margin `string` Offset space around the root boundary. Accepts values like CSS margin syntax. Type Default ‘0px’    
`threshold` threshold `string` One or more space-separated values representing visibility percentages that trigger the observer callback. Type Default ‘0’    

Events events link

Learn more about events.

Name Description
`wa-intersect` Fired when a tracked element begins or ceases intersecting.

Need a hand? Report a bug Ask for help