Intersection Observer
<wa-intersection-observer>
Tracks immediate child elements and fires events as they move in and out of view.
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="bulb"></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 Jump to heading
Adding Observable Content Jump to heading
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 Jump to heading
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 Jump to heading
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 Jump to heading
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>
Slots Jump to heading
Learn more about using slots.
| Name | Description |
|---|---|
| (default) | Elements to track. Only immediate children of the host are monitored. |
Attributes & Properties Jump to heading
Learn more about attributes and properties.
| Name | Description | Reflects | |
|---|---|---|---|
rootroot |
Element ID to define the viewport boundaries for tracked targets.
Type
string | nullDefault
null |
||
rootMarginroot-margin |
Offset space around the root boundary. Accepts values like CSS margin syntax.
Type
stringDefault
'0px' |
||
thresholdthreshold |
One or more space-separated values representing visibility percentages that trigger the observer callback.
Type
stringDefault
'0' |
||
intersectClassintersect-class |
CSS class applied to elements during intersection. Automatically removed when elements leave
the viewport, enabling pure CSS styling based on visibility state.
Type
stringDefault
'' |
||
onceonce |
If enabled, observation ceases after initial intersection.
Type
booleanDefault
false |
|
|
disableddisabled |
Deactivates the intersection observer functionality.
Type
booleanDefault
false |
|
Events Jump to heading
Learn more about events.
| Name | Description |
|---|---|
wa-intersect |
Fired when a tracked element begins or ceases intersecting. |
Importing Jump to heading
Autoloading components via projects is the recommended way to import components. If you prefer to do it manually, use one of the following code snippets.
Let your project code do the work! Sign up for free to use a project with your very own CDN — it's the fastest and easiest way to use Web Awesome.
To manually import this component from NPM, use the following code.
import '@awesome.me/webawesome/dist/components/intersection-observer/intersection-observer.js';
To manually import this component from React, use the following code.
import WaIntersectionObserver from '@awesome.me/webawesome/dist/react/intersection-observer';