Intersection Observer
<quiet-intersection-observer>
Intersection observers watch child elements and dispatch events when they intersect with their root element.
The component uses an
IntersectionObserver
to monitor when its direct children intersect with a root element. The quiet-intersect
event is
dispatched when elements enter and leave the viewport.
<div id="intersection__overview"> <quiet-intersection-observer threshold="1" intersect-class="visible"> <div class="box"><quiet-icon name="bulb"></quiet-icon></div> </quiet-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: 2px solid var(--quiet-neutral-stroke-soft); border-radius: var(--quiet-border-radius); 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; border: var(--quiet-border-style) var(--quiet-border-width) var(--quiet-neutral-stroke-softer); border-radius: var(--quiet-border-radius); background-color: var(--quiet-neutral-fill-softer); color: var(--quiet-neutral-text-on-soft); box-shadow: var(--quiet-shadow-softer); display: flex; align-items: center; justify-content: center; margin-inline: auto; transition: all 50ms cubic-bezier(0.68, -0.55, 0.265, 1.55); quiet-icon { font-size: 3rem; stroke-width: 1px; } &.visible { background-color: var(--quiet-primary-fill-softer); border-color: var(--quiet-primary-stroke-soft); color: var(--quiet-primary-text-colorful); opacity: 1; box-shadow: var(--quiet-shadow-soft); } } + small { display: block; text-align: center; margin-block-start: 1rem; } } </style>
Remember that only direct children of the host element are observed. Nested elements will not trigger intersection events.
Examples Jump to heading
Providing content Jump to heading
Only direct children of the intersection observer are observed. The component is styled with
display: contents
, allowing you to easily apply flex and grid layouts to a containing element.
<div style="display: flex; flex-direction: column;"> <quiet-intersection-observer> <div class="box">Box 1</div> <div class="box">Box 2</div> <div class="box">Box 3</div> </quiet-intersection-observer> </div>
The component monitors when elements enter and leave the root element (the viewport by default) and
dispatches the quiet-intersect
event whenever an intersection state changes. The event includes
event.detail.entry
, which is an
IntersectionObserverEntry
object containing information about the intersection.
You can determine which element triggered the event with entry.target
. You can determine
whether an element is entering or leaving the viewport by checking entry.isIntersecting
.
observer.addEventListener('quiet-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); } });
Customizing the root Jump to heading
Intersections can be observed within a specific container by setting the root
attribute to the
ID of the
root element. Use root-margin
to apply a
rootMargin
to one or all sides of the element.
<div id="scroll-container"> <quiet-intersection-observer root="scroll-container" root-margin="50px 0px" > ... </quiet-intersection-observer> </div>
Providing multiple thresholds Jump to heading
You can monitor different visibility percentages by specifying multiple
threshold
values separated by a space.
<quiet-intersection-observer threshold="0 0.25 0.5 0.75 1"> ... </quiet-intersection-observer>
Applying classes on intersect Jump to heading
Use the intersect-class
attribute to automatically apply the specified class to direct children
when they intersect. This makes it easy to style them without event listeners.
<div id="intersection__classes"> <quiet-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> </quiet-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: 2px solid var(--quiet-neutral-stroke-softer); border-radius: var(--quiet-border-radius); 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; border-radius: 8px; display: flex; align-items: center; justify-content: center; text-align: center; color: white; font-weight: bold; text-shadow: 0 1px #0008; opacity: 0; padding: 2rem; margin-inline: auto; /* Fade */ &.fade { background: linear-gradient(135deg, #5a6fd4 0%, #6a4292 100%); transform: translateY(30px); transition: all 0.6s ease; &.visible { opacity: 1; transform: translateY(0); } } /* Slide */ &.slide { background: linear-gradient(135deg, #d984e2 0%, #dd4e61 100%); transform: translateX(-50px); transition: all 0.5s ease; &.visible { opacity: 1; transform: translateX(0); } } /* Scale */ &.scale { background: linear-gradient(135deg, #439be4 0%, #00dae4 100%); 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: linear-gradient(135deg, #e1658a 0%, #e5cb39 100%); 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>
Use CSS transitions and animations to create advanced, modern effects without a single line of JavaScript.
<div id="intersection__images"> <quiet-intersection-observer threshold="0.8" intersect-class="visible" root="intersection__images" > <img src="https://images.unsplash.com/photo-1671707696618-ca0685b0012e?q=80&w=1000&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="An orange cat smiles up at the camera"> <img src="https://images.unsplash.com/photo-1736593494119-d0a69181b414?q=80&w=1000&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="A kitten lays in its bed and cuddles a pillow"> <img src="https://images.unsplash.com/photo-1622576041274-ae5dc580175d?q=80&w=800&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="Two kittens nestled up on a blanket"> <img src="https://images.unsplash.com/photo-1719310694054-6fc99b7c14ec?q=80&w=1000&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="An orange kitten explores a tall grassy yard"> <img src="https://images.unsplash.com/photo-1601217156006-3358e1514676?q=80&w=1000&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="A kitten peeks out from inside a cardboard box"> <img src="https://images.unsplash.com/photo-1737912031624-e17ba0d7f673?q=80&w=1000&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="A gray and white cat sleeps on a blanket with evening lights in the background"> </quiet-intersection-observer> </div> <style> /* Container styles */ #intersection__images { display: flex; flex-direction: row; gap: 0; border: 2px solid var(--quiet-neutral-stroke-soft); border-radius: var(--quiet-border-radius); padding: 1rem; overflow-y: auto; scroll-snap-type: x mandatory; /* Image styles */ img { flex-shrink: 0; display: block; width: 75%; object-fit: cover; border-radius: var(--quiet-border-radius); background-color: var(--quiet-neutral-fill-softer); color: var(--quiet-neutral-text-on-soft); box-shadow: var(--quiet-shadow-softer); filter: grayscale(100%); margin-inline: auto; transform: scale(0.85) rotateY(-15deg); opacity: 0.6; transition: all 600ms cubic-bezier(0.25, 0.46, 0.45, 0.94); scroll-snap-align: center; &.visible { background-color: var(--quiet-primary-fill-softer); transform: scale(1) rotateY(0deg); filter: grayscale(0); opacity: 1; box-shadow: var(--quiet-shadow-soft); } } } </style>
API Jump to heading
Importing Jump to heading
The autoloader is the recommended way to import components but, if you prefer to do it manually, the following code snippets will be helpful.
To manually import <quiet-intersection-observer>
from the CDN, use the following
code.
import 'https://cdn.jsdelivr.net/npm/@quietui/quiet-browser@1.0.0/dist/components/intersection-observer/intersection-observer.js';
To manually import <quiet-intersection-observer>
from npm, use the following code.
import '@quietui/quiet/dist/components/intersection-observer/intersection-observer.js';
Slots Jump to heading
Intersection Observer supports the following slots. Learn more about using slots
Name | Description |
---|---|
(default) | The elements to observe. Only direct children of the host element are observed. |
Properties Jump to heading
Intersection Observer has the following properties that can be set with corresponding attributes. In many cases, the attribute's name is the same as the property's name. If an attribute is different, it will be displayed after the property. Learn more about attributes and properties
Property / Attribute | Description | Reflects | Type | Default |
---|---|---|---|---|
root
|
The ID of the element to use as as the bounding box of the viewport for the observed targets. |
|
string
|
null
|
rootMargin
root-margin
|
Margin around the root. Can have values similar to the CSS margin property. |
|
string
|
'0px'
|
threshold
|
Either a single number or space-delimited numbers which indicate at what percentage of the target's visibility the observer's callback should be executed. |
|
string
|
'0'
|
intersectClass
intersect-class
|
A CSS class name to apply to elements while they're intersecting. The class will be removed when the element is no longer in the viewport. This allows you to apply styles to elements as they enter and exit the viewport using pure CSS. |
|
string
|
''
|
once
|
When true, stops observing after the first intersection. |
|
boolean
|
false
|
disabled
|
Disables the intersection observer. |
|
boolean
|
false
|
Events Jump to heading
Intersection Observer dispatches the following custom events. You can listen to them the same way was native events. Learn more about custom events
Name | Description |
---|---|
quiet-intersect |
Emitted when an observed element starts or stops intersecting.
event.detail.entry contains the respective
IntersectionObserverEntry
object.
|