Wider Bilibili

哔哩哔哩宽屏体验

当前为 2025-01-13 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Wider Bilibili
  3. // @namespace https://greasyfork.org/users/1125570
  4. // @version 0.4.6
  5. // @author posthumz
  6. // @description 哔哩哔哩宽屏体验
  7. // @license MIT
  8. // @icon https://www.bilibili.com/favicon.ico
  9. // @supportURL https://github.com/posthumz/wider-bilibili/issues
  10. // @match http*://*.bilibili.com/*
  11. // @grant GM_addStyle
  12. // @grant GM_addValueChangeListener
  13. // @grant GM_getValue
  14. // @grant GM_registerMenuCommand
  15. // @grant GM_setValue
  16. // @run-at document-start
  17. // @noframes
  18. // @compatible firefox 117+
  19. // @compatible chrome 120+
  20. // @compatible edge 120+
  21. // @compatible safari 17.2+ (理论上,实际未经测试)
  22. // ==/UserScript==
  23.  
  24. (async function () {
  25. 'use strict';
  26.  
  27. const styles = {
  28. video: `/* 播放器 */
  29. :root {
  30. --navbar-height: 64px;
  31. --player-height: 100vh;
  32. }
  33.  
  34. /* 播放器定位 */
  35. #playerWrap.player-wrap,
  36. #bilibili-player-wrap {
  37. position: absolute;
  38. left: 0;
  39. right: 0;
  40. top: 0;
  41. height: auto;
  42. /* 番剧页加载时播放器会有右填充 */
  43. padding-right: 0;
  44. }
  45.  
  46. #bilibili-player {
  47. /* 播放器适应宽高 */
  48. height: auto !important;
  49. width: auto !important;
  50. box-shadow: none !important;
  51.  
  52. .bpx-player-container {
  53. box-shadow: none;
  54. }
  55.  
  56. /* Bilibili Evolved 夜间模式样式的优先级很高,所以嵌套在#bilibili-player里面 */
  57. .bpx-player-video-info {
  58. color: hsla(0, 0%, 100%, .9) !important;
  59. margin-right: 10px;
  60. /* 某些页面莫名其妙加了width */
  61. width: auto !important;
  62. }
  63.  
  64. .bpx-player-video-btn-dm,
  65. .bpx-player-dm-setting,
  66. .bpx-player-dm-switch {
  67. fill: hsla(0, 0%, 100%, .9) !important;
  68. }
  69.  
  70. .bpx-player-dm-hint>a {
  71. color: hsla(0, 0%, 100%, .6) !important;
  72. fill: hsla(0, 0%, 100%, .6) !important;
  73. }
  74. }
  75.  
  76. /* 限制高度上限100vh */
  77. .bpx-player-video-wrap>video {
  78. max-height: 100vh;
  79. }
  80.  
  81. /* 小窗时仍然保持播放器容器高度 */
  82. .bpx-docker:has(>.bpx-player-container[data-screen="mini"]) {
  83. height: var(--player-height);
  84. }
  85.  
  86. /* 加载时强制占用全屏 */
  87. .bpx-player-container:not([data-screen="mini"]) .bpx-player-video-area:has(>.bpx-state-loading) video {
  88. height: 100vh;
  89. }
  90.  
  91. /* 换源时强制占用全屏 */
  92. .bpx-player-video-wrap>video:not([src]) {
  93. height: 100vh;
  94. }
  95.  
  96. /* 这啥?加载时会导致屏幕超出 */
  97. .bpx-player-cmd-dm-wrap {
  98. position: absolute;
  99. top: 0;
  100. }
  101.  
  102. /* 加载动画强制显示 */
  103. .bpx-player-loading-panel-blur {
  104. display: flex !important;
  105. }
  106.  
  107. /* 强制显示播放器控件 */
  108. .bpx-player-top-left-title,
  109. .bpx-player-top-left-music,
  110. .bpx-player-top-mask {
  111. display: block !important;
  112. }
  113.  
  114. /* 不然会鬼畜 */
  115. .bpx-player-top-mask {
  116. transition-property: none !important;
  117. }
  118.  
  119. /* 原弹幕发送区域不显示 */
  120. .bpx-player-sending-area {
  121. display: none;
  122. }
  123.  
  124. /* 原宽屏/网页全屏按钮不显示 */
  125. .bpx-player-ctrl-wide,
  126. .bpx-player-ctrl-web {
  127. display: none;
  128. }
  129.  
  130. /* 以防窗口过窄 */
  131. #app {
  132. width: fit-content;
  133. min-width: 100vw;
  134. }
  135.  
  136. /* 导航栏 */
  137. #biliMainHeader {
  138. height: auto !important;
  139. margin-top: var(--player-height);
  140. margin-bottom: 0;
  141. position: initial;
  142. visibility: initial !important;
  143.  
  144. >.bili-header>.bili-header__bar {
  145. position: relative !important;
  146. height: var(--navbar-height);
  147. max-width: none;
  148. }
  149.  
  150. /* BiliBili Evolved自定义顶栏加载前,强制显示原生顶栏 */
  151. &:not(:has(>.custom-navbar)) .bili-header__bar {
  152. display: flex !important;
  153. }
  154.  
  155. /* 自定义顶栏加载后 */
  156. >.custom-navbar {
  157. position: relative;
  158. z-index: 3 !important;
  159. }
  160. }
  161.  
  162. /* 自定义顶栏加载前 */
  163. body>.custom-navbar {
  164. z-index: 0 !important;
  165. }
  166.  
  167. /* 使用 static 才能让播放器的 absolute 正确定位 */
  168. /* 视频、番剧、收藏/稍后再看页 */
  169. .video-container-v1,
  170. .left-container,
  171. .main-container,
  172. .playlist-container--left {
  173. position: static !important;
  174. }
  175.  
  176. /* 视频页、番剧页、收藏/稍后再看页的下方容器 */
  177. .video-container-v1,
  178. .main-container,
  179. .playlist-container {
  180. padding: 0 var(--layout-padding) !important;
  181. max-width: none !important;
  182. min-width: auto !important;
  183. }
  184.  
  185. .left-container,
  186. .plp-l,
  187. .playlist-container--left {
  188. flex: 1;
  189. width: auto;
  190. }
  191.  
  192. .plp-r {
  193. /* 番剧页加载时不会先使用sticky */
  194. position: sticky !important;
  195. padding-top: 0 !important;
  196. }
  197.  
  198. /* 以防播放器挡住一些浮窗 */
  199. .playlist-container--left,
  200. .bilibili-player-wrap {
  201. z-index: 1 !important;
  202. }
  203.  
  204. /* 番剧页下方容器 */
  205. .main-container {
  206. width: auto !important;
  207. box-sizing: border-box;
  208. display: flex;
  209.  
  210. /* 番剧页弹窗 */
  211. >:nth-last-child(2)[class^=dialogcoin_coin_dialog_mask] {
  212. z-index: 100001;
  213. }
  214.  
  215. /* 右下方浮动按钮位置 */
  216. >:last-child[class^=navTools_floatNavExp] {
  217. z-index: 2 !important;
  218. }
  219. }
  220.  
  221. /* 特殊页面 */
  222. .special .main-container {
  223. margin-top: 0;
  224. }
  225.  
  226. .special>.special-cover {
  227. max-height: calc(var(--player-height) + var(--navbar-height));
  228. }
  229.  
  230. .player-left-components {
  231. padding-right: 30px !important;
  232. }
  233.  
  234. .toolbar {
  235. padding-top: 0;
  236. }
  237.  
  238. /* 视频标题自动高度 */
  239. #viewbox_report,
  240. .video-info-container {
  241. height: auto;
  242. }
  243.  
  244. /* 视频标题换行显示 */
  245. .video-title {
  246. white-space: normal !important;
  247. }
  248.  
  249. /* bgm浮窗 */
  250. #bgm-entry {
  251. z-index: 114514 !important;
  252. left: 0 !important;
  253. }
  254.  
  255. /* 笔记浮窗 */
  256. .note-pc {
  257. z-index: 114514 !important;
  258. }
  259.  
  260. .fixed-sidenav-storage {
  261. z-index: initial !important;
  262. }
  263.  
  264. /* Bilibili Evolved侧栏 */
  265. .be-settings .sidebar {
  266. z-index: 114514 !important;
  267. }`,
  268. t: `#app {
  269.  
  270. /* 单个动态 */
  271. >.bg+.content {
  272. width: auto;
  273. margin: 10px 0;
  274.  
  275. >.card {
  276. margin: 0 var(--layout-padding)
  277. }
  278.  
  279. >.sidebar-wrap {
  280. right: 58px;
  281. margin-right: var(--layout-padding);
  282. }
  283. }
  284.  
  285. /* 动态页 */
  286. >[class^=bili-dyn-home] {
  287. margin: 0 var(--layout-padding);
  288.  
  289. .left {
  290. .bili-dyn-live-users {
  291. margin-bottom: 10px;
  292. position: initial !important;
  293. transform: none !important;
  294. }
  295. }
  296.  
  297. .right {
  298. width: initial;
  299.  
  300. .bili-dyn-banner {
  301. display: none;
  302. }
  303.  
  304. .bili-dyn-topic-box {
  305. transform: none !important;
  306. }
  307. }
  308.  
  309. main {
  310. flex: 1
  311. }
  312.  
  313. .bili-dyn-sidebar {
  314. right: var(--layout-padding);
  315. transform: none;
  316. }
  317. }
  318. }`,
  319. space: `/* 空间页 */
  320. #app {
  321. margin: 0 var(--layout-padding);
  322. min-width: 1120px;
  323. }
  324.  
  325. #biliMainHeader {
  326. height: initial !important;
  327. }
  328.  
  329. div.wrapper,
  330. .search-page {
  331. width: auto !important;
  332. margin: 0;
  333. }
  334.  
  335. /* 主页 */
  336. #page-index {
  337. >div.col-1 {
  338. /* 以防不支持round */
  339. max-width: calc(100% - 400px);
  340. width: round(down, calc(100% - 400px), 180px);
  341.  
  342. .section {
  343. >.content {
  344. width: auto;
  345. }
  346.  
  347. /* 投稿、投币、点赞 */
  348. &.video>.content,
  349. &.coin>.content,
  350. .channel-video {
  351. margin-left: -10px;
  352. overflow: auto !important;
  353. scroll-snap-type: both mandatory;
  354.  
  355. >.small-item {
  356. padding: 10px !important;
  357. scroll-snap-align: start;
  358. }
  359.  
  360. &::after {
  361. content: none;
  362. }
  363. }
  364.  
  365. /* 收藏 */
  366. &.fav>.content {
  367. margin-top: -14px;
  368. margin-left: -10px;
  369.  
  370. >.fav-item {
  371. margin: 14px 10px;
  372. }
  373. }
  374.  
  375. /* 番剧 */
  376. &.bangumi>.content>.large-item {
  377. margin-right: 0;
  378. }
  379. }
  380.  
  381. .article-content {
  382. width: calc(100% - 135px);
  383. }
  384. }
  385. }
  386.  
  387. /* 动态 */
  388. #page-dynamic>div.col-1 {
  389. width: calc(100% - 360px);
  390. }
  391.  
  392. /* 投稿, 搜索 */
  393. #page-video {
  394. width: 100% !important;
  395.  
  396. >.col-full {
  397. display: flex;
  398.  
  399. >.main-content {
  400. flex: 1;
  401.  
  402. .cube-list {
  403. width: auto !important;
  404. display: flex;
  405. flex-wrap: wrap;
  406. justify-content: center;
  407. }
  408. }
  409. }
  410. }
  411.  
  412. /* 合集 */
  413. .channel-index {
  414. width: auto !important;
  415.  
  416. .channel-list {
  417. gap: 20px;
  418.  
  419. &::before,
  420. &::after {
  421. content: none;
  422. }
  423.  
  424. .channel-item {
  425. margin: 0 !important;
  426. }
  427. }
  428. }
  429.  
  430. /* 收藏夹, 关注 */
  431. #page-fav,
  432. #page-follows {
  433. .col-full {
  434. display: flex !important;
  435.  
  436. .fav-main,
  437. .follow-main {
  438. flex: 1;
  439. }
  440. }
  441.  
  442. .fav-content>.fav-video-list {
  443. margin: 10px;
  444.  
  445. >.small-item {
  446. margin: 10px !important;
  447. }
  448. }
  449. }
  450.  
  451. /* 追番 */
  452. #page-bangumi,
  453. #page-pgc {
  454. .section>.content {
  455. width: initial;
  456.  
  457. .pgc-space-follow-page {
  458. padding-left: 0;
  459. }
  460. }
  461. }`,
  462. search: `/* 搜索页 */
  463. .i_wrapper {
  464. padding: 0 var(--layout-padding) !important;
  465. }`,
  466. read: `/* 阅读页 */
  467. #app {
  468. margin: 0 var(--layout-padding);
  469.  
  470. >.article-detail {
  471. width: auto;
  472.  
  473. .article-up-info {
  474. width: auto;
  475. margin: 0 80px 20px;
  476. }
  477.  
  478. .right-side-bar {
  479. right: 0;
  480. }
  481. }
  482. }`,
  483. opus: `div.opus-detail {
  484. width: initial;
  485. margin: 0 var(--layout-padding);
  486. }
  487.  
  488. .right-sidebar-wrap {
  489. margin-left: 0;
  490. right: 0;
  491. }`,
  492. message: `#message-navbar {
  493. display: none;
  494. }
  495.  
  496. .container {
  497. max-width: none !important;
  498. width: auto !important;
  499. }
  500.  
  501. .space-right-top {
  502. padding-top: 0 !important;
  503. }`,
  504. home: `/* 首页 */
  505. div#i_cecream {
  506. max-width: none;
  507. }
  508.  
  509. .feed-card,
  510. .floor-single-card,
  511. .bili-video-card {
  512. margin-top: 0px !important;
  513. }
  514.  
  515. .palette-button-wrap {
  516. left: initial !important;
  517. right: 30px;
  518. }`,
  519. common: `/* This overrides :root style */
  520. html {
  521. --layout-padding: 30px;
  522. }
  523.  
  524. div.bili-header {
  525. min-width: auto !important;
  526. max-width: none !important;
  527. }
  528.  
  529. /* 搜索栏 */
  530. .center-search-container {
  531. min-width: 0;
  532. }
  533.  
  534. /* 兼容性检测 */
  535. .wb-button-group::before {
  536. content: '内核版本不完全适配脚本,请考虑升级浏览器';
  537. color: red;
  538. }
  539.  
  540. /* 脚本选项 */
  541. #wider-bilibili {
  542. --wb-bg: var(--Wh0, #FFF);
  543. --wb-fg: var(--Ga10, #18191C);
  544. --wb-white: rgb(255, 255, 255);
  545. --wb-blue: 0, 174, 236;
  546. --wb-pink: 255, 102, 153;
  547. --wb-red: 248, 90, 84;
  548.  
  549. position: fixed;
  550. top: 0;
  551. bottom: 0;
  552. height: fit-content;
  553. max-height: 80vh;
  554. left: 0;
  555. right: 0;
  556. width: fit-content;
  557. max-width: 80vw;
  558. z-index: 114514;
  559. padding: 10px;
  560. border-radius: 10px;
  561. margin: auto;
  562. box-sizing: border-box;
  563. overflow: auto;
  564. flex-direction: column;
  565. gap: 10px;
  566.  
  567. outline: 2px solid rgba(var(--wb-blue), 0.8);
  568. outline-offset: 0;
  569. background-color: var(--wb-bg);
  570. color: var(--wb-fg);
  571. font-size: 20px;
  572.  
  573. opacity: 0.9;
  574.  
  575. &:hover {
  576. opacity: 1
  577. }
  578.  
  579. >header {
  580. position: sticky;
  581. z-index: 2;
  582. top: -10px;
  583. display: flex;
  584. justify-content: space-between;
  585. margin: -10px;
  586. background-color: var(--wb-bg);
  587. font-weight: bold;
  588.  
  589. &::before {
  590. content: "Wider Bilibili 选项";
  591. align-self: center;
  592. margin-left: 10px;
  593. margin-right: auto;
  594. }
  595. }
  596.  
  597. .wb-button-group {
  598. display: flex;
  599. margin-bottom: 10px;
  600.  
  601. >* {
  602. height: 100%;
  603. }
  604. }
  605.  
  606. div.wb-button-group::before {
  607. display: none;
  608. }
  609.  
  610. a,
  611. button {
  612. border: none;
  613. padding: 4px;
  614. background: none;
  615. color: var(--wb-fg);
  616. display: flex;
  617. font-size: 16px;
  618. text-wrap: nowrap;
  619. transition: opacity .1s;
  620. cursor: pointer;
  621.  
  622. &:hover {
  623. opacity: 0.75;
  624. }
  625.  
  626. &:active {
  627. opacity: 0.5;
  628. }
  629.  
  630. >svg {
  631. width: 20px;
  632. height: 20px;
  633. fill: currentColor;
  634. fill-rule: evenodd;
  635. clip-rule: evenodd;
  636. }
  637. }
  638.  
  639. #wb-close {
  640. width: fit-content;
  641. height: fit-content;
  642. opacity: 1;
  643.  
  644. &:hover {
  645. background-color: rgb(var(--wb-red));
  646. }
  647.  
  648. &:active {
  649. background-color: rgba(var(--wb-red), 0.75);
  650. }
  651. }
  652.  
  653. >fieldset {
  654. border: none;
  655. border-radius: 10px;
  656. padding: 10px;
  657. margin: 0;
  658. display: grid;
  659. grid-template-columns: 1fr 1fr;
  660. gap: 10px 15px;
  661. background-color: rgba(127, 127, 127, 0.1);
  662.  
  663. &::before {
  664. content: attr(data-title);
  665. border-radius: 4px 4px 0 0;
  666. border-bottom: 2px solid rgba(127, 127, 127, 0.1);
  667. grid-column: 1 / -1;
  668. }
  669. }
  670.  
  671. label {
  672. display: inline-flex;
  673. gap: 10px;
  674. place-items: center;
  675. position: relative;
  676. text-wrap: nowrap;
  677.  
  678. &[data-hint]:hover::before {
  679. position: absolute;
  680. bottom: 110%;
  681. left: 0;
  682. right: 0;
  683. margin: 0 auto;
  684. width: fit-content;
  685. padding: 3px 5px;
  686. border-radius: 5px;
  687. content: attr(data-hint);
  688. font-size: 12px;
  689. background-color: rgb(var(--wb-blue));
  690. color: var(--wb-white);
  691. white-space: pre-line;
  692. }
  693. }
  694.  
  695. input {
  696. box-sizing: content-box;
  697. margin: 0;
  698. padding: 4px;
  699. height: 20px;
  700. font-size: 16px;
  701. transition: .2s;
  702.  
  703. &:hover {
  704. box-shadow: 0 0 8px rgb(var(--wb-blue));
  705. }
  706.  
  707. &[type=checkbox] {
  708. box-sizing: content-box;
  709. border-radius: 20px;
  710. min-width: 40px;
  711. background-color: #ccc;
  712. appearance: none;
  713. cursor: pointer;
  714.  
  715. &::before {
  716. content: "";
  717. position: relative;
  718. display: block;
  719. transition: 0.3s;
  720.  
  721. height: 100%;
  722. aspect-ratio: 1/1;
  723. border-radius: 50%;
  724. background-color: #FFF;
  725. }
  726.  
  727. &:checked {
  728. background-color: rgb(var(--wb-blue));
  729. }
  730.  
  731. &:checked::before {
  732. transform: translateX(20px);
  733. }
  734.  
  735. &:active {
  736. opacity: 0.5;
  737. }
  738. }
  739.  
  740. &[type=number] {
  741. width: 40px;
  742. border: none;
  743. border-radius: 5px;
  744. outline: 2px solid rgb(var(--wb-blue));
  745. background: none;
  746. color: var(--wb-fg);
  747. appearance: textfield;
  748.  
  749. &::-webkit-inner-spin-button {
  750. appearance: none;
  751. }
  752. }
  753. }
  754. }`,
  755. upperNavigation: `/* 导航栏上置 (默认下置) */
  756. :root {
  757. --player-height: calc(100vh - var(--navbar-height));
  758. }
  759.  
  760. #biliMainHeader {
  761. margin-top: 0;
  762. margin-bottom: var(--player-height);
  763. /* 播放器的 z-index 是100000 */
  764. z-index: 114514;
  765. }
  766.  
  767.  
  768. #playerWrap.player-wrap,
  769. #bilibili-player-wrap {
  770. top: var(--navbar-height);
  771. }
  772.  
  773. /* 限制高度上限(非全屏时) */
  774. .bpx-player-container:not(:fullscreen) .bpx-player-video-wrap>video {
  775. max-height: calc(100vh - var(--navbar-height));
  776. }`,
  777. stickyHeader: `#biliMainHeader {
  778. position: sticky;
  779. top: 0;
  780. /* 其他元素 z-index 基本是<100 */
  781. z-index: 100;
  782. }`,
  783. stickyAside: `#app .left {
  784. height: fit-content;
  785. position: sticky;
  786. top: 72px;
  787. }`,
  788. pauseShowControls: `/* 暂停显示控件 */
  789. .bpx-player-container.bpx-state-paused {
  790.  
  791. .bpx-player-top-wrap,
  792. .bpx-player-control-top,
  793. .bpx-player-control-bottom,
  794. .bpx-player-control-mask {
  795. opacity: 1 !important;
  796. visibility: visible !important;
  797. }
  798.  
  799. div.bpx-player-shadow-progress-area {
  800. visibility: hidden !important;
  801. }
  802.  
  803. .bpx-player-pbp {
  804. bottom: 100% !important;
  805. margin-bottom: 5px;
  806. opacity: 1 !important;
  807. left: 0;
  808. right: 0;
  809. width: auto !important;
  810.  
  811. .bpx-player-pbp-pin {
  812. opacity: 1 !important;
  813. }
  814. }
  815. }`,
  816. mini: `/* 小窗 */
  817. .bpx-player-container {
  818. --mini-width: 320px;
  819. /* 最小宽度,以防不可见 */
  820. min-width: 180px;
  821.  
  822. &[data-screen="mini"] {
  823. max-width: var(--mini-width) !important;
  824. width: auto !important;
  825. height: auto !important;
  826.  
  827. /* 以防竖屏视频超出:留出导航栏高度+16px */
  828. .bpx-player-video-wrap>video {
  829. max-height: calc(100vh - 16px - var(--navbar-height));
  830. }
  831. }
  832. }
  833.  
  834. .bpx-player-mini-resizer {
  835. position: absolute;
  836. left: 0;
  837. width: 10px;
  838. height: 100%;
  839. cursor: ew-resize;
  840. }`,
  841. hideControls: `.bpx-player-control-mask,
  842. .bpx-player-control-top,
  843. .bpx-player-control-bottom,
  844. .bpx-player-pbp {
  845. opacity: 0 !important;
  846. }
  847.  
  848. .bpx-player-top-wrap:hover {
  849. opacity: 1 !important;
  850. }
  851.  
  852. .bpx-player-control-wrap:not(:hover) {
  853. .bpx-player-shadow-progress-area {
  854. opacity: 1 !important;
  855. visibility: visible !important;
  856. }
  857. }
  858.  
  859. .bpx-player-control-wrap:hover {
  860.  
  861. .bpx-player-control-mask,
  862. .bpx-player-control-top,
  863. .bpx-player-control-bottom,
  864. .bpx-player-pbp {
  865. opacity: 1 !important;
  866. }
  867. }`,
  868. fixHeight: `.bpx-player-container:not([data-screen="mini"]) .bpx-player-video-wrap>video {
  869. height: 100vh;
  870. }`,
  871. compactControls: `/* 播放器控件 */
  872. .bpx-player-control-bottom {
  873. padding: 0 20px !important;
  874. }
  875.  
  876. .bpx-player-control-bottom>* {
  877. flex: initial !important;
  878. }
  879.  
  880. /* 控件区域 */
  881. .bpx-player-control-bottom-left,
  882. .bpx-player-control-bottom-right {
  883. min-width: auto !important;
  884. gap: 8px;
  885. }
  886.  
  887. /* Bilibili Evolved 自定义控件区域 */
  888. .bpx-player-control-bottom-left>.be-video-control-bar-extend {
  889. gap: 8px;
  890. }
  891.  
  892. /* 减少中间填充空间 */
  893. .bpx-player-control-bottom-center {
  894. flex: 1 !important;
  895. padding: 0 20px !important;
  896. }
  897.  
  898. /* 防止选集/倍速按钮错位 */
  899. .bpx-player-control-bottom-right>.bpx-player-ctrl-btn:hover {
  900. padding: 0;
  901. }
  902.  
  903. /* 所有按钮控件 */
  904. .bpx-player-ctrl-btn {
  905. margin: 0 !important;
  906. width: fit-content !important;
  907. }
  908.  
  909. .bpx-player-ctrl-time {
  910. width: 130px !important;
  911. }
  912.  
  913. /* 时间控件 */
  914. .bpx-player-ctrl-time-seek {
  915. width: 100% !important;
  916. padding: 0 !important;
  917. left: 0 !important;
  918. }
  919.  
  920. .bpx-player-ctrl-time-label {
  921. text-align: center !important;
  922. text-indent: 0 !important;
  923. }
  924.  
  925. /* 弹幕发送框区域 */
  926. .bpx-player-sending-bar {
  927. background-color: transparent !important;
  928. max-width: 90% !important;
  929. }
  930.  
  931. .bpx-player-video-inputbar {
  932. max-width: none !important;
  933. }
  934.  
  935. .bpx-player-video-inputbar-wrap {
  936. width: auto;
  937. }`
  938. };
  939. const waitFor = (loaded, desc = "页面加载", retry = 100, interval = 100) => new Promise((resolve, reject) => {
  940. const intervalID = setInterval((res = loaded()) => {
  941. if (res) {
  942. clearInterval(intervalID);
  943. console.info(`${desc}已加载`);
  944. return resolve(res);
  945. }
  946. if (--retry === 0) {
  947. clearInterval(intervalID);
  948. return reject(new Error(`${desc}加载超时`));
  949. }
  950. if (retry % 10 === 0) {
  951. console.debug(`${desc}等待加载`);
  952. }
  953. }, interval);
  954. });
  955. const observeFor = (className, parent) => new Promise((resolve) => {
  956. const elem = parent.getElementsByClassName(className)[0];
  957. if (elem) {
  958. return resolve(elem);
  959. }
  960. new MutationObserver((mutations, observer) => {
  961. for (const mutation of mutations) {
  962. for (const node of mutation.addedNodes) {
  963. if (node instanceof Element && node.classList.contains(className)) {
  964. observer.disconnect();
  965. return resolve(node);
  966. }
  967. }
  968. }
  969. }).observe(parent, { childList: true });
  970. });
  971. const waitReady = () => new Promise((resolve) => {
  972. document.readyState === "loading" ? window.addEventListener("DOMContentLoaded", () => resolve(), { once: true }) : resolve();
  973. });
  974. const html = `<div id="wider-bilibili" style="display: none;">
  975. <header>
  976. <div class="wb-button-group">
  977. <a target="_blank" href="//greasyfork.org/scripts/474507">
  978. <svg viewBox="0 0 96 96" width="20" height="20" xmlns="http://www.w3.org/2000/svg">
  979. <!-- Based on https://github.com/denilsonsa/denilsonsa.github.io -->
  980. <circle fill="#000" r="48" cy="48" cx="48"/>
  981. <path fill="#000" stroke="#000" stroke-width="4" d="M 44,29 a6.36396,6.36396 0,0,1 0,9 l36,36 a3.25,3.25 0,0,1 -6.5,6.5 l-36,-36 a6.36396,6.36396 0,0,1 -9,0 l-19,-19 a1.76777,1.76777 0,0,1 0,-2.5 l13.0,-13 a1.76777,1.76777 0,0,1 2.5,0 z"/>
  982. <path fill="#fff" d="M 44,29 a6.36396,6.36396 0,0,1 0,9 l36,36 a3.25,3.25 0,0,1 -6.5,6.5 l-36,-36 a6.36396,6.36396 0,0,1 -9,0 l-19,-19 a1.76777,1.76777 0,0,1 2.5,-2.5 l14,14 4,-4 -14,-14 a1.76777,1.76777 0,0,1 2.5,-2.5 l14,14 4,-4 -14,-14 a1.76777,1.76777 0,0,1 2.5,-2.5 z"/>
  983. </svg>
  984. </a>
  985. <a target="_blank" href="//github.com/posthumz/wider-bilibili">
  986. <!-- From https://www.radix-ui.com/icons -->
  987. <svg viewBox="0 0 15 15" width="20" height="20" xmlns="http://www.w3.org/2000/svg"><path d="M7.49933 0.25C3.49635 0.25 0.25 3.49593 0.25 7.50024C0.25 10.703 2.32715 13.4206 5.2081 14.3797C5.57084 14.446 5.70302 14.2222 5.70302 14.0299C5.70302 13.8576 5.69679 13.4019 5.69323 12.797C3.67661 13.235 3.25112 11.825 3.25112 11.825C2.92132 10.9874 2.44599 10.7644 2.44599 10.7644C1.78773 10.3149 2.49584 10.3238 2.49584 10.3238C3.22353 10.375 3.60629 11.0711 3.60629 11.0711C4.25298 12.1788 5.30335 11.8588 5.71638 11.6732C5.78225 11.205 5.96962 10.8854 6.17658 10.7043C4.56675 10.5209 2.87415 9.89918 2.87415 7.12104C2.87415 6.32925 3.15677 5.68257 3.62053 5.17563C3.54576 4.99226 3.29697 4.25521 3.69174 3.25691C3.69174 3.25691 4.30015 3.06196 5.68522 3.99973C6.26337 3.83906 6.8838 3.75895 7.50022 3.75583C8.1162 3.75895 8.73619 3.83906 9.31523 3.99973C10.6994 3.06196 11.3069 3.25691 11.3069 3.25691C11.7026 4.25521 11.4538 4.99226 11.3795 5.17563C11.8441 5.68257 12.1245 6.32925 12.1245 7.12104C12.1245 9.9063 10.4292 10.5192 8.81452 10.6985C9.07444 10.9224 9.30633 11.3648 9.30633 12.0413C9.30633 13.0102 9.29742 13.7922 9.29742 14.0299C9.29742 14.2239 9.42828 14.4496 9.79591 14.3788C12.6746 13.4179 14.75 10.7025 14.75 7.50024C14.75 3.49593 11.5036 0.25 7.49933 0.25Z"></path></svg>
  988. </a>
  989. <button id="wb-close">
  990. <!-- From https://www.radix-ui.com/icons -->
  991. <svg viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg"><path d="M12.8536 2.85355C13.0488 2.65829 13.0488 2.34171 12.8536 2.14645C12.6583 1.95118 12.3417 1.95118 12.1464 2.14645L7.5 6.79289L2.85355 2.14645C2.65829 1.95118 2.34171 1.95118 2.14645 2.14645C1.95118 2.34171 1.95118 2.65829 2.14645 2.85355L6.79289 7.5L2.14645 12.1464C1.95118 12.3417 1.95118 12.6583 2.14645 12.8536C2.34171 13.0488 2.65829 13.0488 2.85355 12.8536L7.5 8.20711L12.1464 12.8536C12.3417 13.0488 12.6583 13.0488 12.8536 12.8536C13.0488 12.6583 13.0488 12.3417 12.8536 12.1464L8.20711 7.5L12.8536 2.85355Z"></path></svg>
  992. </button>
  993. </div>
  994. </header>
  995. <fieldset data-title="通用">
  996. <label><input type="number" min="0">左右边距</label>
  997. </fieldset>
  998. <fieldset data-title="播放页">
  999. <label data-hint="播放器无上下黑边"><input type="checkbox">自动高度</label>
  1000. <label data-hint="试试拉一下小窗左侧?&#10;记录小窗宽度与位置"><input type="checkbox">小窗样式</label>
  1001. <label><input type="checkbox">导航栏下置</label>
  1002. <label><input type="checkbox">粘性导航栏</label>
  1003. <label><input type="checkbox">紧凑控件间距</label>
  1004. <label data-hint="默认检测到鼠标活动显示控件&#10;需要一直显示请打开此选项"><input type="checkbox">暂停显示控件</label>
  1005. <label data-hint="在线人数/弹幕数"><input type="checkbox">显示观看信息</label>
  1006. <label data-hint="默认隐藏控件区&#10;悬浮到相应位置以显示"><input type="checkbox">隐藏控件</label>
  1007. </fieldset>
  1008. <fieldset data-title="动态页">
  1009. <label data-hint="可能导致侧栏显示不全"><input type="checkbox">粘性侧栏</label>
  1010. </fieldset>
  1011. </div>`;
  1012. function styleToggle(s, init = true, flip = false) {
  1013. if (flip) {
  1014. init = !init;
  1015. }
  1016. const style = GM_addStyle(s);
  1017. if (!init) {
  1018. style.disabled = true;
  1019. }
  1020. return flip ? (enable) => {
  1021. style.disabled = enable;
  1022. } : (enable) => {
  1023. style.disabled = !enable;
  1024. };
  1025. }
  1026. function onStyleValueChange(toggle) {
  1027. return (_k, _o, newVal) => toggle(newVal);
  1028. }
  1029. const commonOptions = {
  1030. 左右边距: {
  1031. default_: 30,
  1032. callback: (init) => {
  1033. document.documentElement.style.setProperty("--layout-padding", `${init}px`);
  1034. return (_k, _o, newVal) => document.documentElement.style.setProperty("--layout-padding", `${newVal || 30}px`);
  1035. }
  1036. }
  1037. };
  1038. const videoOptions = {
  1039. 自动高度: {
  1040. // 也就是说,不会有上下黑边
  1041. default_: true,
  1042. callback: (init) => {
  1043. const container = document.getElementsByClassName("bpx-player-container")[0];
  1044. document.documentElement.style.setProperty("--player-height", `${container.clientHeight}px`);
  1045. const observer = new ResizeObserver((entries) => {
  1046. if (container.dataset.screen === "mini") return;
  1047. const { height } = entries[0].contentRect;
  1048. if (height && height <= window.innerHeight)
  1049. document.documentElement.style.setProperty("--player-height", `${height}px`);
  1050. });
  1051. const toggle = styleToggle(styles.fixHeight, init, true);
  1052. init && observer.observe(container);
  1053. return onStyleValueChange((enable) => {
  1054. toggle(enable);
  1055. enable ? observer.observe(container) : observer.disconnect(), document.documentElement.style.removeProperty("--player-height");
  1056. });
  1057. }
  1058. },
  1059. 小窗样式: {
  1060. default_: true,
  1061. callback: (init) => {
  1062. const toggle1 = styleToggle(styles.mini, init);
  1063. const toggle2 = styleToggle(".bpx-player-container{--mini-width:initial}", init, true);
  1064. return onStyleValueChange((enable) => (toggle1(enable), toggle2(enable)));
  1065. }
  1066. },
  1067. 导航栏下置: {
  1068. default_: true,
  1069. callback: (init) => onStyleValueChange(styleToggle(styles.upperNavigation, init, true))
  1070. },
  1071. 粘性导航栏: {
  1072. default_: true,
  1073. callback: (init) => onStyleValueChange(styleToggle(styles.stickyHeader, init))
  1074. },
  1075. 紧凑控件间距: {
  1076. default_: true,
  1077. callback: (init) => onStyleValueChange(styleToggle(styles.compactControls, init))
  1078. },
  1079. 暂停显示控件: {
  1080. default_: false,
  1081. callback: (init) => onStyleValueChange(styleToggle(styles.pauseShowControls, init))
  1082. },
  1083. 显示观看信息: {
  1084. default_: true,
  1085. callback: (init) => onStyleValueChange(styleToggle(".bpx-player-video-info{display:flex!important}", init))
  1086. },
  1087. 隐藏控件: {
  1088. default_: true,
  1089. callback: (init) => onStyleValueChange(styleToggle(styles.hideControls, init))
  1090. }
  1091. };
  1092. const timelineOptions = {
  1093. 粘性侧栏: {
  1094. default_: false,
  1095. callback: (init) => onStyleValueChange(styleToggle(styles.stickyAside, init))
  1096. }
  1097. };
  1098. function listenOptions(options) {
  1099. for (const [name, { default_, callback }] of Object.entries(options))
  1100. GM_addValueChangeListener(name, callback(GM_getValue(name, default_)));
  1101. }
  1102. const optionsFlat = { ...commonOptions, ...videoOptions, ...timelineOptions };
  1103. waitReady().then(() => {
  1104. document.body.insertAdjacentHTML("beforeend", html);
  1105. const app = document.getElementById("wider-bilibili");
  1106. GM_registerMenuCommand("选项", () => {
  1107. app.style.display = "flex";
  1108. });
  1109. document.getElementById("wb-close")?.addEventListener("click", () => {
  1110. app.style.display = "none";
  1111. });
  1112. const modifiers = ["ctrlKey", "altKey", "shiftKey", "metaKey"];
  1113. const comb = [["altKey", "shiftKey"], "W"];
  1114. (function addListener() {
  1115. document.addEventListener("keydown", (ev) => {
  1116. const { key } = ev;
  1117. if (key === comb[1] && modifiers.every((mod) => comb[0].includes(mod) === ev[mod])) {
  1118. ev.stopImmediatePropagation();
  1119. ev.stopPropagation();
  1120. app.style.display = app.style.display === "none" ? "flex" : "none";
  1121. }
  1122. setTimeout(addListener, 250);
  1123. }, { once: true });
  1124. })();
  1125. for (const input of app.getElementsByTagName("input")) {
  1126. const key = input.parentElement?.textContent;
  1127. if (!key) {
  1128. continue;
  1129. }
  1130. const option = optionsFlat[key];
  1131. if (!option) {
  1132. continue;
  1133. }
  1134. switch (input.type) {
  1135. case "checkbox":
  1136. input.checked = GM_getValue(key, option.default_);
  1137. input.onchange = () => GM_setValue(key, input.checked);
  1138. break;
  1139. case "number":
  1140. input.value = GM_getValue(key, option.default_);
  1141. input.oninput = () => {
  1142. const val = Number(input.value);
  1143. Number.isInteger(val) && GM_setValue(key, val);
  1144. };
  1145. break;
  1146. }
  1147. }
  1148. listenOptions(commonOptions);
  1149. }).catch(console.error);
  1150. GM_addStyle(styles.common);
  1151. const url = new URL(window.location.href);
  1152. switch (url.host) {
  1153. case "www.bilibili.com": {
  1154. if (url.pathname === "/") {
  1155. GM_addStyle(styles.home);
  1156. console.info("使用首页宽屏样式");
  1157. break;
  1158. }
  1159. if (url.pathname.startsWith("/read")) {
  1160. GM_addStyle(styles.read);
  1161. console.info("使用阅读页宽屏样式");
  1162. break;
  1163. }
  1164. if (url.pathname.startsWith("/opus")) {
  1165. GM_addStyle(styles.opus);
  1166. break;
  1167. }
  1168. const style = GM_addStyle(styles.video);
  1169. await( waitReady());
  1170. const player = document.getElementById("bilibili-player");
  1171. if (!player) {
  1172. style.remove();
  1173. break;
  1174. }
  1175. const container = await( waitFor(() => player.getElementsByClassName("bpx-player-container")[0], "播放器内容器"));
  1176. listenOptions(videoOptions);
  1177. if (container.getAttribute("data-screen") !== "mini") {
  1178. container.setAttribute("data-screen", "web");
  1179. }
  1180. container.setAttribute = new Proxy(container.setAttribute.bind(container), {
  1181. apply: (target, thisArg, [name, val]) => target.apply(thisArg, [name, name === "data-screen" && val !== "mini" ? "web" : val])
  1182. });
  1183. container.style.setProperty("--mini-width", `${GM_getValue("小窗宽度", 320)}px`);
  1184. GM_addValueChangeListener("小窗宽度", (_k, _o, newVal) => container.style.setProperty("--mini-width", `${newVal}px`));
  1185. GM_addStyle(`.bpx-player-container[data-screen="mini"] {
  1186. translate: ${84 - GM_getValue("小窗右", 52)}px ${48 - GM_getValue("小窗下", 8)}px;
  1187. }`);
  1188. new MutationObserver(() => {
  1189. if (container.dataset.screen != "mini") return;
  1190. if (container.style.right === "84px" && container.style.bottom === "48px") return;
  1191. const { right, bottom } = container.getBoundingClientRect();
  1192. GM_setValue("小窗右", Math.round(window.innerWidth - right));
  1193. GM_setValue("小窗下", Math.round(window.innerHeight - bottom));
  1194. }).observe(container, { attributes: true, attributeFilter: ["style"] });
  1195. const miniResizer = document.createElement("div");
  1196. miniResizer.className = "bpx-player-mini-resizer";
  1197. miniResizer.onmousedown = (ev) => {
  1198. ev.stopImmediatePropagation();
  1199. ev.preventDefault();
  1200. const resize = (ev2) => {
  1201. const miniWidth = Math.max(container.offsetWidth + container.getBoundingClientRect().x - ev2.x + 5, 0);
  1202. GM_setValue("小窗宽度", Math.round(miniWidth));
  1203. };
  1204. if (ev.button !== 0) return;
  1205. document.addEventListener("mousemove", resize);
  1206. document.addEventListener("mouseup", () => document.removeEventListener("mousemove", resize), { once: true });
  1207. };
  1208. const videoArea = container.getElementsByClassName("bpx-player-video-area")[0];
  1209. if (!videoArea) {
  1210. console.error("页面加载错误:视频区域不存在");
  1211. break;
  1212. }
  1213. observeFor("bpx-player-mini-warp", videoArea).then((wrap) => wrap.appendChild(miniResizer)).catch(console.error);
  1214. const sendingBar = player.getElementsByClassName("bpx-player-sending-bar")[0];
  1215. if (!sendingBar) {
  1216. console.error("页面加载错误:发送框不存在");
  1217. break;
  1218. }
  1219. const danmaku = (await( observeFor("bpx-player-video-info", sendingBar))).parentElement;
  1220. const bottomCenter = container.getElementsByClassName("bpx-player-control-bottom-center")[0];
  1221. if (!bottomCenter || !danmaku) {
  1222. console.error("页面加载错误:弹幕框不存在");
  1223. break;
  1224. }
  1225. document.addEventListener("fullscreenchange", () => document.fullscreenElement || bottomCenter.replaceChildren(danmaku));
  1226. bottomCenter.replaceChildren(danmaku);
  1227. const header = document.getElementById("biliMainHeader");
  1228. await( waitFor(() => document.getElementById("nav-searchform"), "搜索框").then(() => {
  1229. observeFor("custom-navbar", document.body).then((nav) => header?.append(nav)).catch(console.error);
  1230. }));
  1231. console.info("宽屏模式成功启用");
  1232. break;
  1233. }
  1234. case "t.bilibili.com":
  1235. GM_addStyle(styles.t);
  1236. listenOptions(timelineOptions);
  1237. waitFor(() => document.getElementsByClassName("right")[0], "右侧栏").then((right) => {
  1238. const left = document.getElementsByClassName("left")[0];
  1239. left.appendChild(right);
  1240. }).catch(console.error);
  1241. console.info("使用动态样式");
  1242. break;
  1243. case "space.bilibili.com":
  1244. GM_addStyle(styles.space);
  1245. console.info("使用空间样式");
  1246. break;
  1247. case "message.bilibili.com":
  1248. GM_addStyle(styles.message);
  1249. console.info("使用通知样式");
  1250. break;
  1251. case "search.bilibili.com":
  1252. GM_addStyle(styles.search);
  1253. console.info("使用搜索页样式");
  1254. break;
  1255. default:
  1256. console.info(`未适配页面,仅启用通用样式: ${url.href}`);
  1257. break;
  1258. }
  1259.  
  1260. })();