Important
This is the 1.x page, kept for the deprecated jQuery plugin and the old bundle
filenames. The current release is 2.x โ the same factory, the same options, events
and methods, no jQuery, and the bundles renamed and moved into dist/. Its page is
one level up, and the 1.x README is on GitHub at
v1.2.1.
๐บ Youtube Background v1.x
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@1
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="jquery.youtube-background.min.js"></script>
Warning
There is also a jQuery plugin, jQuery("[data-vbg]").youtube_background(), which
registers itself only if jQuery is on the page. It is deprecated as of 1.2.0, warns
once when called, and is removed in 2.0.0.
The seek bars, play/mute toggles and group controls used above are optional and live in
a bundle of their own. Each is a class over markup you write, pointed at a background by
data-target, and each has a destroy() that takes its listeners back:
<script src="youtube-background-experimental.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 |
0โ1, 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();