您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
哔哩哔哩宽屏体验
当前为
- // ==UserScript==
- // @name 哔哩哔哩宽屏
- // @name:en Wider Bilibili
- // @namespace https://greasyfork.org/users/1125570
- // @description 哔哩哔哩宽屏体验
- // @description:en BiliBili, but wider
- // @version 0.3.1
- // @author posthumz
- // @license MIT
- // @match http*://*.bilibili.com/*
- // @icon https://www.bilibili.com/favicon.ico
- // @run-at document-end
- // @grant GM_addStyle
- // @grant GM_getValue
- // @grant GM_setValue
- // @grant GM_registerMenuCommand
- // @grant GM_addValueChangeListener
- // @noframes
- // ==/UserScript==
- (async () => {
- 'use strict'
- const styles = {
- home:
- `.feed-card, .floor-single-card, .bili-video-card {
- margin-top: 0px !important;
- }`,
- t:
- `.bili-dyn-home--member {
- margin: 0 var(--layout-padding) !important;
- main {
- flex: 1
- }
- }
- `,
- read:
- `.article-detail {
- width: 90%;
- .article-up-info {
- width: initial;
- margin: 0 80px 20px;
- }
- .right-side-bar {
- right: 0;
- }
- }`,
- video:
- `/* 播放器 */
- #bilibili-player {
- position: relative;
- z-index: 1;
- width: 100%;
- height: 100%;
- }
- #playerWrap,
- #bilibili-player-wrap {
- position: relative;
- height: 100vh;
- min-height: 20vh;
- padding: 0;
- }
- /* 小窗 */
- .bpx-player-container[data-screen="mini"] {
- height: auto !important; /* 以视频长宽比为准,且不显示黑边 */
- transform: translateY(24px) !important;
- }
- .bpx-player-container:not([data-screen="mini"]) {
- width: 100% !important;
- }
- /* 视频标题换行显示 */
- #viewbox_report {
- height: auto;
- }
- .video-title {
- white-space: normal !important;
- }
- /* 视频页, 番剧页, 收藏(包括稍后再看)页 下方容器 */
- .video-container-v1, .main-container, .playlist-container {
- z-index: 0;
- padding: 0 var(--layout-padding);
- }
- .left-container, .plp-l, .playlist-container--left {
- flex: 1;
- }
- .plp-r {
- padding-top: 0 !important;
- }
- /* 番剧/影视页下方容器 */
- .main-container {
- width: 100%;
- margin: 0;
- padding: 15px 50px 15px 25px !important;
- box-sizing: border-box;
- display: flex;
- }
- .player-left-components {
- padding-right: 30px !important;
- }
- .toolbar {
- padding-top: 0;
- }
- /* 播放器控件 */
- .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: 12px;
- >:not(.bpx-player-ctrl-viewpoint, .bpx-player-ctrl-time) {
- width: auto !important;
- }
- }
- .bpx-player-ctrl-viewpoint {
- margin: 0;
- }
- .bpx-player-control-bottom-center {
- padding: 0 !important;
- display: flex;
- justify-content: center;
- }
- .bpx-player-sending-bar {
- margin: 0 !important;
- }
- .bpx-player-control-bottom-right {
- min-width: initial !important;
- }
- .bpx-player-ctrl-time-label {
- text-align: center !important;
- text-indent: 0 !important;
- }
- .bpx-player-ctrl-time, .bpx-player-ctrl-quality {
- margin-right: 0 !important;
- }
- .bpx-player-video-info {
- margin-right: 0 !important;
- display: flex !important;
- }
- /* 右下方浮动按钮 */
- div[class^=navTools_floatNav] {
- z-index: 1 !important;
- }
- /* 笔记位移 (不然笔记会超出页面初始范围) */
- .note-pc {
- transform: translate(-12px, 64px);
- }
- /* 导航栏 (兼容Bilibili Evolved自定义导航栏) */
- #biliMainHeader, .custom-navbar {
- position: sticky !important;
- top: 0;
- z-index: 3 !important;
- }
- #biliMainHeader > .bili-header {
- min-height: 0 !important;
- }
- .bili-header__bar {
- position: relative !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;
- }
- /* Bilibili Evolved侧栏 */
- .be-settings {
- z-index: 3;
- position: fixed;
- }`,
- mini:
- `.bpx-player-container {
- min-width: 180px;
- }
- .bpx-player-mini-resizer {
- position: absolute;
- left: 0;
- width: 10px;
- height: 100%;
- cursor: ew-resize;
- }`,
- general:
- `:root {
- --layout-padding: ${GM_getValue('左右边距', 30)}px;
- }
- /* 搜索栏 */
- .center-search-container {
- min-width: 0;
- }
- .nav-search-input {
- padding-right: 0;
- }
- .nav-search-clean {
- display: none;
- }
- /* 脚本设置样式 */
- #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);
- box-shadow: 0 0 4px #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;
- }
- .slider::before {
- content: "";
- display: block;
- position: relative;
- height: 100%;
- aspect-ratio: 1/1;
- border-radius: 50%;
- background-color: #fff;
- transition: .4s;
- }
- .slider {
- appearance: none;
- width: 40px;
- border-radius: 20px;
- box-sizing: content-box;
- cursor: pointer;
- background-color: #ccc;
- transition: .4s;
- }
- .slider:checked::before {
- transform: translateX(20px);
- }
- .slider:checked {
- background-color: #00a0d8;
- }
- .slider:hover {
- box-shadow: 0 0 4px #00a0d8;
- }
- .slider:active {
- opacity: 0.5;
- }
- #WBOptions input[type=number] {
- width: 60px;
- border: none;
- border-radius: 5px;
- background: transparent;
- box-shadow: 0 0 0 2px #00a0d8;
- color: var(--text1) !important;
- }`}
- GM_addStyle(styles.general)
- // /** @type Map<string, Function> */
- // const callbacks = new Map()
- // 设置选项功能
- const options = document.body.appendChild(document.createElement('div'))
- options.id = 'WBOptions'
- options.innerHTML =`<button id="WBOptionsClose">×</button>
- <header>⚙️宽屏选项</header>
- <label><input type="checkbox" class="slider"${GM_getValue('导航栏下置', true) ? ' checked': ''}>导航栏下置</label>
- <label><input type="number" placeholder="px" value="${GM_getValue('左右边距', 30)}">左右边距</label>`
- GM_registerMenuCommand('选项', () => { options.style.display = 'grid' })
- options.getElementsByTagName('button')[0]?.addEventListener('click', () => { options.style.display = 'none' })
- for (const Element of options.children) {
- if (Element instanceof HTMLLabelElement) {
- const input = Element.getElementsByTagName('input')[0]
- const key = Element.textContent ?? ''
- switch (input?.type) {
- case null:
- default:
- console.error('啊?')
- break
- case 'checkbox':
- input.onchange = () => {
- GM_setValue(key, input.checked)
- }
- break
- case 'number':
- input.oninput = () => {
- const val = Number(input.value)
- if (Number.isInteger(val)) {
- GM_setValue(key, val)
- }
- }
- break
- }
- }
- }
- GM_addValueChangeListener('左右边距', (_n, _o, newVal) =>
- document.documentElement.style.setProperty('--layout-padding', `${newVal}px`))
- // 每一定时间检测某个条件是否满足,超时则reject
- const waitFor = (/** @type {() => boolean}*/ loaded, retry = 100, interval = 100) =>
- new Promise((resolve, reject) => {
- const intervalID = setInterval(() => {
- if (--retry == 0) {
- console.error('页面加载超时')
- clearInterval(intervalID)
- return reject()
- }
- if (loaded()) {
- clearInterval(intervalID)
- return resolve(null)
- }
- if (retry % 10 == 0) { console.debug('等待页面加载') }
- }, interval)
- })
- const url = new URL(window.location.href)
- switch (url.host) {
- case 't.bilibili.com':
- GM_addStyle(styles.t)
- console.info('使用动态样式')
- break
- case 'www.bilibili.com': {
- // #region 首页
- if (document.getElementById('i_cecream')) {
- GM_addStyle(styles.home)
- console.info('使用首页宽屏样式')
- break
- }
- // #endregion
- // #region 阅读页
- if (document.getElementsByClassName('article-detail')[0]) {
- GM_addStyle(styles.read)
- console.info('使用阅读页宽屏样式')
- break
- }
- // #endregion
- // #region 播放页
- // 播放器不存在时不执行
- const player = document.getElementById('bilibili-player')
- if (!player) { return console.info('未找到播放器,不启用宽屏模式') }
- // 主容器,视频播放页为#app,番剧/影视播放页为.home-container
- const home = document.getElementById('app') ?? document.getElementsByClassName('home-container')[0]
- // 播放器外容器,视频播放页为#playerWrap,番剧/影视播放页为#bilibili-player-wrap
- const wrap = document.getElementById('playerWrap') ?? document.getElementById('bilibili-player-wrap')
- // 在新版本页面,播放器存在时都应该存在
- if (!wrap || !home) { return console.error(
- `页面加载错误:${[
- wrap ? '' : '播放器外容器',
- home ? '' : '主容器',
- ].filter(Boolean).join(', ')},请检查是否为新版页面`
- ) }
- // 等待人数加载
- const b = player.getElementsByTagName('b')
- await waitFor(() => b[0]?.textContent != null)
- await waitFor(() => b[0]?.textContent != '-')
- // 导航栏 (兼容Bilibili Evolved自定义顶栏,有可能延后加载)
- const navigation = await (async () => {
- const header = document.getElementById('biliMainHeader')
- if (header) {
- header.style.setProperty('height', 'initial', 'important')
- // 将导航栏移至主容器最前
- home.insertAdjacentElement('afterbegin', header)
- // bili-header__bar不可见时使用自定义顶栏
- const headerBar = header.getElementsByClassName('bili-header__bar')[0]
- if (headerBar && window.getComputedStyle(headerBar).display == 'none') {
- const navbar = document.getElementsByClassName('custom-navbar')
- await waitFor(() => Boolean(navbar[0]))
- return home.insertAdjacentElement('afterbegin', navbar[0])
- }
- }
- return header
- })()
- // 播放器内容器
- const container = player.getElementsByClassName('bpx-player-container')[0]
- // 播放器底中部框 (用于放置弹幕框内容)
- const bottomCenter = (() => {
- const center = player.getElementsByClassName('bpx-player-control-bottom-center')[0]
- // 番剧版使用squirtle-controller-wrap-center,但也存在bpx-player-control-bottom-center
- // 所以通过检测前一个元素(bpx-player-control-bottom-left)是否有子元素来判断使用哪个
- return center?.previousElementSibling?.hasChildNodes() ? center
- : player.getElementsByClassName('squirtle-controller-wrap-center')[0]
- })()
- // 弹幕框
- const danmaku = player.getElementsByClassName('bpx-player-sending-bar')[0]
- // 正常情况应该都存在
- if (!navigation || !(container instanceof HTMLDivElement) || !bottomCenter || !danmaku) {
- return console.error(
- `页面加载错误:${[
- navigation ? '' : '导航栏',
- container ? '' : '播放器内容器',
- bottomCenter ? '' : '播放器底中部框',
- danmaku ? '' : '弹幕框',
- ].filter(Boolean).join(', ')}`
- )
- }
- // 改变导航栏位置,true为视频下方,false为视频上方,默认为下方
- const lowerNavigation = (value = true) => {
- if (value) {
- wrap.style.removeProperty('height')
- navigation.insertAdjacentElement('beforebegin', wrap)
- } else {
- wrap.style.height = `calc(100vh - ${navigation.clientHeight}px)`
- navigation.insertAdjacentElement('afterend', wrap)
- }
- return value
- }
- lowerNavigation(GM_getValue('导航栏下置', true))
- GM_addValueChangeListener('导航栏下置', (_n, _o, newVal) => { lowerNavigation(newVal) })
- // 使用宽屏样式 (除非当前是小窗模式)
- if (container.getAttribute('data-screen') != 'mini') {
- container.setAttribute('data-screen', 'web')
- }
- // 重载container的setAttribute:data-screen被设置为mini(小窗)以外的值时将其设置为web(宽屏)
- const setAttributeContainer = container.setAttribute.bind(container)
- container.setAttribute = (name, value) =>
- setAttributeContainer(name, name == 'data-screen' && value != 'mini' ? 'web' : value)
- // 番剧页面需要初始与退出全屏时移除#bilibili-player-wrap的class
- if (wrap.id == 'bilibili-player-wrap') {
- wrap.className = ''
- document.addEventListener('fullscreenchange',
- () => { document.fullscreenElement ?? (wrap.className = '') }
- )
- }
- // 退出全屏时弹幕框移至播放器下方
- document.addEventListener('fullscreenchange',
- () => { document.fullscreenElement ?? bottomCenter.replaceChildren(danmaku) }
- )
- // 移除原 宽屏/网页全屏 按钮,因为没有用了
- for (const className of [
- 'bpx-player-ctrl-wide', 'bpx-player-ctrl-web',
- 'squirtle-widescreen-wrap', 'squirtle-pagefullscreen-wrap',
- ]) { player.getElementsByClassName(className)[0]?.remove() }
- // 添加视频样式
- GM_addStyle(styles.video)
- // 将弹幕框移至播放器下方一次
- bottomCenter.replaceChildren(danmaku)
- // 将笔记移至主容器,不然会被视频和导航栏遮挡
- const note = document.getElementsByClassName('note-pc')[0]
- if (note) {
- navigation.insertAdjacentElement('afterend', note)
- }
- console.info('宽屏模式成功启用')
- // #region 小窗
- GM_addStyle(styles.mini)
- const miniResizer = document.createElement('div')
- miniResizer.className = 'bpx-player-mini-resizer'
- miniResizer.onmousedown = (ev) => {
- ev.stopImmediatePropagation()
- ev.preventDefault()
- const resize = (/** @type MouseEvent */ 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})
- }
- container.getElementsByClassName('bpx-player-mini-warp')[0]?.appendChild(miniResizer) ?? (
- container.getElementsByClassName('bpx-player-video-area')[0] &&
- new MutationObserver((mutations, observer) => {
- mutations.filter(mutation => mutation.type == 'childList').forEach(mutation => {
- mutation.addedNodes.forEach(node => {
- if (node instanceof Element && node.classList.contains('bpx-player-mini-warp')) {
- node.appendChild(miniResizer)
- observer.disconnect()
- }
- })
- })
- }
- ).observe(container.getElementsByClassName('bpx-player-video-area')[0], {childList: true})
- )
- // #endregion
- break
- // #endregion
- }
- default:
- console.info('未知页面')
- break
- }
- })()