// ==UserScript== // @name Wider Bilibili // @namespace https://greasyfork.org/users/1125570 // @version 0.4.0 // @author posthumz // @description 哔哩哔哩宽屏体验 // @license MIT // @icon https://www.bilibili.com/favicon.ico // @match http*://*.bilibili.com/* // @exclude http*://www.bilibili.com/correspond/* // @exclude http*://message.bilibili.com/pages/nav/header_sync // @grant GM_addStyle // @grant GM_addValueChangeListener // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_setValue // @run-at document-start // @downloadURL none // ==/UserScript== (async function () { 'use strict'; const styles = { video: `/* 播放器 */ :root { --navbar-height: 64px; --video-height: 100vh; } div#playerWrap, div#bilibili-player-wrap { position: absolute; left: 0; right: 0; top: 0; height: var(--video-height); /* 番剧页加载时会有右填充 */ padding-right: 0; } div#bilibili-player { width: 100%; height: 100%; box-shadow: none !important; } /* 导航栏 */ #biliMainHeader { margin-top: var(--video-height); margin-bottom: 0; position: sticky; top: 0; z-index: 3; } .custom-navbar { position: sticky !important; z-index: 3 !important; } .bili-header.fixed-header { min-height: initial !important; } .bili-header__bar { position: relative !important; } /* 使用 static 才能让播放器的 absolute 正确定位 */ /* 视频、番剧、收藏/稍后再看页 */ .video-container-v1, .left-container, .main-container, .playlist-container--left { position: static !important; } /* 加载动画强制显示 */ .bpx-player-loading-panel-blur { display: flex !important; } /* 强制显示播放器控件 */ .bpx-player-top-left-title, .bpx-player-top-left-music, .bpx-player-top-mask { display: block !important; } /* 原弹幕发送区域不显示 */ .bpx-player-sending-area { display: none; } /* 原宽屏/网页全屏按钮不显示 */ .bpx-player-ctrl-wide, .bpx-player-ctrl-web { display: none; } /* 视频页、番剧页、收藏/稍后再看页的下方容器 */ .video-container-v1, .main-container, .playlist-container { z-index: 0; margin-top: var(--video-height); padding: 0 var(--layout-padding); } .left-container, .plp-l, .playlist-container--left { flex: 1; } .plp-r { position: sticky !important; /* 番剧页加载时不会先使用sticky */ } /* 番剧/影视页下方容器 */ .main-container { width: 100%; margin: 0; padding-top: 15px; padding-left: var(--layout-padding); padding-right: var(--layout-padding); box-sizing: border-box; display: flex; } .player-left-components { padding-right: 30px !important; } .toolbar { padding-top: 0; } /* 视频标题换行显示 */ #viewbox_report { height: auto; } .video-title { white-space: normal !important; } /* bgm浮窗 */ #bgm-entry { z-index: 114514 !important; left: 0 !important; } /* 笔记浮窗 */ .note-pc { z-index: 114514 !important; } /* 番剧页右下方浮动按钮修正 */ div[class^=navTools_floatNav] { z-index: 2 !important; } /* Bilibili Evolved侧栏 */ .be-settings .sidebar { z-index: 114514 !important; } /* Bilibili Evolved 夜间模式修正 */ .bpx-player-container .bpx-player-sending-bar { background-color: transparent !important; } .bpx-player-container .bpx-player-video-info { color: hsla(0, 0%, 100%, .9) !important; } .bpx-player-container .bpx-player-sending-bar .bpx-player-video-btn-dm, .bpx-player-container .bpx-player-sending-bar .bpx-player-dm-setting, .bpx-player-container .bpx-player-sending-bar .bpx-player-dm-switch { fill: hsla(0, 0%, 100%, .9) !important; }`, t: `/* 动态页 */ #app .bg+.content { box-sizing: border-box; border-width: 10px var(--layout-padding); border-style: solid; border-color: transparent; width: initial !important; .sidebar-wrap { right: 58px; } } .bili-dyn-home--member { margin: 0 var(--layout-padding) !important; main { flex: 1 } .left { display: none; } }`, space: `/* 空间页 */ .wrapper, .search-page { width: initial !important; margin: 0 var(--layout-padding) !important; } /* 视频卡片 */ .small-item { padding-left: 10px !important; padding-right: 10px !important; } /* 主页, 动态 */ #page-index, #page-dynamic { display: flex; justify-content: space-between; gap: 10px; &::before, &::after { content: none; } .col-1 { flex: 1; >.video>.content { display: flex; flex-wrap: wrap; } .section.coin>.content { display: flex; flex-wrap: wrap; margin: 0; width: initial !important; } } .channel>.content { width: initial !important; .channel-video { overflow-x: auto; } } .fav-item { margin-right: 20px !important; } } /* 投稿, 搜索 */ #page-video .col-full { display: flex; >.main-content { flex: 1; .cube-list { width: initial !important; display: flex; flex-wrap: wrap; justify-content: center; } } } /* 合集 */ .channel-index { width: 100% !important; } .feed-dynamic { flex: 1; }`, search: `/* 搜索页 */ .i_wrapper { padding: 0 var(--layout-padding); }`, read: `/* 阅读页 */ #app>.article-detail { box-sizing: border-box; border-width: 0 var(--layout-padding); border-style: solid; border-color: transparent; width: 100%; .article-up-info { width: initial; margin: 0 80px 20px; } .right-side-bar { right: 0; } }`, home: `/* 首页 */ .feed-card, .floor-single-card, .bili-video-card { margin-top: 0px !important; } .feed-roll-btn { /* left: initial !important; right: calc(30px - var(--layout-padding)); */ /* 右下角已有刷新按键,何必呢 */ display: none; } .palette-button-wrap { left: initial !important; right: 30px; }`, common: `/* This overrides :root style */ body { --layout-padding: 30px; } html, body { width: initial !important; height: initial !important; } /* 搜索栏 */ .center-search-container { min-width: 0; } .nav-search-input { width: 0 !important; padding-right: 0 !important; }`, upperNavigation: `/* 导航栏上置 (默认下置) */ :root { --video-height: calc(100vh - var(--navbar-height)); } #biliMainHeader { margin-top: 0; margin-bottom: var(--video-height); } #playerWrap, #bilibili-player-wrap { top: var(--navbar-height); height: var(--video-height) !important; }`, pauseShow: `/* 暂停显示控件 */ .bpx-state-paused { .bpx-player-top-wrap, .bpx-player-control-top, .bpx-player-control-bottom, .bpx-player-control-mask { opacity: 1 !important; visibility: visible !important; } .bpx-player-shadow-progress-area { visibility: hidden !important; } .bpx-player-pbp { bottom: 100% !important; margin-bottom: 5px; opacity: 1 !important; left: 0; right: 0; width: initial !important; .bpx-player-pbp-pin { opacity: 1 !important; } } }`, options: `/* 脚本选项 */ #wider-bilibili { position: fixed; top: 0; bottom: 0; height: fit-content; max-height: 80vh; left: 0; right: 0; width: fit-content; max-width: 80vw; z-index: 114514; padding: 10px; border-radius: 10px; margin: auto; box-sizing: border-box; overflow: auto; flex-direction: column; gap: 10px; outline: 2px solid var(--v_brand_blue); outline-offset: 0; background-color: var(--bg1); color: var(--text1); font-size: 20px; font-family: "HarmonyOS_Regular", "PingFang SC", "Helvetica Neue", "Microsoft YaHei", sans-serif !important; opacity: 0.9; &:hover { opacity: 1 } >header { position: sticky; z-index: 2; top: -10px; display: flex; justify-content: space-between; margin: -10px; text-overflow: clip; padding-left: 20px; font-weight: bold; background-color: var(--bg1); &::before { content: "Wider Bilibili 选项"; align-self: center; flex: 1; } } .wb-button-group { display: flex; margin-bottom: 10px; >* { height: 100%; } } #wb-close { box-sizing: border-box; padding: 4px; width: fit-content; background-color: var(--v_stress_red); fill: var(--text1); &:hover { background-color: var(--v_stress_red_hover); } &:active { background-color: var(--v_stress_red_active); } } button { border: none; padding: 4px 8px; background: none; color: var(--text_white); transition: opacity .1s; background-color: var(--v_brand_blue); font-size: 16px; text-wrap: nowrap; &:hover { background-color: var(--v_brand_blue_hover); } &:active { background-color: var(--v_brand_blue_active); } >a { color: inherit; text-decoration: none; } } >fieldset { border: none; border-radius: 10px; padding: 10px; margin: 0; display: grid; grid-template-columns: 1fr 1fr; gap: 10px 15px; background-color: rgba(127, 127, 127, 0.1); &::before { content: attr(data-title); border-radius: 4px 4px 0 0; border-bottom: 2px solid rgba(127, 127, 127, 0.1); grid-column: 1 / -1; } } label { display: inline-flex; gap: 10px; place-items: center; position: relative; text-wrap: nowrap; &[data-hint]:hover::before { position: absolute; bottom: 110%; left: 0; right: 0; margin: 0 auto; width: fit-content; padding: 3px 5px; border-radius: 5px; content: attr(data-hint); font-size: 12px; background-color: var(--v_brand_blue); white-space: pre-line; } &::after { content: attr(data-key); } } input { box-sizing: content-box; margin: 0; padding: 4px; height: 20px; font-size: 16px; transition: .2s; &:hover { box-shadow: 0 0 8px var(--v_brand_blue); } &[type=checkbox] { box-sizing: content-box; border-radius: 20px; min-width: 40px; background-color: #ccc; appearance: none; cursor: pointer; &::before { content: ""; position: relative; display: block; transition: 0.3s; height: 100%; aspect-ratio: 1/1; border-radius: 50%; background-color: #FFF; } &:checked { background-color: var(--v_brand_blue); } &:checked::before { transform: translateX(20px); } &:active { opacity: 0.5; } } &[type=number] { width: 40px; border: none; border-radius: 5px; outline: 2px solid var(--v_brand_blue); background: none; color: var(--text1); appearance: textfield; &::-webkit-inner-spin-button { appearance: none; } } } }`, mini: `/* 小窗 */ .bpx-player-container[data-screen="mini"] { /* 以视频长宽比为准,不显示黑边和阴影 */ height: auto !important; box-shadow: none; /* 修正小窗位置 */ translate: 32px 40px; } .bpx-player-container { &[data-screen="mini"] { width: var(--mini-width) !important; } /* 最小宽度,以防不可见 */ min-width: 180px; } .bpx-player-mini-resizer { position: absolute; left: 0; width: 10px; height: 100%; cursor: ew-resize; }`, controls: `/* 播放器控件 */ .bpx-player-control-bottom { padding: 0 24px; } .bpx-player-control-bottom-left, .bpx-player-control-bottom-right, .bpx-player-sending-bar, .be-video-control-bar-extend { gap: 10px; } .bpx-player-ctrl-btn { width: auto !important; margin: 0 !important; } .bpx-player-ctrl-time-seek { width: 100% !important; padding: 0 !important; left: 0 !important; } .bpx-player-control-bottom-left { min-width: initial !important; } .bpx-player-control-bottom-center { padding: 0 20px !important; } .bpx-player-control-bottom-right { min-width: initial !important; >div { padding: 0 !important; } } .bpx-player-ctrl-time-label { text-align: center !important; text-indent: 0 !important; } .bpx-player-video-inputbar { min-width: initial !important; }` }; function waitFor(loaded, desc = "页面加载", retry = 100, interval = 100) { return new Promise((resolve, reject) => { const intervalID = setInterval((res = loaded()) => { if (res) { clearInterval(intervalID); console.info(`${desc}已加载`); return resolve(res); } if (--retry === 0) { console.error("页面加载超时"); clearInterval(intervalID); return reject(new Error("timeout")); } if (retry % 10 === 0) { console.debug(`等待${desc}`); } }, interval); }); } function observeFor(className, parent) { return new Promise((resolve) => { const elem = parent.getElementsByClassName(className)[0]; if (elem) { return resolve(elem); } new MutationObserver((mutations, observer) => { for (const mutation of mutations) { for (const node of mutation.addedNodes) { if (node instanceof Element && node.classList.contains(className)) { observer.disconnect(); return resolve(node); } } } }).observe(parent, { childList: true }); }); } function waitReady() { return new Promise((resolve) => { document.readyState === "loading" ? window.addEventListener("DOMContentLoaded", () => resolve(), { once: true }) : resolve(); }); } const html = ``; GM_addStyle(styles.options); function toggleStyle(s, init = true, flip = false) { if (flip) { init = !init; } const style = GM_addStyle(s); if (!init) { style.disabled = true; } return flip ? (enable) => { style.disabled = enable; } : (enable) => { style.disabled = !enable; }; } function onStyleValueChange(toggle) { return (_k, _o, val) => toggle(val); } const options = { 左右边距: { page: "common", default: 30, callback: (init) => { document.body.style.setProperty("--layout-padding", `${init}px`); return (_k, _o, newVal) => document.body.style.setProperty("--layout-padding", `${newVal}px`); } }, 导航栏下置: { page: "video", default: true, callback: (init) => onStyleValueChange(toggleStyle(styles.upperNavigation, init, true)) }, 小窗样式: { page: "video", default: true, callback: (init) => { document.documentElement.style.setProperty("--mini-width", "320px"); const toggle1 = toggleStyle(styles.mini, init); const toggle2 = toggleStyle(".bpx-player-container { --mini-width: initial }", init, true); return onStyleValueChange((enable) => { toggle1(enable); toggle2(enable); }); } }, 控件样式: { page: "video", default: true, callback: (init) => onStyleValueChange(toggleStyle(styles.controls, init)) }, 暂停显示控件: { page: "video", default: false, callback: (init) => onStyleValueChange(toggleStyle(styles.pauseShow, init)) } }; function activate(targetPage) { for (const [name, { page, default: d, callback }] of Object.entries(options)) { page === targetPage && GM_addValueChangeListener(name, callback(GM_getValue(name, d))); } } waitReady().then(() => { document.body.insertAdjacentHTML("beforeend", html); const app = document.getElementById("wider-bilibili"); GM_registerMenuCommand("选项", () => { app.style.display = "flex"; }); document.getElementById("wb-close")?.addEventListener("click", () => { app.style.display = "none"; }); for (const input of app.getElementsByTagName("input")) { const key = input.parentElement?.dataset.key; if (!key) { continue; } const option = options[key]; switch (input.type) { case "checkbox": input.checked = GM_getValue(key, option.default); input.onchange = () => GM_setValue(key, input.checked); break; case "number": input.value = GM_getValue(key, option.default); input.oninput = () => { const val = Number(input.value); Number.isInteger(val) && GM_setValue(key, val); }; break; } } const modifiers = ["ctrlKey", "altKey", "shiftKey", "metaKey"]; const comb = [["altKey", "shiftKey"], "W"]; document.addEventListener("keyup", (ev) => { const { key } = ev; if (key === comb[1] && modifiers.every((mod) => comb[0].includes(mod) === ev[mod])) { app.style.display = app.style.display === "none" ? "flex" : "none"; } }); activate("common"); }); GM_addStyle(styles.common); const url = new URL(window.location.href); switch (url.host) { case "www.bilibili.com": { if (url.pathname === "/") { GM_addStyle(styles.home); console.info("使用首页宽屏样式"); break; } if (url.pathname.startsWith("/read")) { GM_addStyle(styles.read); console.info("使用阅读页宽屏样式"); break; } const style = GM_addStyle(styles.video); await( waitReady()); const player = document.getElementById("bilibili-player"); if (!player) { style.remove(); break; } activate("video"); observeFor("custom-navbar", document.body).then((nav) => document.getElementById("biliMainHeader").insertAdjacentElement("beforeend", nav)); const container = await( waitFor(() => player.getElementsByClassName("bpx-player-container")[0], "播放器内容器")); if (container.getAttribute("data-screen") !== "mini") { container.setAttribute("data-screen", "web"); } container.setAttribute = new Proxy(container.setAttribute, { apply: (target, thisArg, [name, val]) => target.apply(thisArg, [name, name === "data-screen" && val !== "mini" ? "web" : val]) }); const miniResizer = document.createElement("div"); miniResizer.className = "bpx-player-mini-resizer"; miniResizer.onmousedown = (ev) => { ev.stopImmediatePropagation(); ev.preventDefault(); const resize = (ev2) => document.documentElement.style.setProperty( "--mini-width", `${container.offsetWidth + container.offsetLeft - ev2.x + 1}px` ); document.addEventListener("mousemove", resize); document.addEventListener("mouseup", () => document.removeEventListener("mousemove", resize), { once: true }); }; const videoArea = container.getElementsByClassName("bpx-player-video-area")[0]; if (!videoArea) { console.error("页面加载错误:视频区域不存在"); break; } observeFor("bpx-player-mini-warp", videoArea).then((wrap) => wrap.appendChild(miniResizer)); const sendingBar = player.getElementsByClassName("bpx-player-sending-bar")[0]; if (!sendingBar) { console.error("页面加载错误:发送框不存在"); break; } const danmaku = (await( observeFor("bpx-player-video-info", sendingBar))).parentElement; const bottomCenter = container.getElementsByClassName("bpx-player-control-bottom-center")[0]; if (!bottomCenter || !danmaku) { console.error("页面加载错误:弹幕框不存在"); break; } document.addEventListener("fullscreenchange", () => { if (!document.fullscreenElement) { bottomCenter.replaceChildren(danmaku); } }); bottomCenter.replaceChildren(danmaku); console.info("宽屏模式成功启用"); break; } case "t.bilibili.com": GM_addStyle(styles.t); waitFor(() => document.getElementsByClassName("right")[0], "动态右栏").then((right) => { right.prepend(...document.getElementsByClassName("left")[0]?.childNodes ?? []); }); console.info("使用动态样式"); break; case "space.bilibili.com": GM_addStyle(styles.space); console.info("使用空间样式"); break; case "search.bilibili.com": GM_addStyle(styles.search); console.info("使用搜索页样式"); break; default: console.info(`未适配页面,仅启用通用样式: ${url.href}`); break; } })();