For this html:
<html>
<div id="vids">
<video id="vid-1" src="vid1.mp4">
</video>
<video id="vid-2" src="vid2.mp4">
</video>
</div>
</html>
The videos show up correctly.
I want to add an overlay text (caption) on top of each video like:
"Video 1 - Greenery"
"Video 2 - Island"
How can I do this using Javascript alone without modifying the HTML?
If not overlay, how do I display caption right above or below each video.
I have tried this:
var vid = document.getElementById("vid-1");
vid.insertAdjacentHTML("aftereend","Video 1 - Greenery");
But the text "Video 1 - Greenery", appears on right side of video. If it appeared directly above or below video that would work too.
TL;DR:
<figcaption>
(and <figure>
).position: absolute
.We can map video IDs to captions. Then for all entries, we can create and insert the captions before their corresponding videos:
const idToCaption = new Map();
idToCaption.set("vid-1", "Video 1 - Greenery");
idToCaption.set("vid-2", "Video 2 - Island");
for (const [id, caption] of idToCaption.entries()) {
const p = document.createElement("p");
p.textContent = caption;
const video = document.getElementById(id);
// Assign caption as label
p.id = `${id}-label`;
video.setAttribute("aria-labelledby", p.id);
video.parentElement.insertBefore(p, video);
}
<div id="vids">
<video id="vid-1" src="vid1.mp4"></video>
<video id="vid-2" src="vid2.mp4"></video>
</div>
Note:
aria-labelledby
. (Captions labelling content is done implicitly when using <figcaption>
as below.)Without styling, the captions will come before their videos. To place them above their videos we need styling (CSS).
Placing elements above others can be achieved by taking them out of the flow, e.g. with position: absolute
.
We want to position the captions relative to the videos. But the captions are siblings and not children of the videos, which makes relative positioning more difficult.
We can wrap each caption and its video in another element, which will effectively represent the video. That way, the caption is a child of the "video" (the wrapper), which makes positioning it correctly easier:
const idToCaption = new Map();
idToCaption.set("vid-1", "Video 1 - Greenery");
idToCaption.set("vid-2", "Video 2 - Island");
for (const [id, caption] of idToCaption.entries()) {
const p = document.createElement("p");
p.textContent = caption;
const video = document.getElementById(id);
p.id = `${id}-label`;
video.setAttribute("aria-labelledby", p.id);
// New code starts here
const wrapper = document.createElement("span");
wrapper.classList.add("wrapper");
p.classList.add("caption");
video.replaceWith(wrapper);
wrapper.append(p, video);
}
.caption {
position: absolute;
/*Example: Position at top-left*/
top: 0;
left: 0;
}
.wrapper {
/* `position:absolute` elements are relative to
* first non-`static` ancestor.
* Make wrapper non-`static`. */
position: relative;
/* `display:inline-block` makes
* `.wrapper` behave like <video>,
* but contain it (here: inherit its size). */
display: inline-block;
}
/*Ignore; presentational*/
.caption {
margin: 0;
color: white;
background-color: black;
}
video {border: 1px solid black}
<div id="vids">
<video id="vid-1" src="vid1.mp4"></video>
<video id="vid-2" src="vid2.mp4"></video>
</div>
There already exist elements for captions and the wrapper: <figcaption>
and <figure>
. You should prefer these where applicable, e.g. here:
const idToCaption = new Map();
idToCaption.set("vid-1", "Video 1 - Greenery");
idToCaption.set("vid-2", "Video 2 - Island");
for (const [id, caption] of idToCaption.entries()) {
const figcaption = document.createElement("figcaption");
figcaption.textContent = caption;
const video = document.getElementById(id);
/* Notice that <figcaption> automatically applies to its
* <figure>'s other content. No "aria-labelledby" required anymore! */
const figure = document.createElement("figure");
video.replaceWith(figure);
figure.append(figcaption, video);
}
figcaption {
position: absolute;
top: 0;
left: 0;
}
figure {position: relative}
/*Ignore; presentational*/
figcaption {
margin: 0;
color: white;
background-color: black;
}
video {border: 1px solid black}
<div id="vids">
<video id="vid-1" src="vid1.mp4"></video>
<video id="vid-2" src="vid2.mp4"></video>
</div>
Note: The <figure>
element is display: block
whereas <video>
is display: inline
by default. The stylesheet should fix this inconsistency.