您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Youtube 悬浮弹幕,可拖动位置,可调节宽度
当前为
// ==UserScript== // @name Youtube 悬浮弹幕 // @namespace 67373tools // @version 0.1.5 // @description Youtube 悬浮弹幕,可拖动位置,可调节宽度 // @author XiaoMIHongZHaJi // @match https://www.youtube.com/* // @grant GM_registerMenuCommand // @license MIT // ==/UserScript== // ❤️ 广告:欢迎收看陈一发儿直播:https://67373.net // 如果有 bug,在上面网站也可以找到反馈联系方式 // 代码 https://github.dev/67373net/youtube-float-danmu/blob/main/index.js // 测试地址:https://www.youtube.com/watch?v=jfKfPfyJRdk // ✴️ 通用 localStorage.removeItem('danmuParams'); // 清除旧版数据; const videoDoc = parent.document.querySelector('video').ownerDocument; const modes = { "0": '全显示', "1": '短用户名', "2": '无用户名', "3": '全隐藏' }; let configs; getLocal(); function getLocal() { const defaultConfigs = { showMode: 0, fontSize: 15, top: 88, left: 58, maxHeight: 528, width: 528, gap: 3, transparent: 0.58, focusNames: [], highlightNames: [], blockNames: [], isFocusNames: false, isHighlightNames: false, isBlockNames: false, }; const storedConfigs = JSON.parse(localStorage.getItem('danmuConfigs') || '{}'); configs = Object.assign({}, defaultConfigs, storedConfigs); for (let key in configs) { if (!(key in defaultConfigs)) { delete configs[key]; } }; setLocal(); }; function setLocal(params) { localStorage.setItem('danmuConfigs', JSON.stringify(Object.assign(configs, params))); }; GM_registerMenuCommand("重置位置", () => { let defaultPosition = { top: 88, left: 58, maxHeight: 528, width: 528 } setLocal(defaultPosition) let danmuEle = videoDoc.querySelector('#danmu-ele'); danmuEle.querySelector('#danmu-content').style.height = defaultPosition.maxHeight + 'px'; danmuEle.querySelector('#danmu-content').style.maxHeight = defaultPosition.maxHeight + 'px'; danmuEle.querySelector('#danmu-ctrl').style.visibility = 'visible'; for (let i in defaultPosition) defaultPosition[i] = defaultPosition[i] + 'px'; Object.assign(danmuEle.style, defaultPosition); }); function checkHeight(danmuEle) { function childBottom() { let children = danmuEle.querySelectorAll('.danmu-item'); if (children.length == 0) return 0; let child = children[children.length - 1]; return child.getBoundingClientRect().bottom; } const fatherBottom = () => danmuEle.getBoundingClientRect().bottom; // console. log(childBottom() , fatherBottom()); while (childBottom() > fatherBottom() /* || childBottom() > videoDoc.defaultView.innerHeight */) { let children = danmuEle.querySelectorAll('.danmu-item'); if (children.length == 0) break; let isRemove = []; for (let i = 0; i < children.length; i++) { isRemove[i] = children[i].getBoundingClientRect().top <= children[0].getBoundingClientRect().top + 5; } isRemove.map((item, i) => { if (item) children[i].parentNode.removeChild(children[i]); }); } } function setStyle(danmuEle) { let floatDanmuStyle = videoDoc.querySelector('#float-danmu-style'); if (!floatDanmuStyle) { floatDanmuStyle = videoDoc.createElement('style'); floatDanmuStyle.id = 'float-danmu-style'; document.head.appendChild(floatDanmuStyle); } let baseStyle = ` .danmu-highlight { border: solid 1.8px rgba(255, 191, 0, 1.8); box-shadow: inset 0 0 ${configs.gap + configs.fontSize / 2}px rgba(255, 191, 0, 0.8); /* 内发光效果 */ } #danmu-ele { position: absolute; color: white; height: auto; z-index: 67373; top: ${configs.top}px; left: ${configs.left}px; width: ${configs.width}px; } #danmu-ctrl { background-color: rgba(0,0,0,0.5); border: solid white 0.1px; padding: 2.8px; font-size: 12.8px; } #danmu-pop-board { display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: #f0f0f0; padding: 18px; border: 1px solid #ccc; color: black; font-size: 1.58em; } #danmu-content { font-size: ${configs.fontSize}px; max-height: ${configs.maxHeight}px; height: auto; } .danmu-username-long, .danmu-username-short { color: rgb(200,200,200); } .danmu-item { background-color: rgba(0, 0, 0, ${configs.transparent}); border-radius: ${configs.gap / 2.8 + 0.8}px; padding: ${configs.gap}px ${configs.gap * 1.5}px; display: inline-block; margin: ${configs.gap / 5 + 0.5}px; } .danmu-item img { border-radius: 888px; width: ${configs.fontSize * 1.18}px; height: ${configs.fontSize * 1.18}px; margin-right: ${configs.fontSize / 3}px; display: inline; vertical-align: middle; } .danmu-text { color: white; }`; const showMode = modes[configs.showMode]; if (danmuEle) { if (showMode == '全隐藏') { danmuEle.querySelector('#danmu-content').style.display = 'none'; } else { danmuEle.querySelector('#danmu-content').style.display = 'block'; checkHeight(danmuEle); }; } let showModeStyle = ''; switch (showMode) { case '全显示': showModeStyle = ` .danmu-username-long { display: inline !important; } .danmu-username-short { display: none !important; }`; break; case '短用户名': showModeStyle = ` .danmu-username-long { display: none !important; } .danmu-username-short { display: inline !important; }`; break; case '无用户名': showModeStyle = ` .danmu-username-long { display: none !important; } .danmu-username-short { display: none !important; }`; break; }; floatDanmuStyle.textContent = baseStyle + showModeStyle; } // ✴️ 主页面 if (location.href.startsWith('https://www.youtube.com/watch?v=') || location.href.startsWith('https://www.youtube.com/live/')) { setStyle(); // 加 css 到 header danmuEleInit(); function danmuEleInit() { let timer = setInterval(() => { if (document.querySelector('#danmu-ele')) { clearInterval(timer); return; } if (document.querySelector('#chat-container iframe')) { // 检测到iframe try { let danmuEle = getDanmuEle(); danmuEle.danmuurl = videoDoc.URL; document.querySelector('body').appendChild(danmuEle) } catch { }; } }, 888); setTimeout(() => { clearInterval(timer); }, 28888) // 半分钟没检测到iframe,放弃 } // 监听页面跳转事件 (function (history) { const pushState = history.pushState; const replaceState = history.replaceState; function onStateChange(event) { let danmuEle = document.getElementById('danmu-ele'); if (!danmuEle) { danmuEleInit(); } else if (danmuEle.danmuurl != document.URL) danmuEle.parentNode.removeChild(danmuEle); }; window.addEventListener('popstate', onStateChange); window.addEventListener('hashchange', onStateChange); history.pushState = function (state) { const result = pushState.apply(history, arguments); onStateChange({ state }); return result; }; history.replaceState = function (state) { const result = replaceState.apply(history, arguments); onStateChange({ state }); return result; }; const observer = new MutationObserver(() => { if (document.location.href !== observer.lastHref) { observer.lastHref = document.location.href; onStateChange({}); } }); observer.observe(document, { subtree: true, childList: true }); observer.lastHref = document.location.href; })(window.history); }; // ✴️ 弹幕 iframe 页面 if (location.href.startsWith('https://www.youtube.com/live_chat')) { if (document.readyState == "complete" || document.readyState == "loaded" || document.readyState == "interactive") { main(); } else { document.addEventListener("DOMContentLoaded", main); }; function main() { let danmuEle = parent.document.querySelector("#danmu-ele"); let config = { childList: true, subtree: true }; let observer = new MutationObserver(mutations => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (!danmuEle) danmuEle = parent.document.querySelector("#danmu-ele"); if (!danmuEle) return; if (node.nodeType !== 1) return; if (!node.tagName.toLowerCase().match(/yt-live-chat-(text|paid)-message-renderer/)) return; let el = digestYtChatDom(node); if (!el) return; danmuEle.querySelector('#danmu-content').appendChild(el); checkHeight(danmuEle); }); }); }); observeDanmu(); function observeDanmu() { let timer = setInterval(() => { // let ytbChatEle = document.querySelector('#item-offset'); let ytbChatEle = document.querySelector('#contents.style-scope.yt-live-chat-app'); if (!ytbChatEle) return; clearInterval(timer); observer.observe(ytbChatEle, config); }, 888); } }; }; // 获取聊天内容 小米(改 // let lastUserName; const hasName = (name, arr) => arr.map(a => a.toLowerCase()).includes(name); function digestYtChatDom(dom) { const userPhotoElement = dom.querySelector("#author-photo #img"); const userphoto = userPhotoElement ? userPhotoElement.outerHTML : ''; const contentElement = dom.querySelector("#message"); const content = contentElement ? contentElement.innerHTML : ''; let usernameElement = dom.querySelector("#author-name"); let username = usernameElement ? usernameElement.innerHTML : ''; if (!username) return; if (configs.isFocusNames && !hasName(username, configs.focusNames)) return; if (configs.isBlockNames && hasName(username, configs.blockNames)) return; if (username && username.indexOf("<") > -1) { username = username.substring(0, username.indexOf("<")).trim(); } // if (lastUserName == username) return; // lastUserName = username; let el = videoDoc.createElement('div'); el.className = 'danmu-item'; if (configs.isHighlightNames && hasName(username, configs.highlightNames)) el.className += ' danmu-highlight'; let color = ''; if (dom.querySelector("#card") && dom.querySelector("#purchase-amount")) { username = "(SC) " + username; color = getComputedStyle(dom).getPropertyValue("--yt-live-chat-paid-message-primary-color"); color = `style="color: ${color}"`; } el.innerHTML += `${userphoto}`; let separator = content ? ':' : ''; el.innerHTML += `<span class="danmu-username-long" ${color}>${username}<span class="danmu-badge">` + `</span>${separator}</span>`; el.innerHTML += `<span class="danmu-username-short" ${color}>${username.substring(0, 1)}<span class="danmu-badge">` + `</span>${separator}</span>`; el.innerHTML += `<span class="danmu-text" ${color}>${content}</span>`; setTimeout(() => { if (el.querySelector('img').src.startsWith('data')) { el.querySelector('img').src = dom.querySelector("#author-photo #img").src; } try { let badge = dom.querySelector("yt-icon div"); let path = badge.querySelector('path'); if (path.getAttribute('d').startsWith('M9.64589146,7.05569719')) { badge.style.width = '1em'; badge.style.display = 'inline-block'; badge.style.color = 'lightyellow'; el.querySelector('.danmu-badge').appendChild(badge); } } catch (e) { } }, 88) return el; }; // ✴️ 初始化 function eleRefresh(danmuEle, ifTextRefresh) { danmuEle = danmuEle || videoDoc.querySelector('#danmu-ele'); if (!danmuEle) return; danmuEle.querySelector('#danmu-setting-status').innerText = `${configs.isFocusNames ? '✅' : '❌'}` + `${configs.isHighlightNames ? '✅' : '❌'}${configs.isBlockNames ? '✅' : '❌'}`; danmuEle.querySelector('#show-mode').innerText = modes[configs.showMode]; danmuEle.querySelector('#danmu-fontsize').innerText = `字号${configs.fontSize}`; danmuEle.querySelector('#danmu-gap').innerText = `间距${configs.gap}`; danmuEle.querySelector('#danmu-transparent').innerText = `透明${configs.transparent}`; danmuEle.querySelector('#danmu-height').innerText = `高度${configs.maxHeight}`; danmuEle.querySelector('#danmu-is-focus-names').checked = configs.isFocusNames; danmuEle.querySelector('#danmu-is-highlight-names').checked = configs.isHighlightNames; danmuEle.querySelector('#danmu-is-block-names').checked = configs.isBlockNames; setStyle(danmuEle); if (ifTextRefresh) textRefresh(danmuEle); }; function textRefresh(danmuEle) { danmuEle.querySelector('#danmu-focus-names').value = configs.focusNames.join('\n'); danmuEle.querySelector('#danmu-highlight-names').value = configs.highlightNames.join('\n'); danmuEle.querySelector('#danmu-block-names').value = configs.blockNames.join('\n'); } // 建立基本元素 function getDanmuEle() { let danmuEle = document.createElement('div') danmuEle.id = 'danmu-ele'; danmuEle.innerHTML = ` <div id="danmu-ctrl" > <button id="danmu-settings">设置</button> <button id="show-mode"></button> <span id="danmu-setting-status"></span> </div> <div id="danmu-content"></div> <div id="danmu-pop-board"> <span style="white-space: nowrap;"> <span id="danmu-fontsize"></span> <button id="danmu-fontsize-add">+</button> <button id="danmu-fontsize-minus">-</button> </span> <span style="white-space: nowrap;"> <span id="danmu-gap"></span> <button id="danmu-gap-add">+</button> <button id="danmu-gap-minus">-</button> </span> <span style="white-space: nowrap;"> <span id="danmu-transparent"></span> <button id="danmu-transparent-add">+</button> <button id="danmu-transparent-minus">-</button> </span> <span style="white-space: nowrap;"> <span id="danmu-height"></span> <button id="danmu-height-add">+</button> <button id="danmu-height-minus">-</button> </span> <div style="margin:0.28em 0"> <input type="checkbox" id="danmu-is-focus-names"> 关注模式:只显示这些用户名的弹幕。每行一个。 </div> <textarea id="danmu-focus-names" style="width: 100%; height: 128px"></textarea> <div style="margin:0.28em 0"> <input type="checkbox" id="danmu-is-highlight-names"> 高亮模式:这些用户名会高亮。 </div> <textarea id="danmu-highlight-names" style="width: 100%; height: 128px"></textarea> <div style="margin:0.28em 0"> <input type="checkbox" id="danmu-is-block-names"> 屏蔽模式:这些用户名会被屏蔽。 </div> <textarea id="danmu-block-names" style="width: 100%; height: 128px"></textarea> <div style="height:0.5em"></div> <button id="danmu-pop-board-cancel">取消</button> <button id="danmu-pop-board-submit">确定</button> </div>`; eleRefresh(danmuEle); let danmuContentEl = danmuEle.querySelector('#danmu-content'); // 移入移出显示 let isMouseIn; danmuEle.addEventListener('mouseenter', () => { isMouseIn = true; danmuEle.querySelector('#danmu-ctrl').style.visibility = 'visible'; // danmuContentEl.style.border = 'white solid 1px'; // danmuContentEl.style.borderRight = '8.8px dashed white'; // danmuContentEl.style.borderBottom = '8.8px dashed white'; function addStripedBorder(element) { element.style.borderRight = '8px solid black'; element.style.borderBottom = '8px solid black'; element.style.borderLeft = '8px solid white'; element.style.borderImage = 'repeating-linear-gradient(45deg, black, black 5px, white 5px, white 10px) 1'; } addStripedBorder(danmuContentEl); danmuContentEl.style.height = `${configs.maxHeight - 1}px`; }); danmuEle.addEventListener('mouseleave', () => { isMouseIn = false; setTimeout(() => { if (!isMouseIn) { danmuEle.querySelector('#danmu-ctrl').style.visibility = 'hidden'; danmuContentEl.style.borderLeft = ''; danmuContentEl.style.borderRight = ''; danmuContentEl.style.borderBottom = ''; danmuContentEl.style.borderImage = ''; danmuContentEl.style.height = 'auto'; } }, 158) }); // 阻断点击事件穿透 屏蔽 danmuEle.querySelector('#danmu-ctrl').addEventListener('click', event => event.stopPropagation()); danmuEle.querySelector('#danmu-ctrl').addEventListener('dblclick', event => event.stopPropagation()); // 控制功能 - 字号大小 function fontSizeChange(change) { setLocal({ fontSize: Math.max(0, configs.fontSize + change) }); eleRefresh(danmuEle); }; danmuEle.querySelector('#danmu-fontsize-add').addEventListener('click', e => fontSizeChange(1)); danmuEle.querySelector('#danmu-fontsize-minus').addEventListener('click', e => fontSizeChange(-1)); // 控制功能 - 间距大小 function gapChange(change) { setLocal({ gap: configs.gap + change }); eleRefresh(danmuEle); }; danmuEle.querySelector('#danmu-gap-add').addEventListener('click', e => gapChange(1)); danmuEle.querySelector('#danmu-gap-minus').addEventListener('click', e => gapChange(-1)); // 控制功能 - 透明度 let transparentTimerI, transparentTimerT; function transparentChange(change) { change = Math.round(100 * (configs.transparent + change)) / 100; setLocal({ transparent: change }); eleRefresh(danmuEle); }; function transparentMouseDown(change) { transparentChange(change); transparentTimerT = setTimeout(() => { transparentTimerI = setInterval(() => { transparentChange(change); }, 88) }, 888); videoDoc.addEventListener('mouseup', transparentMouseStop); }; function transparentMouseStop() { clearInterval(transparentTimerI); clearTimeout(transparentTimerT); videoDoc.removeEventListener('mouseup', transparentMouseStop); } danmuEle.querySelector('#danmu-transparent-add').addEventListener('mousedown', e => transparentMouseDown(0.01)); danmuEle.querySelector('#danmu-transparent-minus').addEventListener('mousedown', e => transparentMouseDown(-0.01)); // 控制功能 - 高度 function setHeight(num) { setLocal({ maxHeight: Math.max(0, configs.maxHeight + num) }); danmuContentEl.style.height = `${configs.maxHeight - 1}px`; danmuContentEl.style.maxHeight = `${configs.maxHeight}px`; eleRefresh(danmuEle); checkHeight(danmuEle); } danmuEle.querySelector('#danmu-height-add').addEventListener('click', e => setHeight(18)); danmuEle.querySelector('#danmu-height-minus').addEventListener('click', e => setHeight(-18)); // 显示模式切换 danmuEle.querySelector('#show-mode').addEventListener('click', () => { setLocal({ showMode: (configs.showMode + 1) % Object.keys(modes).length }); danmuEle.querySelector('#show-mode').innerText = modes[configs.showMode]; eleRefresh(danmuEle); }); // 用户筛选相关功能 function settingSubmit() { setLocal({ focusNames: danmuEle.querySelector('#danmu-focus-names').value.split('\n').filter(item => item.trim()), highlightNames: danmuEle.querySelector('#danmu-highlight-names').value.split('\n').filter(item => item.trim()), blockNames: danmuEle.querySelector('#danmu-block-names').value.split('\n').filter(item => item.trim()), }); danmuEle.querySelector('#danmu-pop-board').style.display = 'none'; }; function settingCancel() { let namesChanged = false; if (danmuEle.querySelector('#danmu-focus-names').value.split('\n').filter(item => item.trim()) != configs.focusNames) namesChanged = true; if (danmuEle.querySelector('#danmu-highlight-names').value.split('\n').filter(item => item.trim()) != configs.highlightNames) namesChanged = true; if (danmuEle.querySelector('#danmu-block-names').value.split('\n').filter(item => item.trim()) != configs.blockNames) namesChanged = true; if (namesChanged) { if (confirm('名字列表有修改,是否丢弃这些修改?')) { danmuEle.querySelector('#danmu-pop-board').style.display = 'none'; eleRefresh(danmuEle); } else return; } else { danmuEle.querySelector('#danmu-pop-board').style.display = 'none'; eleRefresh(danmuEle); } }; danmuEle.querySelector('#danmu-settings').addEventListener('click', () => { if (danmuEle.querySelector('#danmu-pop-board').style.display == 'block') { settingCancel(); } else { eleRefresh(danmuEle, true); danmuEle.querySelector('#danmu-pop-board').style.display = 'block'; }; }); danmuEle.querySelector('#danmu-pop-board-cancel').addEventListener('click', () => { settingCancel() }); danmuEle.querySelector('#danmu-pop-board-submit').addEventListener('click', e => settingSubmit()); danmuEle.querySelector('#danmu-is-focus-names').addEventListener('change', event => { setLocal({ isFocusNames: event.target.checked }); eleRefresh(danmuEle); }); danmuEle.querySelector('#danmu-is-highlight-names').addEventListener('change', event => { setLocal({ isHighlightNames: event.target.checked }); eleRefresh(danmuEle); }); danmuEle.querySelector('#danmu-is-block-names').addEventListener('change', event => { setLocal({ isBlockNames: event.target.checked }); eleRefresh(danmuEle); }); // 鼠标边缘箭头 let mouseStatus = { width: 0, height: 0, left: 0 }; danmuContentEl.addEventListener('mousemove', function (event) { const rect = danmuContentEl.getBoundingClientRect(); const offset = 10; if (event.clientX <= rect.right && event.clientX >= rect.right - offset && event.clientY <= rect.bottom && event.clientY >= rect.bottom - offset) { danmuContentEl.style.cursor = 'nwse-resize'; // 右下 mouseStatus = { width: 1, height: 1, left: 0 }; } else if (event.clientX >= rect.left && event.clientX <= rect.left + offset && event.clientY <= rect.bottom && event.clientY >= rect.bottom - offset) { danmuContentEl.style.cursor = 'nesw-resize'; // 左下 mouseStatus = { width: -1, height: 1, left: 1 }; } else if (event.clientX >= rect.left && event.clientX <= rect.left + offset) { danmuContentEl.style.cursor = 'ew-resize'; // 左 mouseStatus = { width: -1, height: 0, left: 1 }; } else if (event.clientX <= rect.right && event.clientX >= rect.right - offset) { danmuContentEl.style.cursor = 'ew-resize'; // 右 mouseStatus = { width: 1, height: 0, left: 0 }; } else if (event.clientY <= rect.bottom && event.clientY >= rect.bottom - offset) { danmuContentEl.style.cursor = 'ns-resize'; // 下 mouseStatus = { width: 0, height: 1, left: 0 }; } else { danmuContentEl.style.cursor = 'default'; // 默认箭头 mouseStatus = { width: 0, height: 0, left: 0 }; } }); // 边缘拖拽 danmuContentEl.addEventListener('mousedown', function (event) { event.stopPropagation(); let doc = event.target.ownerDocument; let x = event.clientX; let y = event.clientY; let width = danmuEle.offsetWidth; let height = danmuContentEl.offsetHeight; let left = danmuEle.offsetLeft; let mouse = JSON.parse(JSON.stringify(mouseStatus)); function doDrag(e) { e.stopPropagation(); danmuEle.style.width = width + mouse.width * (e.clientX - x) + 'px'; danmuContentEl.style.height = height + mouse.height * (e.clientY - y) + 'px'; danmuContentEl.style.maxHeight = height + mouse.height * (e.clientY - y) + 'px'; danmuEle.style.left = left + mouse.left * (e.clientX - x) + 'px'; }; function stopDrag(e) { mouseStatus = { width: 0, height: 0, left: 0 }; e.stopPropagation(); videoDoc.body.style.userSelect = ''; videoDoc.body.style.webkitUserSelect = ''; videoDoc.body.style.msUserSelect = ''; videoDoc.body.style.mozUserSelect = ''; setLocal({ width: danmuContentEl.offsetWidth, maxHeight: danmuContentEl.offsetHeight, left: danmuEle.offsetLeft }); eleRefresh(danmuEle); doc.removeEventListener('mousemove', doDrag); doc.removeEventListener('mouseup', stopDrag); }; if (mouseStatus.width || mouseStatus.height) { videoDoc.body.style.userSelect = 'none'; videoDoc.body.style.webkitUserSelect = 'none'; videoDoc.body.style.msUserSelect = 'none'; videoDoc.body.style.mozUserSelect = 'none'; doc.addEventListener('mousemove', doDrag); doc.addEventListener('mouseup', stopDrag); }; }); // 整体拖拽 danmuEle.querySelector('#danmu-ctrl').style.cursor = 'grab'; danmuEle.querySelector('#danmu-ctrl').addEventListener('mousedown', drag); function drag(e) { let doc = e.target.ownerDocument; e.stopPropagation(); e.preventDefault(); let shiftX = e.clientX - danmuEle.getBoundingClientRect().left; let shiftY = e.clientY - danmuEle.getBoundingClientRect().top; function moveAt(pageX, pageY) { danmuEle.querySelector('#danmu-ctrl').style.visibility = 'visible'; configs.top = pageY - shiftY; configs.left = pageX - shiftX; danmuEle.style.top = configs.top + 'px'; danmuEle.style.left = configs.left + 'px'; } function onMouseMove(event) { moveAt(event.pageX, event.pageY); } doc.addEventListener('mousemove', onMouseMove); doc.addEventListener('mouseup', function () { setLocal(); doc.removeEventListener('mousemove', onMouseMove); doc.onmouseup = null; }, { once: true }); } return danmuEle; }; // console. log('YouTube 悬浮弹幕'); // 边缘测试: // iframe重新加载时,会不会清空 // 从直播跳到视频时,会不会清空