The IntersectionObserver API provides an efficient mechanism to detect when elements enter or exit the viewport. This is particularly useful for managing video playback based on visibility during scrolling. Triggering video play and pause actions dynamically according to user scroll position optimizes resource usage, such as CPU and bandwidth, and ensures that videos only consume resources when visible to the user.

Setting Up IntersectionObserver for Video Playback

To set up playback control based on visibility, create an IntersectionObserver instance with a callback that receives intersection entries. Each entry provides information about whether the target element (video) is intersecting with the viewport according to defined thresholds.

Example:

code
const videos = document.querySelectorAll('video');

const observerCallback = (entries, observer) => {
entries.forEach(entry => {
const video = entry.target;
if (entry.isIntersecting) {
video.play();
} else {
video.pause();
}
});
};

const observerOptions = {
root: null, // Observe visibility relative to viewport
rootMargin: '0px', // No margin around viewport
threshold: 0.5 // Trigger callback when 50% of video is visible
};

const observer = new IntersectionObserver(observerCallback, observerOptions);

videos.forEach(video => observer.observe(video));

Explanation:

  • threshold: 0.5 means the callback triggers when at least half of the video element is visible.
  • When a video crosses this visibility threshold, playback starts or pauses accordingly.
  • This method prevents offscreen videos from playing unnecessarily, conserving CPU and bandwidth.
Banner for Video Galleries

Optimizing Playback Triggers

Debouncing Playback

During rapid scrolling, multiple visibility changes may cause frequent toggling of video playback, potentially degrading performance and causing erratic user experience. Debouncing delays execution until scrolling activity stabilizes.

code
let timeoutId;

const debouncedCallback = (entries) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.play();
} else {
entry.target.pause();
}
});
}, 100);
};

const observer = new IntersectionObserver(debouncedCallback, observerOptions);

Explanation:

  • Debouncing delays execution until scrolling settles.
  • This reduces rapid play/pause calls improving performance and user experience.

Handling Multiple Videos and Resource Management

On pages with multiple videos, unrestricted concurrent playback can cause overlapping audio and high resource consumption. To prevent this, implement logic that allows only one video to play at a time.

code
let activeVideo = null;

const observerCallback = (entries) => {
entries.forEach(entry => {
const video = entry.target;
if (entry.isIntersecting && activeVideo !== video) {
if (activeVideo) activeVideo.pause();
video.play();
activeVideo = video;
} else if (!entry.isIntersecting && activeVideo === video) {
video.pause();
activeVideo = null;
}
});
};

Explanation:

  • Ensures only one video plays at a time.
  • Prevents overlapping audio and excessive resource consumption.

Managing Visibility Changes and Tab Focus

Browser tabs that are not visible should avoid playing videos to conserve battery and CPU. Listening for page visibility changes allows pausing active videos when the tab is inactive and resuming playback when visible again.

Implementation:

code
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
if (activeVideo) activeVideo.pause();
} else {
if (activeVideo) activeVideo.play();
}
});

Explanation:

  • Listens to page visibility changes.
  • Pauses video when the tab is hidden and resumes when visible.

Customizing Thresholds for Different Use Cases

Adjust the threshold and rootMargin values to control when playback triggers, depending on the layout or desired behavior.

Example:

code
const observerOptions = {
rootMargin: '0px 0px -50% 0px', // Trigger when video is halfway visible
threshold: 0
};

Explanation:

  • rootMargin offsets the viewport boundaries to trigger earlier or later.
  • Useful for triggering playback before the video is fully visible.

Cleanup and Unobserving Elements

When videos are dynamically added or removed from the DOM, it is important to manage observation properly to prevent memory leaks and avoid unnecessary callbacks.

Example:

code
videos.forEach(video => {
observer.observe(video);
});

// Later, to stop observing
observer.unobserve(someVideoElement);

Explanation:

  • Proper cleanup prevents observers from monitoring detached or irrelevant elements.
  • Improves performance in dynamic content environments.