// ==UserScript==
// @name Youtube Fullscreen Mode
// @name:ko 유튜브 풀스크린
// @description -
// @description:ko -
// @version 2025.09.19
// @author ndaesik
// @icon https://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Youtube_shorts_icon.svg/193px-Youtube_shorts_icon.svg.png
// @match *://*.youtube.com/*
// @grant none
// @namespace https://ndaesik.tistory.com/
// ==/UserScript==
(() => {
'use strict';
if (window.top !== window.self) return;
const scrollbarHideCSS = document.createElement('style');
scrollbarHideCSS.dataset.tm = 'yt-fullscreen-suggest';
scrollbarHideCSS.textContent = `
body{overflow-y:auto;}
`.replaceAll(';','!important;');
const fullscreenVideoCSS = document.createElement('style');
fullscreenVideoCSS.dataset.tm = 'yt-fullscreen-video';
fullscreenVideoCSS.textContent = `
ytd-app:not([guide-persistent-and-visible]) [theater] #player video,
:is(ytd-watch-flexy[theater],ytd-watch-flexy[fullscreen]) #full-bleed-container {
height:100vh;max-height:100vh;min-height:100vh;
}
ytd-watch-flexy[theater]{scrollbar-width:none;}
ytd-watch-flexy[theater]::-webkit-scrollbar{display:none;}
ytd-watch-flexy[theater] ~ body{scrollbar-width:none;-ms-overflow-style:none;}
ytd-watch-flexy[theater] ~ body::-webkit-scrollbar{display:none;}
`.replaceAll(';','!important;');
const autoHideTopCSS = document.createElement('style');
autoHideTopCSS.dataset.tm = 'yt-fullscreen-autohide';
autoHideTopCSS.className = 'autoHideTopCSS';
autoHideTopCSS.textContent = `
#masthead-container.ytd-app:hover,#masthead-container.ytd-app:focus-within{width:100%;}
#masthead-container.ytd-app,
#masthead-container.ytd-app:not(:hover):not(:focus-within){width:calc(50% - 150px);}
#masthead-container.ytd-app:not(:hover):not(:focus-within){transition:width .4s ease-out .4s;}
ytd-app:not([guide-persistent-and-visible]) :is(#masthead-container ytd-masthead, #masthead-container.ytd-app::after){
transform:translateY(-56px);transition:transform .1s .3s ease-out;
}
ytd-app:not([guide-persistent-and-visible]) :is(#masthead-container:hover ytd-masthead, #masthead-container:hover.ytd-app::after, #masthead-container:focus-within ytd-masthead){
transform:translateY(0);
}
ytd-app:not([guide-persistent-and-visible]) ytd-page-manager{margin-top:0;}
`.replaceAll(';','!important;');
const $ = {
els: { ytdApp: null, player: null, chatFrame: null },
update() {
this.els.ytdApp = document.querySelector('ytd-app');
this.els.player = document.querySelector('#ytd-player');
this.els.chatFrame = document.querySelector('ytd-live-chat-frame');
}
};
let scrollTimer = null, isContentHidden = false;
let observerForTheater = null;
let listenersAttached = false;
let listenerAbort = null;
const isWatchPage = () => location.pathname === '/watch';
const inTheater = () => {
$.update();
const { ytdApp, player, chatFrame } = $.els;
return ytdApp && player && isWatchPage() &&
(window.innerWidth - ytdApp.offsetWidth + player.offsetWidth +
(chatFrame && !chatFrame.attributes.collapsed ? chatFrame.offsetWidth : 0)) === window.innerWidth;
};
const rafUntil = (pred, timeout = 5000) => new Promise(resolve => {
const t0 = performance.now();
const tick = () => {
if (pred()) return resolve(true);
if (performance.now() - t0 > timeout) return resolve(false);
requestAnimationFrame(tick);
};
tick();
});
const waitForStableLayout = async (timeout = 1500) => {
const t0 = performance.now();
let lastH = 0, stableCount = 0;
const okProgress = () => {
const p = document.querySelector('yt-page-navigation-progress');
if (!p) return true;
if (p.hasAttribute('hidden')) return true;
const s = getComputedStyle(p);
return s.display === 'none' || s.visibility === 'hidden' || s.opacity === '0';
};
const okPlayer = () => {
const v = document.querySelector('#ytd-player video, #movie_player video, ytd-player video');
return !!(v && v.readyState >= 1);
};
const okWatch = () => {
const flexy = document.querySelector('ytd-watch-flexy');
return !!flexy && (flexy.hasAttribute('theater') || flexy.hasAttribute('fullscreen'));
};
while (performance.now() - t0 < timeout) {
const h = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight);
stableCount = (h === lastH) ? (stableCount + 1) : 0;
lastH = h;
if (stableCount >= 2 && okProgress() && okPlayer() && okWatch()) return true;
await new Promise(r => requestAnimationFrame(r));
}
return false;
};
const forceScrollTop = async () => {
const html = document.documentElement;
const body = document.body;
const prevBehavior = html.style.scrollBehavior;
const prevAnchorHtml = html.style.overflowAnchor;
const prevAnchorBody = body.style.overflowAnchor;
html.style.scrollBehavior = 'auto';
html.style.overflowAnchor = 'none';
body.style.overflowAnchor = 'none';
const playerRoot = document.querySelector('#full-bleed-container, #ytd-player, ytd-player');
if (playerRoot && typeof playerRoot.scrollIntoView === 'function') {
try { playerRoot.scrollIntoView({ block: 'start', inline: 'nearest' }); } catch(_) {}
}
const attempts = [0, 0, 16, 33, 66, 100, 150, 200, 250, 300, 400];
for (const ms of attempts) {
if (ms) await new Promise(r => setTimeout(r, ms));
window.scrollTo(0, 0);
void document.body.offsetHeight;
if (window.scrollY === 0) break;
}
html.style.scrollBehavior = prevBehavior || '';
html.style.overflowAnchor = prevAnchorHtml || '';
body.style.overflowAnchor = prevAnchorBody || '';
};
const resetScrollTopSmart = async () => {
try { if ('scrollRestoration' in history) history.scrollRestoration = 'manual'; } catch(_) {}
window.scrollTo(0,0);
await new Promise(r => requestAnimationFrame(() => { window.scrollTo(0,0); r(); }));
await new Promise(r => requestAnimationFrame(() => { window.scrollTo(0,0); r(); }));
await waitForStableLayout(1500);
await forceScrollTop();
};
let autoHideOn = false;
const shouldShowAutoHideCSS = () => {
const scrollPosition = window.scrollY;
const viewportHeight = window.innerHeight + 56;
return isWatchPage() && inTheater() && scrollPosition <= viewportHeight;
};
const updateAutoHideCSS = () => {
const need = shouldShowAutoHideCSS();
if (need === autoHideOn) return;
autoHideOn = need;
if (need) {
if (!document.head.querySelector('style[data-tm="yt-fullscreen-autohide"]')) {
document.head.appendChild(autoHideTopCSS);
}
} else {
autoHideTopCSS.remove();
}
};
const checkConditions = () => {
if (!isWatchPage()) return;
const watchFlexy = document.querySelector('ytd-watch-flexy');
const primaryContent = document.querySelector('#primary');
const secondaryContent = document.querySelector('#secondary');
const isTheater = watchFlexy?.hasAttribute('theater');
const isScrollTop = window.scrollY === 0;
if (!primaryContent || !secondaryContent || !isTheater) return;
if (isScrollTop && !isContentHidden) {
if (scrollTimer) clearTimeout(scrollTimer);
scrollTimer = setTimeout(() => {
primaryContent.style.display = 'none';
secondaryContent.style.display = 'none';
isContentHidden = true;
}, 2000);
} else if (!isScrollTop && scrollTimer) {
clearTimeout(scrollTimer);
scrollTimer = null;
if (isContentHidden) {
primaryContent.style.display = '';
secondaryContent.style.display = '';
isContentHidden = false;
}
}
};
const showContent = () => {
const primaryContent = document.querySelector('#primary');
const secondaryContent = document.querySelector('#secondary');
if (isContentHidden && primaryContent && secondaryContent) {
primaryContent.style.display = '';
secondaryContent.style.display = '';
isContentHidden = false;
if (scrollTimer) { clearTimeout(scrollTimer); scrollTimer = null; }
setTimeout(checkConditions, 1000);
}
};
const attachCSS = () => {
if (!document.head.querySelector('style[data-tm="yt-fullscreen-suggest"]')) {
document.head.appendChild(scrollbarHideCSS);
}
if (!document.head.querySelector('style[data-tm="yt-fullscreen-video"]')) {
document.head.appendChild(fullscreenVideoCSS);
}
updateAutoHideCSS();
};
const detachCSS = () => {
scrollbarHideCSS.remove();
fullscreenVideoCSS.remove();
autoHideTopCSS.remove();
observerForTheater?.disconnect();
observerForTheater = null;
if (listenerAbort) { listenerAbort.abort(); listenerAbort = null; }
listenersAttached = false;
if (isContentHidden) {
const primaryContent = document.querySelector('#primary');
const secondaryContent = document.querySelector('#secondary');
if (primaryContent) primaryContent.style.display = '';
if (secondaryContent) secondaryContent.style.display = '';
isContentHidden = false;
}
if (scrollTimer) { clearTimeout(scrollTimer); scrollTimer = null; }
autoHideOn = false;
};
const setupEventListeners = () => {
if (listenersAttached) return;
listenersAttached = true;
listenerAbort = new AbortController();
const sig = listenerAbort.signal;
window.addEventListener('scroll', () => requestAnimationFrame(() => {
updateAutoHideCSS();
checkConditions();
}), { passive: true, signal: sig });
window.addEventListener('click', () => {
setTimeout(updateAutoHideCSS, 100);
requestAnimationFrame(showContent);
}, { passive: true, signal: sig });
window.addEventListener('wheel', () => requestAnimationFrame(showContent), { passive: true, signal: sig });
const watchFlexyInit = () => {
const watchFlexy = document.querySelector('ytd-watch-flexy');
if (!watchFlexy) return;
observerForTheater?.disconnect();
observerForTheater = new MutationObserver(() => requestAnimationFrame(checkConditions));
observerForTheater.observe(watchFlexy, { attributes: true, attributeFilter: ['theater'] });
};
new MutationObserver(watchFlexyInit).observe(document.documentElement, { childList: true, subtree: true });
watchFlexyInit();
window.addEventListener('yt-navigate-finish', () => { resetScrollTopSmart(); }, { passive: true, signal: sig });
window.addEventListener('yt-navigate-cache-restored', () => { resetScrollTopSmart(); }, { passive: true, signal: sig });
window.addEventListener('yt-page-data-updated', () => { resetScrollTopSmart(); }, { passive: true, signal: sig });
window.addEventListener('ytd-player-updated', () => { resetScrollTopSmart(); }, { passive: true, signal: sig });
};
const URL_EVENT = 'tm-url-change';
const fireUrlEvent = () => window.dispatchEvent(new Event(URL_EVENT));
const _pushState = history.pushState;
const _replaceState = history.replaceState;
history.pushState = function(...args) { const r = _pushState.apply(this, args); fireUrlEvent(); return r; };
history.replaceState = function(...args) { const r = _replaceState.apply(this, args); fireUrlEvent(); return r; };
window.addEventListener('popstate', fireUrlEvent);
window.addEventListener('hashchange', fireUrlEvent);
window.addEventListener('yt-navigate-finish', fireUrlEvent);
window.addEventListener('yt-navigate-start', fireUrlEvent);
let lastPath = location.pathname + location.search;
const onRoute = () => {
const now = location.pathname + location.search;
if (now === lastPath) return;
lastPath = now;
if (isWatchPage()) {
attachCSS();
setupEventListeners();
resetScrollTopSmart();
requestAnimationFrame(() => {
updateAutoHideCSS();
checkConditions();
});
setTimeout(() => { if (window.scrollY !== 0) resetScrollTopSmart(); }, 500);
} else {
detachCSS();
}
};
const boot = () => {
if (isWatchPage()) {
attachCSS();
setupEventListeners();
resetScrollTopSmart();
requestAnimationFrame(() => {
updateAutoHideCSS();
checkConditions();
});
setTimeout(() => { if (window.scrollY !== 0) resetScrollTopSmart(); }, 500);
} else {
detachCSS();
}
};
window.addEventListener(URL_EVENT, onRoute);
boot();
})();