// ==UserScript== // @name Youtube Fullscreen Mode // @name:ko 유튜브 풀스크린 // @description - // @description:ko - // @version 2025.09.18 // @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/ // @downloadURL none // ==/UserScript== (() => { 'use strict'; if (window.top !== window.self) return; const suggestBoxToDarkCSS = document.createElement('style'); suggestBoxToDarkCSS.dataset.tm = 'yt-fullscreen-suggest'; suggestBoxToDarkCSS.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 isProgressHidden = () => { 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 isPlayerReady = () => { const v = document.querySelector('#ytd-player video, #movie_player video, ytd-player video'); return !!(v && v.readyState >= 1); }; 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 rafUntil(() => (document.readyState === 'complete' || isProgressHidden()) && isPlayerReady()); window.scrollTo(0,0); }; 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(suggestBoxToDarkCSS); } if (!document.head.querySelector('style[data-tm="yt-fullscreen-video"]')) { document.head.appendChild(fullscreenVideoCSS); } updateAutoHideCSS(); }; const detachCSS = () => { suggestBoxToDarkCSS.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 }); }; 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(); }); } else { detachCSS(); } }; const boot = () => { if (isWatchPage()) { attachCSS(); setupEventListeners(); resetScrollTopSmart(); requestAnimationFrame(() => { updateAutoHideCSS(); checkConditions(); }); } else { detachCSS(); } }; window.addEventListener(URL_EVENT, onRoute); boot(); })();