您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
哔哩哔哩宽屏体验
当前为
// ==UserScript== // @name Wider Bilibili // @name:zh 哔哩哔哩宽屏 // @namespace http://greasyfork.icu/users/1125570 // @description 哔哩哔哩宽屏体验 // @description:en BiliBili, but wider // @version 0.3.5 // @author posthumz // @license MIT // @match http*://*.bilibili.com/* // @icon https://www.bilibili.com/favicon.ico // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_addValueChangeListener // @noframes // ==/UserScript== (async function () { 'use strict' const styles = { common: `:root { --layout-padding: ${GM_getValue('左右边距', 30)}px; } html, body { width: initial !important; height: initial !important; } /* 搜索栏 */ .center-search-container { min-width: 0; } .nav-search-input { width: 0 !important; padding-right: 0 !important; } /* 脚本设置样式 */ #WBOptions { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: 114514; border-radius: 15px; padding: 20px; display: none; grid-template-columns: repeat(2, 1fr); gap: 20px 30px; background-color: var(--bg1); color: var(--text1); outline: 4px solid #00a0d8; font-size: 18px; } #WBOptionsClose { position: absolute; border: none; right: 0; font-size: 30px; line-height: 30px; width: 30px; border-top-right-radius: 15px; border-bottom-left-radius: 5px; transition: .1s; background-color: transparent; color: var(--text1); } #WBOptionsClose:hover { background-color: #e81123; } #WBOptionsClose:active { opacity: 0.5; } #WBOptions>header { grid-column: 1/-1; } #WBOptions>label { align-items: center; display: flex; gap: 10px; } #WBOptions input { height: 20px; margin: 0; padding: 4px !important; box-sizing: content-box !important; font-size: 16px; } #WBOptions input[type=checkbox] { width: 40px; appearance: none; border-radius: 20px; box-sizing: content-box; cursor: pointer; background-color: #ccc; transition: .2s; } #WBOptions input[type=checkbox]::before { content: ""; display: flex; position: relative; height: 100%; aspect-ratio: 1/1; border-radius: 50%; background-color: #fff; transition: .2s; } #WBOptions input[type=checkbox]:checked { background-color: #00a0d8; } #WBOptions input[type=checkbox]:checked::before { transform: translateX(20px); } #WBOptions input[type=checkbox]:hover { box-shadow: 0 0 4px #00a0d8; } #WBOptions input[type=checkbox]:active { opacity: 0.5; } #WBOptions input[type=number] { width: 60px; border: none; border-radius: 5px; background: none; outline: 2px solid #00a0d8; color: var(--text1) !important; appearance: textfield; } #WBOptions input[type=number]::-webkit-inner-spin-button { appearance: none; }`, home: `/* 首页 */ .feed-card, .floor-single-card, .bili-video-card { margin-top: 0px !important; } .feed-roll-btn { left: initial !important; right: calc(10px - var(--layout-padding)); } .palette-button-outer { padding: 0; } .palette-button-wrap { left: initial !important; right: 10px; }`, t: `.bili-dyn-home--member { margin: 0 var(--layout-padding) !important; main { flex: 1 } } `, 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; } } .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; }`, read: `.article-detail { width: 90%; .article-up-info { width: initial; margin: 0 80px 20px; } .right-side-bar { right: 0; } }`, video: `/* 播放器 */ :root { --nav-height: 64px; --video-height: calc(100vh - var(--nav-height)); } .video-container-v1, /* 视频页 */ .main-container, /* 番剧页 */ .left-container { position: initial !important; } #playerWrap, #bilibili-player-wrap { position: absolute; left: 0; right: 0; top: var(--nav-height); height: var(--video-height); padding-right: 0 !important; /* 番剧页加载时会有右填充 */ } div#bilibili-player { width: 100%; height: 100%; box-shadow: none !important; } /* 修复加载动画不显示 */ .bpx-player-loading-panel-blur { display: flex !important; } /* 原弹幕发送区域不显示 */ .bpx-player-sending-area { display: none; } /* 导航栏 */ #biliMainHeader { margin-bottom: var(--video-height); position: sticky; top: 0; z-index: 2; } .bili-header.fixed-header { min-height: 0 !important; } .bili-header__bar { z-index: 2 !important; position: relative !important; } /* 视频页、番剧页、收藏/稍后再看页的下方容器 */ .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; } /* 番剧页右下方浮动按钮修正 */ div[class^=navTools_floatNav] { z-index: 2 !important; } /* Bilibili Evolved侧栏 */ .be-settings .sidebar { z-index: 2 !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; }`, controls: `/* 播放器控件 */ .bpx-player-top-left-title, .bpx-player-top-left-music { display: block !important; } .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; }`, mini: `/* 小窗 */ .bpx-player-container[data-screen="mini"] { /* 以视频长宽比为准,不显示黑边和阴影 */ height: auto !important; box-shadow: none; translate: 32px 40px; /* 修正小窗位置 */ } /* 非小窗不使用自定义宽度 */ .bpx-player-container[data-screen="web"] { width: 100% !important; } /* 最小宽度,以防不可见 */ .bpx-player-container { min-width: 180px; } .bpx-player-mini-resizer { position: absolute; left: 0; width: 10px; height: 100%; cursor: ew-resize; }`, lowerNavigation: `/* 导航栏下置 */ #biliMainHeader { margin-top: 100vh; margin-bottom: 0; } #playerWrap, #bilibili-player-wrap { top: 0; height: 100vh; }` } GM_addStyle(styles.common) /** * @typedef {Object} Option * @property {string} name * @property {boolean|number} default */ const /** @type {Option[]} */ options = [ { name: '导航栏下置', default: true }, { name: '播放器控件样式', default: true }, { name: '左右边距', default: 30 } ] /** @param {Option} option */ function optionInput (option) { switch (typeof option.default) { case 'boolean': return `<input type="checkbox" ${GM_getValue(option.name, option.default) ? ' checked' : ''}>` case 'number': return `<input type="number" min="0" value="${GM_getValue(option.name, option.default)}">` } } // 设置选项功能 const optionsDiv = document.body.appendChild(document.createElement('div')) optionsDiv.id = 'WBOptions' optionsDiv.innerHTML = `<button id="WBOptionsClose">×</button> <header>⚙️宽屏选项</header> ${options.map(option => `<label>${optionInput(option)}${option.name}</label>`).join('\n')}` // 调出设置选项 GM_registerMenuCommand('选项', () => { optionsDiv.style.display = 'grid' }) // 关闭设置选项 document.getElementById('WBOptionsClose')?.addEventListener('click', () => { optionsDiv.style.display = 'none' }) // 设置选项事件 for (const input of optionsDiv.getElementsByTagName('input')) { const key = input.parentElement?.textContent ?? '' if (!key) { continue } switch (input.type) { case 'checkbox': input.onchange = () => { GM_setValue(key, input.checked) } break case 'number': input.oninput = () => { const val = Number(input.value) Number.isInteger(val) && GM_setValue(key, val) } break } } GM_addValueChangeListener('左右边距', (_k, _o, newVal) => document.documentElement.style.setProperty('--layout-padding', `${newVal}px`) ) /** * 等待条件满足并返回结果 * @template T * @param {() => T} loaded * @returns {Promise<NonNullable<T>>} * @description 每一定时间检测某个条件是否满足,超时则reject */ const waitFor = (loaded, desc = '页面加载', retry = 100, interval = 100) => new Promise((resolve, reject) => { const intervalID = setInterval((res = loaded()) => { if (res) { clearInterval(intervalID) console.log(`${desc}已加载`) return resolve(res) } if (--retry === 0) { console.error('页面加载超时') clearInterval(intervalID) return reject(new Error('timeout')) } if (retry % 10 === 0) { console.debug(`等待${desc}`) } }, interval) }) /** * 直接获取元素或等待元素被添加 * @param {string} className * @param {Element} [parent] * @returns {Promise<Element>} */ const observeFor = (className, parent = document.body) => 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 }) }) switch ((new URL(window.location.href)).host) { case 't.bilibili.com': GM_addStyle(styles.t) console.info('使用动态样式') break case 'space.bilibili.com': GM_addStyle(styles.space) console.info('使用空间样式') break case 'www.bilibili.com': { if (document.getElementById('i_cecream')) { // 首页 GM_addStyle(styles.home) console.info('使用首页宽屏样式') break } if (document.getElementsByClassName('article-detail')[0]) { // 阅读页 GM_addStyle(styles.read) console.info('使用阅读页宽屏样式') break } // #region 视频页 const player = document.getElementById('bilibili-player') if (!player) { return console.info('未找到播放器,仅启用通用样式') } // 播放器外容器,视频播放页为#playerWrap,番剧/影视播放页为#bilibili-player-wrap const stylesheets = { video: GM_addStyle(styles.video), controls: GM_addStyle(styles.controls), mini: GM_addStyle(styles.mini), lowerNavigation: GM_addStyle(styles.lowerNavigation) } const header = document.getElementById('biliMainHeader') if (!header) { return console.error('页面加载错误:未找到导航栏') } // 改变导航栏位置至下方 const lowerNavigation = (value = true) => { stylesheets.lowerNavigation.disabled = !value } lowerNavigation(GM_getValue('导航栏下置')) GM_addValueChangeListener('导航栏下置', (_k, _o, newVal) => { lowerNavigation(newVal) }) const styledControls = (value = true) => { stylesheets.controls.disabled = !value } styledControls(GM_getValue('播放器控件样式')) GM_addValueChangeListener('播放器控件样式', (_k, _o, newVal) => { styledControls(newVal) }) // 等待人数加载完成,再进行dom操作 const infos = player.getElementsByClassName('bpx-player-video-info') await waitFor(() => infos[0], '正在观看') // 播放器内容器 const container = player.getElementsByClassName('bpx-player-container')[0] if (!(container instanceof HTMLDivElement)) { return console.error('页面加载错误:播放器内容器不存在') } // 播放器底中部框 (用于放置弹幕框内容) const bottomCenter = container.getElementsByClassName('bpx-player-control-bottom-center')[0] const center = bottomCenter?.previousElementSibling?.hasChildNodes() ? bottomCenter : player.getElementsByClassName('squirtle-controller-wrap-center')[0] // 原弹幕框 const danmaku = container.getElementsByClassName('bpx-player-sending-bar')[0] if (!center || !danmaku) { return console.error('页面加载错误:弹幕框不存在') } // 立即使用宽屏样式 (除非当前是小窗模式) if (container.getAttribute('data-screen') !== 'mini') { container.setAttribute('data-screen', 'web') } // 重载container的setAttribute:data-screen被设置为mini(小窗)以外的值时将其设置为web(宽屏) container.setAttribute = new Proxy(container.setAttribute, { apply: (target, thisArg, /** @type {[string, string]} */ [name, val]) => target.apply(thisArg, [name, name === 'data-screen' && val !== 'mini' ? 'web' : val]) }) // 移除原 宽屏/网页全屏 按钮,因为没有用了 for (const className of [ 'bpx-player-ctrl-wide', 'bpx-player-ctrl-web', 'squirtle-widescreen-wrap', 'squirtle-pagefullscreen-wrap' ]) { player.getElementsByClassName(className)[0]?.remove() } // 退出全屏时弹幕框移至播放器下方 document.addEventListener('fullscreenchange', () => { if (!document.fullscreenElement) { center.replaceChildren(danmaku) } }) // 立即将弹幕框移至播放器下方一次 center.replaceChildren(danmaku) // 将自定义顶栏插入默认顶栏后 observeFor('custom-navbar').then(nav => header.insertAdjacentElement('afterend', nav)) console.info('宽屏模式成功启用') // 添加拖动调整大小的部件 const miniResizer = document.createElement('div') miniResizer.className = 'bpx-player-mini-resizer' miniResizer.onmousedown = ev => { ev.stopImmediatePropagation() ev.preventDefault() /** @param {MouseEvent} ev */ const resize = ev => { container.style.width = `${container.offsetWidth + container.offsetLeft - ev.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] videoArea && observeFor('bpx-player-mini-warp', videoArea).then(wrap => wrap.appendChild(miniResizer)) // #endregion break } default: console.info('未知页面,仅启用通用样式') break } })()