🏠 Home 

B站|bilibili 分P视频详情页优化

调整bilibili 分P视频合集列表,使得可以根据窗口大小上下铺满,标题显示得更长;适配了宽屏显示;支持小窗大小设置;支持右侧视频列表自定义宽度;支持视频列表标题换行显示;以及一些其他的调整


Install this script?
  1. // ==UserScript==
  2. // @name B站|bilibili 分P视频详情页优化
  3. // @license MIT
  4. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAyZJREFUaEPtWlFy2jAQ3XXof5pewJkpmekpAidJ+IQeAnKIwifkJDin6EzoTNwDlOYA4O2sVIEsFFuSkQdm8CfI0r6VdvftkxHO/MEQ+7/8eH3cJtvsffgtD3nf9s71bNV7H3Yz3/m8AdzMVmMgmPBCBW5um4K4nv1MkTpzBOgBwmQ97D75gPAHMH19A8BULdIEBBufUOdtbzDl69HdbVQActGrZVMQh8bzjkLf9xh57wB7xwYCiQZ/vt8tXLx3LON5rSAATUBwsCYESx1oiOfV+8EAQkAcGk95gTjwPTY6+AMAYhHAByJKEWgXrNVHYx/Uchx9kF7NcXIsAdamY0TICtg8m1mvBODzdLUU6eyEHzPWdgDOwfidX7V6IQDoxemEnV827T8ItKW0cwHBRRQ/8j6fNeY7pwGmkyZEc714ihpANMCb2a85ED2WDfUv6bGB2uoHIC7QGrwBpCo2ABGr0xXp6xBAdgHQhufVGpcdqPK2T5fGqRugkwJscp+G6Gg7wMYKFpMk90BFT6U3V0qtp24ORJESBdeBlypQwQBEsYNPY91Y624gLtbDr4O6uKiv/JLgEW4G+g41A1Bq/ewmsjf/jrr9OgCuvGs96pbIZjAAmYPLvXDJSMQF02Is6LdLVyYpO9wTQU9SdjvNNvvjZgBExS56gEmGRfGyTZjD+wVh1c4cgBLHsaxQNAJQdyza+P8CoA0vV61x2YGQHeCi55KZXOaOvgMmnVAFS5cfFY0IkVKiAmDjCXEu6gHBk6QZWqOEMOE6IceECcNRAezFATZaFSbKuW6YFMSVM5nHKjoAXlAVJPa2ukNQvwnPW8Qpl/N/6chcvRRzXCtHqE0AImFYZRVHXh/TWHNuqwDHmW2f/kqCRV7gtu/T7sUGY22CGEAV1xe6C9ZL37GNl1K/qZpL8e38xV3lPbvEGNu3YfPrrWu557TqpGGLxHrL7LutV0xIMG73poavpA77YiW5sDM4FgugZ5MEOl3yWZVhOJ6Cbc2EjgKzEwBbpmpyNWo7Xqbq4foFgDMARdSuCkqP/aGHnJvFs86DTgJd4sgLgMuEbY/5B0Ybna/xpe4TAAAAAElFTkSuQmCC
  5. // @namespace https://sumver.cn
  6. // @version 1.4.1
  7. // @description 调整bilibili 分P视频合集列表,使得可以根据窗口大小上下铺满,标题显示得更长;适配了宽屏显示;支持小窗大小设置;支持右侧视频列表自定义宽度;支持视频列表标题换行显示;以及一些其他的调整
  8. // @author lonelylizard
  9. // @match https://www.bilibili.com/video/*
  10. // @match https://www.bilibili.com/list/*
  11. // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
  12. // @grant GM_addStyle
  13. // @grant GM_registerMenuCommand
  14. // @grant GM_setValue
  15. // @grant GM_getValue
  16. // @grant GM_listValues
  17. // @grant GM_deleteValue
  18. // @run-at document-end
  19. // ==/UserScript==
  20. // 样式处理
  21. const setStyle = function (css) {
  22. let css_list = css.split("}")
  23. css_list.forEach((item, index, array) => {
  24. GM_addStyle(item + "}")
  25. });
  26. }
  27. // 控制菜单
  28. // 视频列表标题换行开关
  29. const menu_title_wrap_status = GM_registerMenuCommand("3、视频列表标题换行开关", function () {
  30. let cur_choose = window.confirm("使得右侧视频合集支持标题换行显示\n当前状态:\n" + (GM_getValue("title_wrap_status") === true ? "已开启" : "已关闭") +
  31. "\n\n按【确认】修改状态,按【取消】保持设置不变\n\n设置后请手动刷新一次网页");
  32. if (cur_choose) {
  33. if (GM_getValue("title_wrap_status")) {
  34. GM_setValue("title_wrap_status", false)
  35. } else {
  36. GM_setValue("title_wrap_status", true)
  37. }
  38. }
  39. });
  40. // 宽屏适配开关
  41. const menu_widescreen_status = GM_registerMenuCommand("1、宽屏适配开关", function () {
  42. let cur_choose = window.confirm("当前宽屏适配状态:\n" + (GM_getValue("widescreen_status") === true ? "已开启" : "已关闭") +
  43. "\n\n按【确认】修改状态,按【取消】保持设置不变\n注:只有在【视频合集宽度调整】未开启或设置为0时,宽屏模式才会生效\n设置后请手动刷新一次网页\n 开启该功能后,与B站视频播放器右下角的【宽屏模式】【网页全屏】会有冲突导致画面展示有可能混乱,请不要同时使用");
  44. if (cur_choose) {
  45. if (GM_getValue("widescreen_status")) {
  46. GM_setValue("widescreen_status", false)
  47. } else {
  48. GM_setValue("widescreen_status", true)
  49. }
  50. }
  51. });
  52. // 小窗尺寸设置开关
  53. // const miniwin_status = GM_registerMenuCommand("2、小窗尺寸设置", function () {
  54. // window.prompt("小窗功能已从本脚本移除,升级为全站小窗设置脚本,如有需要请手动复制以下的链接前往查看","https://greasyfork.org/zh-CN/scripts/494837")
  55. // });
  56. // 视频合集列表比例调整开关
  57. const menu_area_ratio = GM_registerMenuCommand("4、视频合集列表宽度调整", function () {
  58. let area_ratio_prompt = window.prompt("输入0.5表示视频列表占屏幕一半,输入0.25表示占屏幕1/4,\n当前比率:\n" + (GM_getValue("area_ratio") != 0 && typeof (GM_getValue(
  59. "area_ratio")) != 'undefined' ? GM_getValue("area_ratio") : "未设置") +
  60. "\n\n按【确认】修改状态,按【取消】保持设置不变\n如设置导致页面混乱,请输入0还原页面\n\n注意:该功能开启时,宽屏模式会自动关闭\n设置后请手动刷新一次网页\n 开启该功能后,与B站视频播放器右下角的【宽屏模式】【网页全屏】会有冲突导致画面展示有可能混乱,请不要同时使用");
  61. if (typeof (Number(area_ratio_prompt)) === 'number') {
  62. if (area_ratio_prompt.toString()
  63. .split('.')
  64. .pop()
  65. .length <= 2) {
  66. GM_setValue("area_ratio", area_ratio_prompt)
  67. }
  68. }
  69. });
  70. // 默认开启,去除菜单
  71. // // 非分P视频页支持宽屏、自定义比例开关 (即普通视频播放页)
  72. // const no_videos_list_support = GM_registerMenuCommand("5、让非分P视频页支持宽屏、自定义比例开关", function () {
  73. // let cur_choose = window.confirm("让普通的视频播放页(没有视频合集),也按照宽屏或自定义比例进行显示\n当前状态:\n" + (GM_getValue("no_videos_list_support_status") === true ? "已开启" :
  74. // "已关闭") + "\n\n该功能需要【视频合集列表宽度调整】或【宽屏适配开关】开启状态才生效\n按【确认】修改状态,按【取消】保持设置不变\n设置后请手动刷新一次网页");
  75. // if (cur_choose) {
  76. // if (GM_getValue("no_videos_list_support_status")) {
  77. // GM_setValue("no_videos_list_support_status", false)
  78. // } else {
  79. // GM_setValue("no_videos_list_support_status", true)
  80. // }
  81. // }
  82. // });
  83. // 自定义视频合集列表高度
  84. const menu_right_content_height = GM_registerMenuCommand("6、自定义视频合集列表高度", function () {
  85. let area_height_ratio_prompt = window.prompt("输入0.5表示占列表区域高度占屏幕高度一半,输入0.8表示占屏幕高度80%,\n当前比率:\n" + (GM_getValue("area_height_ratio") != 0 && typeof (GM_getValue(
  86. "area_height_ratio")) != 'undefined' ? GM_getValue("area_height_ratio") : "未设置") +
  87. "\n\n按【确认】修改状态,按【取消】保持设置不变\n如设置导致页面混乱,请输入0还原页面\n\n注意:设置的高度不包含标题,所以假如你希望右侧高度有一半用来显示推荐视频,那么设置值大概为0.3");
  88. if (typeof (Number(area_height_ratio_prompt)) === 'number') {
  89. if (area_height_ratio_prompt.toString()
  90. .split('.')
  91. .pop()
  92. .length <= 2) {
  93. GM_setValue("area_height_ratio", area_height_ratio_prompt)
  94. }
  95. }
  96. });
  97. // 重置脚本,删除所有设置,防止先后版本逻辑错误导致的设置不生效或出现错误的问题
  98. const reset_btn = GM_registerMenuCommand("7、重置脚本全部设置", function () {
  99. let cur_choose = window.confirm("重置该脚本的所有设置,通常只有在脚本运作发生混乱的情况下使用。");
  100. if (cur_choose) {
  101. const keys = GM_listValues();
  102. keys.forEach(element => {
  103. GM_deleteValue(element);
  104. });
  105. }
  106. });
  107. (function () {
  108. 'use strict';
  109. // 屏蔽广告
  110. let no_ad_fn = function () {
  111. let css =
  112. `#slide_ad {
  113. display: none
  114. }
  115. /* 去除右侧广告 */
  116. .ad-report {
  117. display: none !important;
  118. min-width: 0px !important;
  119. min-height: 0px !important
  120. }
  121. /* 去除简介下广告 */
  122. #activity_vote {
  123. display: none !important
  124. }
  125. /* 去除右下角直播窗口 */
  126. .pop-live-small-mode {
  127. display: none !important
  128. }
  129. /* 去除右侧游戏广告卡片 */
  130. .video-page-game-card-small {
  131. display: none !important
  132. }
  133. /* 去除视频下方的广播广告 */
  134. .reply-notice {
  135. display: none !important
  136. }`
  137. setStyle(css)
  138. }
  139. no_ad_fn()
  140. // 创建一个观察器实例并传入回调函数
  141. const observer = new MutationObserver((mutations) => {
  142. const targetElement = document.querySelector('.video-pod__body');
  143. if (targetElement) {
  144. fn1();
  145. // 当找到目标元素后停止观察
  146. // observer.disconnect();
  147. }
  148. });
  149. // 配置观察选项:
  150. const config = {
  151. attributes: false,
  152. childList: true,
  153. subtree: true
  154. };
  155. // 选择需要观察变动的节点
  156. const targetNode = document.body;
  157. // 开始观察目标节点
  158. observer.observe(targetNode, config);
  159. // 开始之前检查元素是否已经存在
  160. const existingElement = document.querySelector('.video-pod__body');
  161. if (existingElement) {
  162. observer.disconnect(); // 如果元素已经存在,则不需要继续观察
  163. }
  164. // 2024-10月B站对页面逻辑进行了改写,现在不需要区分那么多类型的合集了
  165. let fn1 = function () {
  166. if (document.querySelector(".video-pod")) {
  167. if (document.querySelector(".video-pod__body")) {
  168. change_title_wrap("fn1")
  169. let list_height = document.querySelector(".video-pod__list")
  170. .scrollHeight;
  171. let res_height = window.innerHeight;
  172. let right_content_top_heigt = document.querySelector(".video-pod__body")
  173. .offsetTop;
  174. let right_content_head = document.querySelector(".video-pod__header").offsetHeight;
  175. let dif_height = res_height - right_content_top_heigt -80;
  176. // 初始化,如果存在按用户设置的高度值,则优先使用用户设置,否则则给默认值
  177. let list_max_height
  178. if(GM_getValue("area_height_ratio") && GM_getValue("area_height_ratio") != 0){
  179. list_max_height = Math.round(res_height*GM_getValue("area_height_ratio"))
  180. }else{
  181. list_max_height = 1000
  182. }
  183. // 判断小节是否展开
  184. let viewpoint_status = false
  185. if (document.querySelector(".bpx-player-viewpoint")) {
  186. if (document.querySelector(".bpx-player-viewpoint")
  187. .getAttribute('fold') == 'true') {
  188. viewpoint_status = true
  189. }
  190. }
  191. // 没有字幕插件、没有小节,那就正常显示
  192. if (!document.querySelector(".transcript-box") && viewpoint_status == false) {
  193. if (list_height > dif_height) {
  194. // 计算列表高度,如果达不到一屏就不铺满
  195. let css =
  196. `.video-pod__body {
  197. height: ${dif_height}px !important;
  198. max-height: ${list_max_height}px !important
  199. }`
  200. setStyle(css)
  201. } else {
  202. // 如果高度小于一屏,同时开始换行功能,会导致高度不正确,这里修改为去除高度属性,让其自适应
  203. let css =
  204. `.video-pod__body {
  205. height: unset !important;
  206. max-height: ${list_max_height}px !important
  207. }`
  208. setStyle(css)
  209. }
  210. } else {
  211. // 兼容脚本:在侧边显示 Bilibili 视频字幕/文稿(原始版)
  212. // 兼容小节列表
  213. if (list_height > res_height) {
  214. let css =
  215. `.video-pod__body {
  216. height: ${res_height - 280}px !important;
  217. max-height: ${list_max_height}px !important
  218. }`
  219. setStyle(css)
  220. } else {
  221. let css =
  222. `.video-pod__body {
  223. height: unset !important;
  224. max-height: ${list_max_height}px !important
  225. border-width:2px !important
  226. }`
  227. setStyle(css)
  228. }
  229. }
  230. // 兼容 Bilibili Evolved中的黑夜模式,检测到开启了黑夜模式则禁用样式,避免合集字体一片黑
  231. if(!document.querySelector("#dark-mode-important")){
  232. let css =
  233. `
  234. /* 普通视频合集、带分类视频合集 */
  235. .video-pod .video-pod__header .header-top .left .title{
  236. display:unset !important
  237. }
  238. .pod-item.simple:hover{
  239. background: #DCE2E3;
  240. border-radius: 4px !important
  241. }
  242. /*去除蓝色字体*/
  243. .single-p:hover,.title-txt,.single-p:hover .title{
  244. color:#000 !important
  245. }
  246. /* 分P视频合集 */
  247. .simple-base-item.normal:hover{
  248. background: #DCE2E3;
  249. border-radius: 4px !important
  250. }
  251. .simple-base-item.normal:hover{
  252. color:#000 !important
  253. }
  254. /* 带封面的视频合集、带封面且带分类的视频合集 */
  255. .pod-item.normal:hover{
  256. background: #DCE2E3;
  257. border-radius: 4px !important
  258. }
  259. `
  260. setStyle(css)
  261. }
  262. }
  263. }
  264. };
  265. // 宽屏适配+自定义设置比率
  266. let change_right_width = function (source) {
  267. // 如果有自定义比率,则优先使用
  268. if (GM_getValue("area_ratio") && GM_getValue("area_ratio") != 0) {
  269. let body_width = document.querySelector("#app")
  270. .offsetWidth;
  271. let res_width = window.innerWidth;
  272. var dif_width = Math.round(res_width * GM_getValue("area_ratio"));
  273. let player_banner_height = document.querySelector(".bpx-player-sending-bar")
  274. .offsetHeight;
  275. // 播放全部视频合集和普通无合集视频,一起调整
  276. if (document.querySelector(".playlist-container--left")) {
  277. let css =
  278. `
  279. @media (min-width: 1681px) {
  280. .playlist-container .playlist-container--right {
  281. width: ${dif_width}px !important;
  282. }
  283. }
  284. .playlist-container .playlist-container--right{
  285. width: ${dif_width}px !important;
  286. }
  287. `
  288. setStyle(css)
  289. }
  290. // 专栏视频合集
  291. if (document.querySelector(".left-container")) {
  292. let css =
  293. `
  294. @media (min-width: 1681px) {
  295. .video-container-v1 .right-container {
  296. width: ${dif_width}px !important;
  297. }
  298. }
  299. .video-container-v1 .right-container{
  300. width: ${dif_width}px !important;
  301. }
  302. `
  303. setStyle(css)
  304. }
  305. } else if (GM_getValue("widescreen_status")) {
  306. let body_width = document.querySelector("#app")
  307. .offsetWidth;
  308. let res_width = window.innerWidth;
  309. if (res_width - 100 > body_width) {
  310. //带鱼屏
  311. let left_div = document.querySelector(".left-container")
  312. .offsetWidth;
  313. let right_div = document.querySelector(".right-container")
  314. .offsetWidth;
  315. var dif_width = (body_width - (left_div + right_div)) + right_div - 100;
  316. } else {
  317. //非带鱼屏
  318. let left_div = document.querySelector(".left-container")
  319. .offsetWidth;
  320. let right_div = document.querySelector(".right-container")
  321. .offsetWidth;
  322. var dif_width = (res_width - (left_div + right_div)) + right_div - 80;
  323. }
  324. // 没有参数即为普通视频页
  325. if (!source) {
  326. let css = `.right-container {
  327. width: ${dif_width}px !important
  328. }`
  329. setStyle(css)
  330. }
  331. }
  332. }
  333. // 支持自定义视频合集(如UP空间-播放全部、收藏夹-播放全部)
  334. let no_videos_list_change_right_width = function () {
  335. if (document.querySelector(".playlist-container--left")) {
  336. // 计算列表高度,如果达不到一屏就不铺满
  337. let list_height = document.querySelector(".action-list-inner")
  338. .scrollHeight;
  339. let res_height = window.innerHeight;
  340. var right_content_top_heigt = document.querySelector(".action-list-container")
  341. .offsetTop;
  342. var dif_height = res_height - right_content_top_heigt - 10;
  343. if (!document.querySelector(".transcript-box")) {
  344. if (list_height > dif_height) {
  345. let css =
  346. `.action-list-container {
  347. height: ${dif_height}px !important;
  348. max-height: 1000px !important
  349. }
  350. #playlist-video-action-list{
  351. max-height:1000px !important
  352. }
  353. .playlist-video-action-list-body,.action-list-body-bottomaction-list-body-bottom,playlist-video-action-list{
  354. height:100% !important
  355. }`
  356. setStyle(css)
  357. } else {
  358. let css =
  359. `.action-list-container {
  360. height: unset !important;
  361. max-height: 1000px !important
  362. }
  363. #playlist-video-action-list-body,#playlist-video-action-list{
  364. max-height: 1000px !important
  365. }`
  366. setStyle(css)
  367. }
  368. } else {
  369. // 兼容脚本:在侧边显示 Bilibili 视频字幕/文稿(原始版)
  370. if (list_height > res_height) {
  371. let css =
  372. `.action-list-container {
  373. height: ${res_height - 280}px !important;
  374. max-height: 1000px !important
  375. }`
  376. setStyle(css)
  377. } else {
  378. let css =
  379. `.action-list-container {
  380. height: unset !important;
  381. max-height: 1000px !important
  382. }`
  383. setStyle(css)
  384. }
  385. }
  386. let css =
  387. `/* 增加聚焦效果 */
  388. .action-list-item:hover {
  389. background: #DCE2E3;
  390. border-radius: 6px !important;
  391. }
  392. /* 去除蓝色字体提醒 */
  393. .action-list-item:hover .title {
  394. color: #000 !important
  395. }`
  396. setStyle(css)
  397. }
  398. }
  399. // 非视频合集的播放页这次自定义比例、宽屏模式
  400. let support_no_video_list = function () {
  401. if (GM_getValue("no_videos_list_support_status")) {
  402. change_right_width()
  403. }
  404. }
  405. // 视频合集换行功能,不限制标题行数
  406. let change_title_wrap = function (source) {
  407. if (GM_getValue("title_wrap_status")) {
  408. if (source == "fn1") {
  409. let css =
  410. ` .simple-base-item .title{
  411. height:unset !important;
  412. margin:4px
  413. }
  414. .simple-base-item .title .title-txt {
  415. display: block; /* 更改 display 属性以适应自动换行 */
  416. overflow: hidden;
  417. word-break: normal;
  418. line-break: anywhere;
  419. line-height: normal;
  420. white-space: normal; /* 允许自动换行 */
  421. }
  422. `
  423. setStyle(css)
  424. }
  425. }
  426. }
  427. // 小节处理函数
  428. let chapter_dispose = function () {
  429. let res_height = window.innerHeight;
  430. let css =
  431. `
  432. .bpx-player-viewpoint-body{
  433. max-height:${res_height - 280}px;
  434. height:min-content !important
  435. }
  436. li.bpx-player-viewpoint-menu-item:hover{
  437. background: #DCE2E3 !important
  438. }
  439. li.bpx-player-viewpoint-menu-item:hover .bpx-player-viewpoint-menu-item-content{
  440. color: #000 !important
  441. }
  442. .bpx-player-viewpoint-menu-item-content :has(.bpx-player-viewpoint-menu-item-active{
  443. color:#00a1d !important
  444. }
  445. `
  446. setStyle(css)
  447. }
  448. // 小窗处理函数
  449. const mini_win_fn = function () {
  450. // 如果用户使用过小窗,则给予弹窗提醒
  451. let reg1 = new RegExp(".","g")
  452. let version_str = GM_info.script.version.replace(reg1,"");
  453. if(!GM_getValue("mini_status")){
  454. if (GM_getValue("mini_height") != 0 && typeof (GM_getValue("mini_height"))!= 'undefined' && version_str <= 133) {
  455. let num = window.prompt("小窗功能已从本脚本移除,升级为全站小窗设置脚本,如有需要请手动复制以下的链接前往查看","https://greasyfork.org/zh-CN/scripts/494837")
  456. }
  457. GM_setValue("mini_status",true)
  458. }
  459. }
  460. // 超竖屏支持
  461. // 竖屏下,阿B原来的最小宽度适配是width=1080px,但是这会在实际1080P分辨率屏幕下内容向右溢出,此处调整为1000px修复该问题
  462. const support_portrait_fn = function () {
  463. let css = `#mirror-vdcon{
  464. min-width:1000px !important
  465. }`
  466. setStyle(css)
  467. }
  468. // 统一调用入口
  469. let run = function () {
  470. fn1();
  471. change_right_width();
  472. chapter_dispose();
  473. mini_win_fn();
  474. support_no_video_list();
  475. // no_videos_list_change_right_width();
  476. support_portrait_fn()
  477. }
  478. run()
  479. // 窗口大小变化时重新计算
  480. const getWindowInfo = () => {
  481. run()
  482. };
  483. const debounce = (fn, delay) => {
  484. let timer;
  485. return function () {
  486. if (timer) {
  487. clearTimeout(timer);
  488. }
  489. timer = setTimeout(() => {
  490. fn();
  491. }, delay);
  492. }
  493. };
  494. const cancalDebounce = debounce(getWindowInfo, 500);
  495. window.addEventListener('resize', cancalDebounce);
  496. window.addEventListener('pushState', function (e) {
  497. run()
  498. });
  499. window.addEventListener('replaceState', function (e) {
  500. run()
  501. });
  502. // B站视频详情页的自动播放下一个视频,或者点击其他视频,使用的是pushState不会刷新页面,这里需要重写pushState、replaceState为来实现监听页面视频是否切换
  503. const bindEventListener = function (type) {
  504. const historyEvent = history[type];
  505. return function () {
  506. const newEvent = historyEvent.apply(this, arguments);
  507. const e = new Event(type);
  508. e.arguments = arguments;
  509. window.dispatchEvent(e);
  510. return newEvent;
  511. };
  512. };
  513. history.pushState = bindEventListener('pushState');
  514. history.replaceState = bindEventListener('replaceState');
  515. // 浏览器前进、后退时,重新计算
  516. window.onpopstate = function (event) {
  517. run()
  518. };
  519. })();