# Recipes
The `IntersectionObserver` itself is just a simple but powerful tool. Here's a
few ideas for how you can use it.
## Lazy image load
It's actually easy to create your own lazy image loader, and this allows you to
build it according to your needs.
**Couple of tips**
- Don't set the `src` (or `srcset`) on the image until it's visible. Images will
always load their `src`, even if you set `display: none;`.
- Make sure to set the
[root margin](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/rootMargin)
for top and bottom, so the Intersection Observer gets triggered before the
image enters the viewport. This gives the image a chance to be loaded before
the user even sees it. Try to start with something like `200px 0px`, but
experiment with it until you find the right value.
- Set `triggerOnce`, so you don't keep monitoring for changes.
- You should always create a wrapping element, that sets the correct aspect
ratio for the image. You can set the padding bottom to be
`${height / width * 100}%` to maintain aspect ratio.
- Either hide the `
` with CSS, or skip rendering it until it's inside the
viewport.
> [!TIP]
> All modern browsers support the native `loading` attribute on `
` tags, so unless you need
> fine-grained control, you can skip the `IntersectionObserver` and use `loading="lazy"` instead.
>
> https://web.dev/articles/browser-level-image-lazy-loading
```jsx
import React from "react";
import { useInView } from "react-intersection-observer";
const LazyImage = ({ width, height, src, ...rest }) => {
const { ref, inView } = useInView({
triggerOnce: true,
rootMargin: "200px 0px",
});
return (
{inView ? (

) : null}
);
};
export default LazyImage;
```
**See [Codesandbox](https://codesandbox.io/embed/lazy-image-load-mjsgc)**
## Trigger animations
Triggering animations once they enter the viewport is also a perfect use case
for an IntersectionObserver.
- Set `triggerOnce`, to only trigger the animation the first time.
- Set `threshold`, to control how much of the element should be visible before
firing the event.
- Instead of `threshold`, you can use `rootMargin` to have a fixed amount be
visible before triggering. Use a negative margin value, like `-100px 0px`, to
have it go inwards. You can also use a percentage value, instead of pixels.
```jsx
import React from "react";
import { useInView } from "react-intersection-observer";
const LazyAnimation = () => {
const { ref, inView } = useInView({
triggerOnce: true,
rootMargin: "-100px 0px",
});
return (
👋
);
};
export default LazyAnimation;
```
## Track impressions
You can use `IntersectionObserver` to track when a user views your element, and
fire an event on your tracking service. Consider using the `useOnInView` to
trigger changes via a callback.
- Set `triggerOnce`, to only trigger an event the first time the element enters
the viewport.
- Set `threshold`, to control how much of the element should visible before
firing the event.
- Instead of `threshold`, you can use `rootMargin` to have a fixed amount be
visible before triggering. Use a negative margin value, like `-100px 0px`, to
have it go inwards. You can also use a percentage value, instead of pixels.
```jsx
import * as React from "react";
import { useOnInView } from "react-intersection-observer";
const TrackImpression = () => {
const ref = useOnInView((inView) => {
if (inView) {
// Fire a tracking event to your tracking service of choice.
dataLayer.push("Section shown"); // Here's a GTM dataLayer push
}
}, {
triggerOnce: true,
rootMargin: "-100px 0",
});
return (
Exemplars sunt zeluss de bassus fuga. Credere velox ducunt ad audax amor.
);
};
export default TrackImpression;
```