Moves and expands YouTube description on the right, making it scrollable and dynamically adjusting height to video.
目前為
// ==UserScript==
// @name Youtube Scrollable Right Side Description
// @description Moves and expands YouTube description on the right, making it scrollable and dynamically adjusting height to video.
// @version 3.0
// @author SH3LL
// @license MIT
// @match *://*.youtube.com/*
// @grant none
// @run-at document-end
// @namespace https://greasyfork.org/users/762057
// ==/UserScript==
(function() {
'use strict';
let descriptionExpander;
const moveEl = (parent, child) => parent?.prepend(child);
const setElStyle = (el, styles) => el?.setAttribute('style', styles);
const qSel = sel => document.querySelector(sel);
const getElById = id => document.getElementById(id);
const ownerObserver = new MutationObserver((mutationsList, observer) => {
if (getElById('owner')) {
observer.disconnect();
console.log('"owner" found, executing.');
executeScript();
}
});
ownerObserver.observe(document.body, { childList: true, subtree: true });
const updateDescHeight = height => {
descriptionExpander && (descriptionExpander.style.maxHeight = `${height - 70}px`);
};
const videoHeightObserver = new MutationObserver(mutations => {
for (const m of mutations) {
if (m.type === 'attributes' && m.attributeName === 'style') {
const heightMatch = m.target.style.height.match(/^(\d+)px$/);
heightMatch && updateDescHeight(parseInt(heightMatch[1], 10));
}
}
});
const observeVideoHeight = () => {
const videoContent = qSel('.ytp-iv-video-content');
if (videoContent) {
videoHeightObserver.observe(videoContent, { attributes: true, attributeFilter: ['style'] });
const initialHeight = videoContent.style.height.match(/^(\d+)px$/);
initialHeight && updateDescHeight(parseInt(initialHeight[1], 10));
} else {
console.warn('No .ytp-iv-video-content found.');
}
};
function executeScript() {
const related = qSel('#related');
const bottomRow = getElById('bottom-row');
const owner = getElById('owner');
const below = qSel('#below');
const infoContainer = getElById('info-container');
descriptionExpander = getElById('description-inline-expander');
const description = getElById('description');
const descriptionInner = getElById('description-inner');
const descStyle = `margin-left: 0; overflow: auto; max-width: 100%; font-size: 1.3rem; line-height: normal; width: auto; padding: 8px; border-bottom-width: 0; --yt-endpoint-text-decoration: underline; background-color: var(--yt-playlist-background-item)`;
moveEl(related, bottomRow);
owner && (moveEl(related, owner), setElStyle(owner, 'margin:0'));
moveEl(below, infoContainer);
setElStyle(infoContainer, 'color:white; font-size: 12px');
if (descriptionExpander && description && descriptionInner) {
setElStyle(descriptionExpander, descStyle);
setElStyle(description, 'margin: 0');
setElStyle(descriptionInner, 'margin: 0');
descriptionExpander.setAttribute('is-expanded', '');
observeVideoHeight();
}
const removalObserver = new MutationObserver(mutations => {
const targets = ['expand', 'collapse', 'ytd-watch-info-text', 'snippet'];
for (const m of mutations) {
if (m.type === 'childList') {
targets.forEach(id => getElById(id)?.remove());
!targets.some(getElById) && (observer.disconnect(), console.log('Removal observer done.'));
}
}
});
const watchInfoFlex = qSel('ytd-watch-metadata > div');
watchInfoFlex && removalObserver.observe(watchInfoFlex, { childList: true, subtree: true });
}
})();