Remember I threw this together in about half an hour, and maybe that amount of cleanup to post here. "Works for me" is the order of the day. The extra debugging alone would be longer than the whole project!
Besides, the function that runs is so light it doesn't really seem worth it. It could even make performance worse, since for reliability you need to observe mutations across the entire DOM, which could occur a lot more often. So for performance you want to add some debouncing too, adding yet more complexity to what's supposed to be a 'quick and dirty' fix.
(function() { 'use strict';
const SITE = "https://www.youtube.com";
const LINK_TO_TIMESTAMP = true;
const SHOW_PREVIEW_IMAGE = true;
const createPreviewElement = (videoId, newURL, frame) => {
const container = document.createElement("div");
container.setAttribute('data-yt-preview', 'true');
container.style.position = "relative";
container.style.width = frame.width + "px";
container.style.height = frame.height + "px";
container.style.cursor = "pointer";
const img = document.createElement("img");
img.src = `https://i.ytimg.com/vi/${videoId}/mqdefault.jpg`;
img.alt = "Preview image of Youtube video";
img.style.width = "100%";
img.style.height = "100%";
img.style.objectFit = "cover";
const playButton = document.createElement("div");
playButton.innerHTML = "▶";
playButton.style.position = "absolute";
playButton.style.top = "50%";
playButton.style.left = "50%";
playButton.style.transform = "translate(-50%, -50%)";
playButton.style.fontSize = "48px";
playButton.style.color = "white";
playButton.style.backgroundColor = "rgba(0, 0, 0, 0.7)";
playButton.style.borderRadius = "50%";
playButton.style.width = "80px";
playButton.style.height = "80px";
playButton.style.display = "flex";
playButton.style.justifyContent = "center";
playButton.style.alignItems = "center";
container.appendChild(img);
container.appendChild(playButton);
container.addEventListener("click", () => {
const iframe = document.createElement("iframe");
iframe.src = frame.src + (frame.src.includes('?') ? '&' : '?') + 'autoplay=1';
iframe.width = frame.width;
iframe.height = frame.height;
iframe.frameBorder = "0";
iframe.allow = "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture";
iframe.allowFullscreen = true;
iframe.setAttribute('data-yt-processed', 'true');
container.parentNode.replaceChild(iframe, container);
});
return container;
};
const replaceEmbed = (frame) => {
const frameURL = frame.src || frame.dataset?.src;
if (!frameURL) return;
const match = frameURL.match(/(^https?:)?\/\/(www\.)?youtube(-nocookie)?\.com\/embed\/([a-zA-Z0-9\-_]{11}).*?(\?.*((t|start)=([\dsmh]*)))?/i);
if (match?.length == 9) {
const videoId = match[4];
const newURL = SITE + "/watch?" + ((LINK_TO_TIMESTAMP && match[8]) ? "t=" + match[8] + "&" : "") + "v=" + videoId;
const previewElement = createPreviewElement(videoId, newURL, frame);
frame.parentNode.replaceChild(previewElement, frame);
// common lazyload hiding methods
previewElement.style.display = "block !important";
previewElement.style.opacity = "100% !important";
previewElement.style.background = "transparent !important";
const parent = previewElement.parentElement;
if (parent) {
parent.style.display = "block !important";
parent.style.opacity = "100% !important";
parent.style.background = "transparent !important";
}
}
};
const observeDOM = () => {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.tagName === 'IFRAME' && !node.hasAttribute('data-yt-processed')) {
replaceEmbed(node);
} else {
node.querySelectorAll('iframe:not([data-yt-processed])').forEach(replaceEmbed);
}
}
});
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
};
// Initial replacement
document.querySelectorAll('iframe:not([data-yt-processed])').forEach(replaceEmbed);
// Start observing DOM changes
observeDOM();
})();Edit: On line 21 & 22, I changed the container width to grab `frame.parentNode.width` instead and did the same for height. Seems to work better for that site at least.