let nrBarrage = {
layoutEl: null,
listEl: null,
observer: null,
processedSet: new Set(),
rowHeight: 72,
rowCount: 0,
trackLastUsed: [],
initCount: 5,
duration: 12000,
maxOnScreen: 20,
initDelay: 4000,
sleep: (ms) => new Promise(r => setTimeout(r, ms || 1000)),
injectStyle: () => {
if (document.getElementById("nr-barrage-style")) return;
var style = document.createElement("style");
style.id = "nr-barrage-style";
style.textContent = `
#DanmakuLayout {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 30vh;
pointer-events: none;
overflow: hidden;
z-index: 99;
}
#DanmakuLayout .nr-danmaku {
position: absolute;
white-space: nowrap;
will-change: transform;
animation: nr-danmaku-fly var(--nr-dur) linear forwards;
font-size: 30px;
line-height: 1.3;
text-shadow: 2px 2px 4px rgba(0,0,0,.7);
padding: 2px 10px;
border-radius: 6px;
background: rgba(0,0,0,.3);
}
#DanmakuLayout .nr-danmaku * {
margin: 0 !important;
padding: 0 !important;
font-size: inherit !important;
line-height: inherit !important;
letter-spacing: normal !important;
vertical-align: middle !important;
}
#DanmakuLayout .nr-danmaku img {
height: 1em;
width: auto;
}
@keyframes nr-danmaku-fly {
0% { transform: translateX(0); }
100% { transform: translateX(calc(-100% - 100vw)); }
}
`;
document.head.appendChild(style);
},
ensureLayout: () => {
if (!nrBarrage.layoutEl) {
nrBarrage.layoutEl = document.getElementById("DanmakuLayout");
}
if (!nrBarrage.layoutEl) {
nrBarrage.layoutEl = document.createElement("div");
nrBarrage.layoutEl.id = "DanmakuLayout";
document.body.appendChild(nrBarrage.layoutEl);
}
nrBarrage.rowCount = Math.floor(window.innerHeight * 0.5 / nrBarrage.rowHeight);
nrBarrage.trackLastUsed = new Array(nrBarrage.rowCount).fill(0);
},
getTrackTop: () => {
var now = Date.now();
var bestIdx = 0;
var bestTime = Infinity;
for (var i = 0; i < nrBarrage.rowCount; i++) {
if (nrBarrage.trackLastUsed[i] < bestTime) {
bestTime = nrBarrage.trackLastUsed[i];
bestIdx = i;
}
}
nrBarrage.trackLastUsed[bestIdx] = now;
return bestIdx * nrBarrage.rowHeight;
},
evictOldest: () => {
var items = nrBarrage.layoutEl.querySelectorAll(".nr-danmaku");
if (items.length >= nrBarrage.maxOnScreen) {
items[0].remove();
}
},
flyItem: (itemWrapper) => {
if (!itemWrapper) return;
var clone = itemWrapper.cloneNode(true);
var el = document.createElement("div");
el.className = "nr-danmaku";
el.style.setProperty("--nr-dur", nrBarrage.duration + "ms");
el.style.top = nrBarrage.getTrackTop() + "px";
el.style.left = "100vw";
var inner = clone.firstElementChild;
if (inner) {
var found = false;
Array.from(inner.children).forEach(child => {
if (found) return;
if (child.tagName === "SPAN" && child.children.length === 0 && child.textContent.trim()) {
found = true;
return;
}
child.remove();
});
el.appendChild(inner);
} else {
el.innerHTML = clone.innerHTML;
}
nrBarrage.evictOldest();
nrBarrage.layoutEl.appendChild(el);
el.addEventListener("animationend", () => el.remove());
},
scanExisting: () => {
var all = nrBarrage.listEl.querySelectorAll("div[data-index]");
var items = Array.from(all).slice(-nrBarrage.initCount);
items.forEach(item => {
var idx = item.dataset.index;
if (!nrBarrage.processedSet.has(idx)) {
nrBarrage.processedSet.add(idx);
var wrapper = item.querySelector('[class*="webcast-chatroom___item-wrapper"]');
if (wrapper && wrapper.firstElementChild) {
nrBarrage.flyItem(wrapper);
}
}
});
all.forEach(item => nrBarrage.processedSet.add(item.dataset.index));
},
startObserve: () => {
nrBarrage.observer = new MutationObserver((mutations) => {
for (var mutation of mutations) {
mutation.addedNodes.forEach(node => {
if (node.nodeType !== 1) return;
var indexEl = node.dataset && node.dataset.index != null
? node
: node.querySelector && node.querySelector("div[data-index]");
if (indexEl) {
var idx = indexEl.dataset.index;
if (!nrBarrage.processedSet.has(idx)) {
nrBarrage.processedSet.add(idx);
var wrapper = indexEl.querySelector('[class*="webcast-chatroom___item-wrapper"]');
if (wrapper && wrapper.firstElementChild) {
nrBarrage.flyItem(wrapper);
}
}
}
});
}
});
nrBarrage.observer.observe(nrBarrage.listEl, {
childList: true,
subtree: true,
});
},
startCleanup: () => {
setInterval(() => {
if (nrBarrage.processedSet.size > 2000) {
var arr = Array.from(nrBarrage.processedSet);
nrBarrage.processedSet = new Set(arr.slice(-500));
}
}, 60000);
},
init: async () => {
await nrBarrage.sleep(nrBarrage.initDelay);
nrBarrage.injectStyle();
nrBarrage.ensureLayout();
for (var i = 0; i < 15; i++) {
nrBarrage.listEl = document.querySelector('[class*="webcast-chatroom___list"]');
if (nrBarrage.listEl) break;
await nrBarrage.sleep();
}
if (!nrBarrage.listEl) {
console.warn("[nrBarrage] 未找到 webcast-chatroom___list,退出");
return;
}
nrBarrage.layoutEl.innerHTML = "";
nrBarrage.scanExisting();
nrBarrage.startObserve();
nrBarrage.startCleanup();
console.debug("[nrBarrage] 已开启漂屏弹幕,观察 webcast-chatroom___list → #DanmakuLayout");
},
destroy: () => {
if (nrBarrage.observer) {
nrBarrage.observer.disconnect();
nrBarrage.observer = null;
}
if (nrBarrage.layoutEl) {
nrBarrage.layoutEl.innerHTML = "";
}
nrBarrage.processedSet.clear();
console.debug("[nrBarrage] 已销毁");
}
};
nrBarrage.init();