文章目錄
- 前言
- 需求分析
- 實施
- 觀察頁面起始渲染
- 編碼
- 效果展示
- 總結
前言
新手上路,歡迎指導
需求分析
想要一個簡約干凈的界面,需要去除推薦欄和廣告部分.
想要自由調節視頻播放速率,需要在視頻控制欄加一個輸入框控制視頻倍速
實施
觀察頁面起始渲染
因為要使用MutationObserver監控元素,所以需要確認頁面開始渲染了哪些東西
- 打個斷點:
- 刷新:
經觀察(需要動手看結果)發現,頁面基本框架已經渲染完成了,側邊的推薦和廣告,頂部左側的導航欄都有了.
缺少中間的搜索框,視頻控件,底部評論
這里怎么清理全靠喜好:
左上導航欄不喜歡,留一個首頁,
右側廣告和推薦都不喜歡,去除不要
編碼
清理的基本思路就是找到不要的元素的類名或id,創建個樣式直接display: none !important
,有要保留的就父元素display: none
再把個別要保留的部分換回原來的顯示屬性,不行就不要的一一display: none !important
插入搜索框拿到值改下video的playbackRate即可
- 準備一個插入css的函數.
function addNewStyle(newStyle) {let styleElement = document.getElementById('mystyles');if (!styleElement) {styleElement = document.createElement('style');styleElement.type = 'text/css';styleElement.id = 'mystyles';document.getElementsByTagName('head')[0].appendChild(styleElement);}styleElement.appendChild(document.createTextNode(newStyle));
}
- 預定義全局樣式
const cssVars = {hide_force: 'display: none !important',hide: 'display: none',show_as_item: 'display: list-item'
};
- 在window加載時加入邏輯監聽body插入元素時去掉左上導航除圖標首頁以外的部分,就完成了.
不太放心又加了個定時器來去除之前遇到的其他廣告.
評論區是一個單獨的組件,封裝在shadow DOM里,所以直接用定時器解決了.
//二次初始化頁面
const observerInto = new MutationObserver(function () {const cssConfig = {hiddenList: {selectors: ['.ad-report', '.recommend-list-v1', '.slide-ad-exp'],props: cssVars.hide_force},headerCleanup: {selectors: ['#biliMainHeader .left-entry li'],props: cssVars.hide},headerRetain: {selectors: ['#biliMainHeader .left-entry li:first-child'],props: cssVars.show_as_item,}};addNewStyle(compileCSS(cssConfig))observerInto.disconnect()
})
observerInto.observe(document.body, {childList: true, subtree: true})
//去除其他廣告
setTimeout(function () {const cssConfig = {hiddenAds: {selectors: ['.vcd', '.video-card-ad-small', '.activity-m-v1', '.act-end'],props: cssVars.hide_force}};addNewStyle(compileCSS(cssConfig));}, 2000);
//去除評論區notice公告
setTimeout(function () {const shadowHost = document.querySelector('bili-comments').shadowRoot.querySelector('bili-comments-header-renderer');// 獲取 Shadow Rootconst shadowRoot = shadowHost.shadowRoot;// 如果 Shadow DOM 是開放的(open),可以直接訪問if (shadowRoot) {// 在 Shadow DOM 中查找元素const noticeElement = shadowRoot.querySelector('#notice');console.log(noticeElement)if (noticeElement) {noticeElement.setAttribute('style', 'display: none !important');}} else {console.log('Shadow DOM 是封閉的(closed),無法訪問');}
}, 6000);
- 監聽搜索框值的變化,改成喜歡的值(需要注意,點擊搜索還是搜索原來的內容)
const observerSearchBar = new MutationObserver(function () {const input = document.querySelector('#nav-searchform input')if (input && input.placeholder && input.placeholder !== '好好學習,天天向上') {input.placeholder = '好好學習,天天向上'input.title = '好好學習,天天向上'}
})
const centerSearchContainer = document.querySelector('#biliMainHeader');
observerSearchBar.observe(centerSearchContainer, {subtree: true,attributes: true,attributeFilter: ['placeholder']
})
- 監聽視頻控件渲染,插入自定義輸入框
const playerContralBottom = document.querySelector(".bpx-player-control-bottom-right");
const observerContralBottom = new MutationObserver(function () {if (playerContralBottom.children.length >= 2) {const input = document.createElement('input');Object.assign(input, {type: 'text',maxLength: 4,placeholder: '?? 播放倍率',style: `
width: 72px;
height: 24px;
padding: 2px 0 2px 4px;
font-size: 12px;
font-weight: bold;
background: linear-gradient(45deg, #f3ec78, #af4261);
border: 1px solid #fff;
border-radius: 15px;
box-shadow: 0 4px 15px rgba(175,66,97,0.3);
color: #fff;
text-shadow: 1px 1px 2px rgba(0,0,0,0.3);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
`});input.addEventListener('focus', () => {input.style.transform = 'scale(1.05)';input.style.boxShadow = '0 8px 25px rgba(175,66,97,0.4)';input.style.borderColor = '#ffd700';});input.addEventListener('blur', () => {input.style.transform = 'scale(1)';input.style.boxShadow = '0 4px 15px rgba(175,66,97,0.3)';input.style.borderColor = '#fff';});
//終止監聽,避免插入監聽死循環observerContralBottom.disconnect()playerContralBottom.insertBefore(input, playerContralBottom.children[1]);input.addEventListener('input', function () {// 輸入警告效果if (this.value.length === 4) {this.style.background = 'linear-gradient(45deg, #af4261, #f3ec78)';setTimeout(() => {this.style.transform = 'translateX(5px)';setTimeout(() => {this.style.transform = 'translateX(-5px)'}, 50);}, 0);} else {this.style.transform = 'scale(1.05)';this.style.background = 'linear-gradient(45deg, #f3ec78, #af4261)';}});//核心邏輯在這里input.addEventListener('keydown', function (event) {if (event.key === 'Enter') {//console.log('你按下了回車鍵,當前輸入的值是:', this.value);const regex = /\d+(\.\d+)?/if (!regex.test(this.value)) {alert("輸入值非法");this.value = ''return;}const video = document.querySelector("video");let rate = parseFloat(this.value).toFixed(2);rate = rate < 0.1 ? (() => {alert("不能低于0.1倍速!已調整為0.1")return 0.1})() : rate > 16 ? (() => {alert("不能高于16倍速!已調整為16")return 16})() : ratevideo.playbackRate = ratethis.value = ''}});}
})
observerContralBottom.observe(playerContralBottom, {childList: true, subtree: true})
效果展示
總結
主要就用到MutationObserver和定時器,以及video.playbackRate
輸入框那一大段基本是deepseek生成的,加了些樣式和動畫.
{childList: true, subtree: true,attributes: true, attributeFilter: ['placeholder']}
用到了四個選項
- 監聽自身或子節點插入
- 監聽整個子樹
- 監聽屬性變化
- 要監聽的屬性值(默認不加全監聽)