Wider Bilibili

哔哩哔哩宽屏体验

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