In short
Browser APIs like Intersection Observer, ResizeObserver, the Clipboard API and Geolocation add powerful features to your pages. Learn lazy loading, copy buttons and location with permissions. What is the Intersection Observer API?
Browser APIs like Intersection Observer, ResizeObserver, the Clipboard API and Geolocation add powerful features to your pages. Learn lazy loading, copy buttons and location with permissions.
What is the Intersection Observer API?
The Intersection Observer tells you when an element enters or leaves the visible part of the screen (the viewport). Think of a security guard at a door who calls you when someone walks in, so you don't have to keep looking yourself.
The old way was to listen to the scroll event and measure every element on every scroll. That is slow. An Intersection Observer lets the browser do the watching efficiently.
Reveal-on-scroll and lazy loading with IntersectionObserver
Let's fade in cards as they scroll into view.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Reveal on scroll</title>
<style>
.card { margin: 60vh auto; width: 300px; padding: 2rem; background: #eef; opacity: 0; transform: translateY(40px); transition: opacity 0.6s, transform 0.6s; }
.card.visible { opacity: 1; transform: none; }
</style>
</head>
<body>
<div class="card">Feature one</div>
<div class="card">Feature two</div>
<div class="card">Feature three</div>
<script>
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('visible'); // show the card
observer.unobserve(entry.target); // stop watching it
}
});
}, { threshold: 0.2 }); // fire when 20% of the card is visible
document.querySelectorAll('.card').forEach((card) => observer.observe(card));
</script>
</body>
</html>
new IntersectionObserver(callback, options) creates the observer. observe() starts watching an element. The callback receives a list of entries; entry.isIntersecting is true when the element is on screen. We add the class once and call unobserve() because we only want the effect one time. As you scroll down, each card fades and slides up.
The same idea works for lazy loading: keep the real image address in a data-src attribute and copy it to src when the image comes close.
const lazyObserver = new IntersectionObserver((entries, obs) => {
for (const entry of entries) {
if (!entry.isIntersecting) continue;
const img = entry.target;
img.src = img.dataset.src; // start downloading now
obs.unobserve(img);
}
}, { rootMargin: '200px' }); // start 200px before it reaches the screen
document.querySelectorAll('img[data-src]').forEach((img) => lazyObserver.observe(img));
rootMargin: '200px' makes the observer react a little early, so the image is often ready by the time the user sees it.
Tip: For plain images, you don't need JavaScript at all:
<img src="photo.jpg" loading="lazy" alt="...">lazy loads natively in all modern browsers. Use an Intersection Observer for custom effects, infinite scroll, or loading other content.
ResizeObserver in brief
A ResizeObserver works the same way, but tells you when an element changes size. It is useful for charts or components that must adapt to their box, not the whole window.
const box = document.querySelector('.card');
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
console.log(`New width: ${Math.round(entry.contentRect.width)}px`);
}
});
resizeObserver.observe(box);
// Output: New width: 300px (and again whenever the card's size changes)
How to copy text to the clipboard with JavaScript
The Clipboard API lets you copy text with navigator.clipboard.writeText(). It returns a promise, so we use async/await.
<p>Your invite code: <code id="code">WELCOME-2026</code></p>
<button id="copyBtn">Copy code</button>
<script>
const copyBtn = document.querySelector('#copyBtn');
copyBtn.addEventListener('click', async () => {
const text = document.querySelector('#code').textContent;
try {
await navigator.clipboard.writeText(text);
copyBtn.textContent = 'Copied!';
} catch (error) {
copyBtn.textContent = 'Copy failed';
console.error('Clipboard error:', error);
}
setTimeout(() => (copyBtn.textContent = 'Copy code'), 2000);
});
</script>
When you click the button, the code is copied and the button says "Copied!" for two seconds. The try...catch handles the case where the browser blocks the copy.
Note: The Clipboard API only works on secure pages (HTTPS or
localhost) and usually only inside a user action, such as a click. Opening the file directly withfile://may not work.
How to get the user's location with the Geolocation API
The Geolocation API asks the browser for the user's position. The browser always asks the user for permission first, and the user can say no. Your code must handle both answers.
const showLocation = () => {
if (!('geolocation' in navigator)) {
console.log('Geolocation is not supported.');
return;
}
navigator.geolocation.getCurrentPosition(
(position) => {
const { latitude, longitude, accuracy } = position.coords;
console.log(`You are near ${latitude.toFixed(3)}, ${longitude.toFixed(3)} (±${Math.round(accuracy)} m)`);
},
(error) => {
if (error.code === error.PERMISSION_DENIED) {
console.log('You said no. You can type your city instead.');
} else if (error.code === error.TIMEOUT) {
console.log('Finding your location took too long.');
} else {
console.log('Your location is not available right now.');
}
},
{ timeout: 10000 } // give up after 10 seconds
);
};
document.querySelector('button')?.addEventListener('click', showLocation);
// Output (after allowing): You are near 51.507, -0.128 (±35 m)
getCurrentPosition() takes a success function, an error function and options. It uses callbacks, not promises. The error code tells you why it failed. Always offer another way, like typing a city, when permission is denied.
You can check the permission state without showing a popup using navigator.permissions.query({ name: 'geolocation' }), which resolves to 'granted', 'denied' or 'prompt'.
A note on notifications and permissions
Notification.requestPermission() asks if your site may show system notifications. The same rules apply as for location: only ask after the user clicks something that clearly needs it, like a "Notify me" button. Asking as soon as the page loads annoys people, and many browsers quietly block those requests. If the answer is 'denied', you cannot ask again from code; the user must change it in the browser settings. Read more on MDN's Intersection Observer page.
Practice exercise
Build a "Back to top" button that only appears after the user scrolls past the page header. Put an Intersection Observer on the <header>: when the header is NOT intersecting, show the button; when it is, hide it. Bonus: add a "Copy page link" button that copies location.href.
Hint: toggle a class with button.classList.toggle('show', !entry.isIntersecting).
Key takeaways
- IntersectionObserver tells you when elements enter the viewport, without slow scroll listeners.
- Use it for reveal-on-scroll and custom lazy loading; plain images can use
loading="lazy". - ResizeObserver reacts to an element's size changes.
navigator.clipboard.writeText()copies text on secure pages, inside a user action.- Geolocation and notifications need permission: ask at the right moment and always handle "no".