npm GitHub

📺 Youtube Background

npm version CI gzip size

ESM module for turning a YouTube, Vimeo or plain video file link into a cover background — with autoplay, looping, play/mute controls, seek bars and playlist groups.

npm install youtube-background

Important

2.0 removed the jQuery plugin and renamed the bundles. The factory and every option, event and method are unchanged — jquery.youtube-background.js is now dist/youtube-background.js, and youtube-background-experimental.js is dist/youtube-background-controls.js. The 1.x page is here.

Note

If you would rather write markup than call a constructor, the same three providers ship as a custom element in video-background-element<video-background src="…">, no factory and no selector. It is where new features go; this package stays maintained for the [data-vbg] API.

Live demos

Every band below is a real instance. The markup that produces it is right under it.

YouTube

Autoplaying, muted, looping, and paused whenever it scrolls out of view.

<div data-vbg="https://www.youtube.com/watch?v=eEpEeyqGlxA" data-vbg-load-background="true"></div>

A slice of a video

start-at skips the intro, end-at keeps the loop short. The play button is the plugin's own.

<div
  data-vbg="https://www.youtube.com/watch?v=MgDZBqTuUuE"
  data-vbg-play-button="true"
  data-vbg-start-at="10"
  data-vbg-end-at="16"
></div>

Sound, and your own controls

Unmute with the plugin's button, or drive it from buttons of your own — those keep their name and carry the state in aria-pressed.

Vimeo

Same attributes, different platform. Unlisted links keep their hash.

<div data-vbg="https://vimeo.com/137250145" data-vbg-mute-button="true"></div>
<!-- unlisted -->
<div data-vbg="https://vimeo.com/304887422/34c51f7a09"></div>

A plain video file

Any .mp4, .webm, .ogg, .avi, .mov, .m4v or .qt URL, played in a native <video>.

<div
  data-vbg="https://media.w3.org/2010/05/sintel/trailer.mp4"
  data-vbg-poster="https://media.w3.org/2010/05/sintel/poster.png"
  data-vbg-start-at="10"
  data-vbg-end-at="25"
></div>

A group

Three backgrounds across all three source types, played as one playlist.

<div class="js-vbg-group">
  <div data-vbg="https://www.youtube.com/watch?v=LC5rEhxGqT4" data-vbg-loop="false" data-vbg-load-background="true"></div>
  <div data-vbg="https://vimeo.com/137250145" data-vbg-loop="false" data-vbg-autoplay="false" data-vbg-load-background="true" style="display: none"></div>
  <div data-vbg="https://media.w3.org/2010/05/bunny/trailer.mp4" data-vbg-loop="false" data-vbg-autoplay="false" data-vbg-end-at="20" style="display: none"></div>
</div>

A group only toggles display between its children, so stacking them is your CSS: the demo above absolutely positions all three over the same box and hides every one but the first. loop="false" is what makes a member end, and an ended member is what advances the group, so a looping member would hold the playlist forever.

The video files are the Blender Foundation's Sintel and Big Buck Bunny trailers, CC-BY 3.0, served from media.w3.org.

Usage

Point an empty element at a video and initialise:

import { VideoBackgrounds } from "youtube-background";

new VideoBackgrounds("[data-vbg]");

Or from a script tag, where the bundle exposes window.VideoBackgrounds:

<script src="youtube-background.min.js"></script>

The seek bars, play/mute toggles and group controls used above are optional and live in a bundle of their own, with an optional stylesheet. Each control is a class over markup you write, pointed at a background by data-target, and each has a destroy() that takes its listeners back:

<link rel="stylesheet" href="youtube-background-controls.min.css">
<script src="youtube-background-controls.min.js"></script>

Options

Every option is settable as a data-vbg-* attribute on the element, or as a key in the object passed to the constructor. Attributes win.

Option Default What it does
autoplay true Start as soon as the element is in view
muted true Start muted — required for autoplay to be allowed
loop true Restart when the video ends
mobile true Create the background on mobile too
always-play false Keep playing while off-screen
start-at 0 Seconds to start from
end-at 0 Seconds to stop at, 0 for the full duration
volume 1 01, applied on first unmute
play-button false Render the plugin's play/pause toggle
mute-button false Render the plugin's mute toggle
poster null Image shown until the first frame plays
load-background false Use the platform's own thumbnail as the poster
resolution '16:9' Aspect ratio used to cover the container
fit-box false Stretch to the container instead of covering it
inline-styles true Let the plugin write the positioning styles
no-cookie true Use the privacy-preserving embed domains
lazyloading false Add loading="lazy" to the iframe — YouTube and Vimeo only
title 'Video background' Accessible name for the player frame

Limits

Since May 2026 (#77), YouTube's player flashes its own round play/pause icon in the middle of the frame on every playback toggle — .ytp-bezel, drawn inside the iframe, which the embed's controls=0 does not cover. The frame is cross-origin, so neither your CSS nor your script reaches it, and the plugin's own play and mute buttons set it off like any other toggle. There is no option that turns it off, here or upstream.

Cosmetic filtering hides it — an element-hiding rule in Adblock Plus / uBlock Origin syntax, where ## means hide this selector on these domains:

www.youtube-nocookie.com,www.youtube.com##.html5-video-player .ytp-bezel

Both domains, because no-cookie defaults to true and puts the player on www.youtube-nocookie.com.

Warning

That fixes the browser it is typed into and nothing further. A content blocker is an extension, and injecting a stylesheet into a cross-origin frame is a permission extensions have and pages do not — your CSS never enters the frame, iframe.contentDocument throws. So it is a development comfort on localhost, and every visitor without that filter still sees the bezel.

Events

Every instance dispatches on its own element and bubbles:

video-background-ready, video-background-play, video-background-pause, video-background-ended, video-background-seeked, video-background-time-update, video-background-state-change, video-background-mute, video-background-unmute, video-background-volume-change, video-background-resize, video-background-destroyed.

document.querySelector("#hero").addEventListener("video-background-ready", (event) => {
  console.log(event.detail.type, event.detail.currentState);
});

A group dispatches on the group element, with the group in event.detail:

video-background-group-play, video-background-group-pause, video-background-group-mute, video-background-group-unmute, video-background-group-next, video-background-group-previous, video-background-group-forward-rewind, video-background-group-backward-rewind.

The last two fire when stepping past either end of the stack wraps around. Up to and including 1.2.0 they never fired, and the unmute event was dispatched under the misspelling video-background-group-umnute.

API

const backgrounds = new VideoBackgrounds("[data-vbg]");

const instance = backgrounds.get(document.querySelector("#hero"));
instance.play();
instance.pause();
instance.mute();
instance.unmute();
instance.setVolume(0.4);
instance.seek(50);          // percent
instance.seekTo(12);        // seconds
instance.setSource("https://vimeo.com/137250145");

backgrounds.pauseAll();
backgrounds.playAll();
backgrounds.add(element);
backgrounds.destroy(element);
backgrounds.destroyAll();