前言

在处理主题的热评功能时,偶然的一个想法,能否将热评代码提出来单独做成一个方法,只需要传递一个数组进去就能完美。

经过优化,已经适配双评论系统。

分享

实现代码

function initializeCommentBarrage(array) {
    if (array === []) return;
    let existingBarrage = window.currentBarrage;

    if (existingBarrage) {
        existingBarrage.destroy();
    }

    let barrage = class {
        constructor() {
            this.config = {
                barrageTimer: [],
                barrageList: [],
                barrageIndex: 0,
                dom: document.querySelector(".comment-barrage"),
                maxBarrage: 1,
                barrageTime: 5000
            };
            this.hoverOnCommentBarrage = false;
            this.init();
        }

        filterAndFlatten(comments) {
            return comments.flatMap(comment => comment.replies ? [comment, ...this.filterAndFlatten(comment.replies)] : [comment]);
        }

        sanitizeContent(content) {
            return content.replace(/(<([^>]+)>)/ig, '').trim();
        }

        createBarrageItem(comment) {
            const content = this.sanitizeContent(comment.content);
            if (!content) return false;
            const element = document.createElement("div");
            element.className = "comment-barrage-item";
            element.innerHTML = `<div class="barrageHead"><a class="barrageTitle" href="javascript:sco.scrollTo('post-comment')">弹幕</a><div class="barrageNick">${comment.nick}</div><img class="barrageAvatar" src="https://cravatar.cn/avatar/${comment.mailMd5}"/><a class="comment-barrage-close" href="javascript:sco.switchCommentBarrage();"><i class="solitude st-close-fill"></i></a></div><a class="barrageContent" href="${comment.id ? `javascript:sco.scrollTo(\'${comment.id}\')` : 'javascript:sco.scrollTo(\'post-comment\')'}">${content}</a>`;
            this.config.dom.appendChild(element);
            this.config.barrageTimer.push(element);
            return true;
        }

        removeBarrageItem(element) {
            element.classList.add("out");
            setTimeout(() => this.config.dom.removeChild(element), 1000);
        }

        manageBarrage() {
            if (this.config.barrageList.length && !this.hoverOnCommentBarrage) {
                if (!this.createBarrageItem(this.config.barrageList[this.config.barrageIndex])) {
                    this.config.barrageIndex = (this.config.barrageIndex + 1) % this.config.barrageList.length;
                    return this.manageBarrage();
                }
                this.config.barrageIndex = (this.config.barrageIndex + 1) % this.config.barrageList.length;
            }
            if (this.config.barrageTimer.length > Math.min(this.config.maxBarrage, this.config.barrageList.length) && !this.hoverOnCommentBarrage) {
                this.removeBarrageItem(this.config.barrageTimer.shift());
            }
        }

        async initBarrage() {
            this.config.dom.style.display =  "flex"
            this.config.barrageList = this.filterAndFlatten(array);
            this.config.dom.innerHTML = "";
            clearInterval(this.commentInterval);
            this.commentInterval = setInterval(() => this.manageBarrage(), this.config.barrageTime);
        }

        async init() {
            await this.initBarrage();
            this.config.dom.addEventListener('mouseover', () => this.hoverOnCommentBarrage = true);
            this.config.dom.addEventListener('mouseout', () => this.hoverOnCommentBarrage = false);
        }

        destroy() {
            clearInterval(this.commentInterval);
            this.config.dom.removeEventListener('mouseover', () => this.hoverOnCommentBarrage = true)
            this.config.dom.removeEventListener('mouseout', () => this.hoverOnCommentBarrage = false)
            this.config.dom.innerHTML = ""
        }
    }

    window.currentBarrage = new barrage();
}

此方法会保持常开,如需要切换请自行添加Cookie
此方法中的跳转方法是sco.scrollTo('post-comment'),请自行更改或自行设计。以下是 scrollTo 方法代码

function scrollTo(elementId) {
    const targetElement = document.getElementById(elementId);
    if (targetElement) {
        const targetPosition = targetElement.getBoundingClientRect().top + window.pageYOffset - 80;
        const startPosition = window.pageYOffset;
        const distanceToScroll = targetPosition - startPosition;
        let animationStartTime = null;
        window.requestAnimationFrame((function smoothScroll(currentTime) {
            animationStartTime = animationStartTime || currentTime;
            const elapsedTime = currentTime - animationStartTime;
            const progressRatio = Math.min(elapsedTime / 0, 1);
            const easing = progressRatio < .5 ? 2 * progressRatio * progressRatio : (4 - 2 * progressRatio) * progressRatio - 1;
            window.scrollTo(0, startPosition + distanceToScroll * easing);
            elapsedTime < 600 && window.requestAnimationFrame(smoothScroll);
        }));
    }
}
div.comment-barrage

<div class="comment-barrage"></div>

样式代码

:root {
    --efu-white: #fff;
    --efu-white-op: rgba(255, 255, 255, 0.2);
    --efu-black: #000;
    --efu-black-op: rgba(0, 0, 0, 0.2);
    --efu-none: rgba(0, 0, 0, 0);
    --efu-gray: #999;
    --efu-gray-op: rgba(153, 153, 153, 0.169);
    --efu-cyan: #00bcd4;
    --efu-cyan-op: rgba(0, 188, 212, 0.169);
    --efu-vip: #e5a80d;
    --efu-speed: #57bd6a;
    --efu-main: var(--efu-theme);
    --efu-main-op: var(--efu-theme-op);
    --efu-main-op-deep: var(--efu-theme-op-deep);
    --efu-main-op-light: var(--efu-theme-op-light);
    --efu-main-none: var(--efu-theme-none);
    --efu-shadow-theme: 0 8px 12px -3px var(--efu-theme-op);
    --efu-shadow-blackdeep: 0 2px 16px -3px rgba(0, 0, 0, 0.15);
    --efu-shadow-main: 0 8px 12px -3px var(--efu-main-op);
    --efu-shadow-blue: 0 8px 12px -3px rgba(40, 109, 234, 0.2);
    --efu-shadow-white: 0 8px 12px -3px rgba(255, 255, 255, 0.2);
    --efu-shadow-black: 0 0 12px 4px rgba(0, 0, 0, 0.05);
    --efu-shadow-yellow: 0px 38px 77px -26px rgba(255, 201, 62, 0.12);
    --efu-shadow-red: 0 8px 12px -3px rgba(238, 125, 121, 0.212);
    --efu-shadow-green: 0 8px 12px -3px rgba(135, 238, 121, 0.212);
    --efu-logo-color: linear-gradient(215deg, #4584ff 0%, #cf0db9 100%);
    --efu-snackbar-time: 5s;
    --global-font-size: 14px;
    --global-bg: #fff;
    --hr-border: #97bcfb;
    --hr-before-color: #6ea2f9;
    --search-bg: #f6f8fa;
    --search-input-color: #4c4948;
    --search-result-title: #4c4948;
    --preloader-bg: #37474f;
    --preloader-color: #fff;
    --tab-border-color: #f0f0f0;
    --tab-botton-bg: #f0f0f0;
    --tab-botton-color: #1f2d3d;
    --tab-button-hover-bg: #dcdcdc;
    --tab-button-active-bg: #fff;
    --sidebar-bg: #f6f8fa;
    --btn-hover-color: #ff7242;
    --btn-color: #fff;
    --btn-bg: #307af6;
    --text-bg-hover: #307af6;
    --light-grey: #eee;
    --text-highlight-color: #1f2d3d;
    --blockquote-color: #6a737d;
    --blockquote-bg: rgba(73, 177, 245, 0.1);
    --reward-pop: #f5f5f5;
    --toc-link-color: #666261;
    --card-box-shadow: 0 3px 8px 6px rgba(7, 17, 27, 0.06);
    --card-hover-box-shadow: 0 3px 8px 6px rgba(7, 17, 27, 0.15);
    --offset: 0px;
    --hlscrollbar-bg: #121212;
}

[data-theme=dark] {
    --efu-theme: #5bc167;
    --efu-theme-op: rgba(91, 193, 103, 0.137);
    --efu-theme-op-deep: rgba(91, 193, 103, 0.867);
    --efu-theme-none: '#5BC16700';
    --efu-blue: #0084ff;
    --efu-red: #ff3842;
    --efu-pink: #d44040;
    --efu-green: #3e9f50;
    --efu-purple: #7a60d2;
    --efu-yellow: #ffc93e;
    --efu-yellow-op: rgba(255, 201, 62, 0.188);
    --efu-orange: #ff953e;
    --efu-fontcolor: #f7f7fa;
    --efu-background: #18171d;
    --efu-reverse: #fff;
    --efu-maskbg: rgba(0, 0, 0, 0.6);
    --efu-maskbgdeep: rgba(0, 0, 0, 0.85);
    --efu-hovertext: #0a84ff;
    --efu-ahoverbg: #fff;
    --efu-lighttext: var(--efu-theme);
    --efu-secondtext: #a1a2b8;
    --efu-scrollbar: rgba(200, 200, 223, 0.4);
    --efu-card-btn-bg: #30343f;
    --efu-post-blockquote-bg: #000;
    --efu-post-tabs-bg: #121212;
    --efu-secondbg: #30343f;
    --efu-shadow-nav: 0 5px 20px 0px rgba(28, 28, 28, 0.4);
    --efu-card-bg: #1b1c20;
    --efu-card-bg-op: var(--efu-white-op);
    --efu-card-bg-none: rgba(29, 27, 38, 0);
    --efu-shadow-lightblack: 0 5px 12px -5px rgba(102, 68, 68, 0);
    --efu-shadow-light2black: 0 5px 12px -5px rgba(102, 68, 68, 0);
    --efu-card-border: #3d3d3f;
    --efu-shadow-border: 0 8px 16px -4px rgba(0, 0, 0, 0.314);
    --style-border: 1px solid var(--efu-card-border);
    --style-border-always: 1px solid var(--efu-card-border);
    --style-border-hover: 1px solid var(--efu-theme);
    --style-border-hover-always: 1px solid var(--efu-theme);
    --style-border-dashed: 1px dashed var(--efu-theme-op);
    --style-border-forever: 2px solid var(--efu-lighttext);
    --efu-hl-bg: #1c1e1e;
    --efu-hltools-bg: #454a50;
}

[data-theme=light] {
    --efu-theme: #0e83cd;
    --efu-theme-op: rgba(14, 131, 205, 0.137);
    --efu-theme-op-deep: rgba(14, 131, 205, 0.867);
    --efu-theme-op-light: rgba(66, 89, 239, 0.051);
    --efu-theme-none: '#0E83CD00';
    --efu-blue: #425aef;
    --efu-red: #f04a63;
    --efu-pink: #ff7c7c;
    --efu-green: #57bd6a;
    --efu-yellow: #c28b00;
    --efu-yellow-op: rgba(217, 156, 0, 0.102);
    --efu-orange: #e38100;
    --efu-purple: #7a60d2;
    --efu-fontcolor: #363636;
    --efu-background: #f7f9fe;
    --efu-reverse: #000;
    --efu-maskbg: rgba(255, 255, 255, 0.6);
    --efu-maskbgdeep: rgba(255, 255, 255, 0.85);
    --efu-hovertext: var(--efu-main);
    --efu-ahoverbg: #f7f7fa;
    --efu-lighttext: var(--efu-main);
    --efu-secondtext: rgba(60, 60, 67, 0.8);
    --efu-scrollbar: rgba(60, 60, 67, 0.4);
    --efu-card-btn-bg: #edf0f7;
    --efu-post-blockquote-bg: #fafcff;
    --efu-post-tabs-bg: #f2f5f8;
    --efu-secondbg: #f7f7f9;
    --efu-shadow-nav: 0 5px 12px -5px rgba(102, 68, 68, 0.05);
    --efu-card-bg: #fff;
    --efu-card-bg-op: var(--efu-black-op);
    --efu-card-bg-none: rgba(255, 255, 255, 0);
    --efu-shadow-lightblack: 0 5px 12px -5px rgba(102, 68, 68, 0);
    --efu-shadow-light2black: 0 5px 12px -5px rgba(102, 68, 68, 0);
    --efu-card-border: #e3e8f7;
    --efu-shadow-border: 0 8px 16px -4px rgba(44, 45, 48, 0.047);
    --style-border: 1px solid var(--efu-card-border);
    --style-border-always: 1px solid var(--efu-card-border);
    --style-border-hover: 1px solid var(--efu-main);
    --style-border-hover-always: 1px solid var(--efu-main);
    --style-border-dashed: 1px dashed var(--efu-theme-op);
    --style-border-forever: 2px solid var(--efu-main);
    --efu-navbg: var(--efu-theme-op);
    --efu-hl-bg: #fff;
    --efu-hltools-bg: #e7e7e7;
}

.comment-barrage {
    position: fixed;
    bottom: 0;
    right: 20px;
    padding: 0 0 20px 10px;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    z-index: 999;
    transition: all 0.3s ease 0s;
    user-select: none;
}

@media screen and (max-width: 768px) {
    .comment-barrage {
        display: none !important;
    }
}

.comment-barrage .comment-barrage-item {
    min-width: 300px;
    max-width: 300px;
    width: fit-content;
    min-height: 80px;
    max-height: 150px;
    margin: 4px;
    padding: 8px 14px;
    background: var(--efu-maskbgdeep);
    border-radius: 8px;
    color: var(--efu-fontcolor);
    animation: 0.6s cubic-bezier(0.42, 0, 0.3, 1.11) 0s 1 normal none running barrageIn;
    transition: all 0.3s ease 0s;
    display: flex;
    flex-direction: column;
    border: var(--style-border);
    backdrop-filter: saturate(180%) blur(20px);
    box-shadow: var(--efu-shadow-border);
    overflow: hidden;
}

.comment-barrage .comment-barrage-item:hover {
    border: var(--style-border-hover);
    box-shadow: var(--efu-shadow-main);
}

.comment-barrage .comment-barrage-item.out {
    opacity: 0;
    animation: 0.6s cubic-bezier(0.42, 0, 0.3, 1.11) 0s 1 normal none running barrageOut;
}

.comment-barrage .comment-barrage-item.hovered {
    opacity: 0;
}

.comment-barrage .comment-barrage-item .comment-barrage-close {
    color: var(--efu-secondtext);
    cursor: pointer;
    line-height: 1;
    margin: 4px;
}

.comment-barrage .comment-barrage-item .comment-barrage-close:hover {
    color: var(--efu-main);
}

.comment-barrage .comment-barrage-item .comment-barrage-close .solitude {
    color: var(--efu-fontcolor);
    font-size: 18px;
}

.comment-barrage .comment-barrage-item .barrageHead {
    height: 30px;
    padding: 0px 0px 6px;
    line-height: 30px;
    font-size: 12px;
    border-bottom: var(--style-border);
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-weight: 700;
}

.comment-barrage .comment-barrage-item .barrageHead .barrageTitle {
    color: var(--efu-card-bg);
    margin-right: 8px;
    background: var(--efu-fontcolor);
    line-height: 1;
    padding: 4px;
    border-radius: 4px;
    white-space: nowrap;
}

.comment-barrage .comment-barrage-item .barrageHead .barrageTitle:hover {
    background: var(--efu-main);
    color: var(--efu-white);
}

.comment-barrage .comment-barrage-item .barrageAvatar {
    width: 16px;
    height: 16px;
    margin: 0px 8px 0px auto;
    border-radius: 50%;
    background: var(--efu-secondbg);
}

.comment-barrage .comment-barrage-item .barrageContent {
    height: calc(100% - 30px);
    overflow: hidden;
    width: fit-content;
    max-height: 48px;
    font-size: 14px !important;
    font-weight: 400 !important;
}

.comment-barrage .comment-barrage-item .barrageContent h1, .comment-barrage .comment-barrage-item .barrageContent h2, .comment-barrage .comment-barrage-item .barrageContent h3, .comment-barrage .comment-barrage-item .barrageContent h4 {
    font-size: 14px !important;
    font-weight: 400 !important;
    margin: 8px 0px !important;
}

.comment-barrage .comment-barrage-item .barrageContent a {
    pointer-events: none;
    font-size: 14px !important;
}

.comment-barrage .comment-barrage-item .barrageContent p {
    margin: 8px 0;
    line-height: 1.3;
    overflow: hidden;
    text-overflow: ellipsis;
    -webkit-line-clamp: 2;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    font-size: 14px;
    max-width: 270px;
}

.comment-barrage .comment-barrage-item .barrageContent p img:not(.tk-owo-emotion), .comment-barrage .comment-barrage-item .barrageContent p display none, .comment-barrage .comment-barrage-item .barrageContent p img.tk-owo-emotion {
    width: 16px;
    padding: 0;
    margin: 0;
    transform: translateY(2px);
}

.comment-barrage .comment-barrage-item .barrageContent::-webkit-scrollbar {
    height: 0;
    width: 4px;
}

.comment-barrage .comment-barrage-item .barrageContent::-webkit-scrollbar-button {
    display: none;
}

传入数据

[
    {
        nick: 'Efu',
        content: '这是我的评论',
        mailMd5: '这是我的邮箱MD5',
        id: '这是此条评论的ID(填写后执行跳转)'
    }
]

各类评论获取当前评论的方法

  1. Twikoo
async function barrageTwikoo() {
    await fetch("twikoo envid", {
        method: "POST",
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify({
            event: "COMMENT_GET",
            accessToken: "你的 Twikoo accessToken",
            url: window.location.pathname
        })
    }).then(async res => {
        if (!res.ok) throw new Error("HTTP error! status: " + res.status)
        const data = await res.json();
    }).catch(error => console.error("An error occurred while fetching comments: ", error))
}

展示

弹幕