
Giscus 在 Astro 中自动更换主题颜色
在构建现代博客时,添加评论系统是提高用户互动的重要方式。Giscus 是一个基于 GitHub Discussions 的评论系统,它完全开源、无广告,并且可以很好地集成到静态网站中。然而,当我们的网站支持深色和浅色主题切换时,评论区的主题也应该相应变化,以保持整体视觉体验的一致性。
本文将介绍如何在 Astro 框架中实现 Giscus 评论系统的自动主题切换功能。
为什么选择 Giscus?
与其他评论系统相比,Giscus 具有以下优势:
- 基于 GitHub Discussions:所有评论都存储在 GitHub 上,无需额外数据库
- 完全开源:代码透明,可自由定制
- 无广告:提供干净的用户体验
- 支持多种主题:包括浅色和深色模式
- 支持 Markdown:评论支持 Markdown 格式
实现步骤
1. 创建 Giscus 组件
首先,我们需要在 Astro 项目中创建一个 Giscus 组件。这个组件将负责加载 Giscus 脚本并处理主题切换。
创建文件 src/components/comments/Giscus.astro
:
---import I18nKey from "../../i18n/i18nKey";import { i18n } from "../../i18n/translation";---
<div class="card-base p-6 md:p-8 mb-4 w-full onload-animation"> <h2 class="text-2xl font-bold mb-6 text-black/90 dark:text-white/90">{i18n(I18nKey.comments)}</h2>
<div id="giscus-container" class="giscus-wrapper"> <!-- 在这里我们将动态插入 Giscus 脚本 --> </div></div>
<script is:inline> // 立即执行函数,确保尽早运行 (function() { // 检测当前主题模式并返回对应的 Giscus 主题 function getGiscusTheme() { return document.documentElement.classList.contains('dark') ? 'dark_tritanopia' : 'fro'; }
// 创建并插入 Giscus 脚本 function createGiscusScript() { const theme = getGiscusTheme(); const script = document.createElement('script');
script.src = 'https://giscus.app/client.js'; script.setAttribute('data-repo', '您的仓库名'); script.setAttribute('data-repo-id', '您的仓库ID'); script.setAttribute('data-category', 'Announcements'); script.setAttribute('data-category-id', '分类ID'); script.setAttribute('data-mapping', 'pathname'); script.setAttribute('data-strict', '0'); script.setAttribute('data-reactions-enabled', '1'); script.setAttribute('data-emit-metadata', '0'); script.setAttribute('data-input-position', 'top'); script.setAttribute('data-theme', theme); script.setAttribute('data-lang', 'zh-CN'); script.setAttribute('data-loading', 'lazy'); script.setAttribute('crossorigin', 'anonymous'); script.async = true;
return script; }
// 监听主题变化 function setupThemeObserver() { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.attributeName === 'class' && mutation.target === document.documentElement) { updateGiscusTheme(); } }); });
observer.observe(document.documentElement, { attributes: true }); }
// 更新 Giscus 主题 function updateGiscusTheme() { const theme = getGiscusTheme();
const iframe = document.querySelector('iframe.giscus-frame'); if (iframe && 'contentWindow' in iframe) { iframe.contentWindow.postMessage( { giscus: { setConfig: { theme } } }, 'https://giscus.app' ); } }
// 初始化 Giscus function initGiscus() { const container = document.getElementById('giscus-container'); if (container) { // 清空容器(防止重复加载) container.innerHTML = ''; // 添加脚本 container.appendChild(createGiscusScript()); // 设置主题观察器 setupThemeObserver(); } }
// 尝试立即加载 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initGiscus); } else { // 如果 DOM 已经加载完成,立即执行 initGiscus(); }
// 确保在页面完全加载后也能正常初始化(双重保险) window.addEventListener('load', initGiscus); })();</script>
<style> .giscus-wrapper { width: 100%; }</style>
注意:上面的代码中,您需要将
data-repo
、data-repo-id
和data-category-id
替换为您自己的值。这些值可以从 Giscus 官网获取。
2. 代码解析
让我们详细解释一下这个组件的工作原理:
主题检测和设置
function getGiscusTheme() { return document.documentElement.classList.contains('dark') ? 'dark_tritanopia' : 'fro';}
这个函数检查文档的根元素是否包含 dark
类来判断当前主题。如果是深色模式,则返回 dark_tritanopia
主题;否则返回 fro
主题。
动态创建 Giscus 脚本
function createGiscusScript() { const theme = getGiscusTheme(); const script = document.createElement('script');
// 设置 Giscus 属性...
return script;}
这个函数动态创建 Giscus 脚本元素,并根据当前主题设置适当的 data-theme
属性。
监听主题变化
function setupThemeObserver() { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.attributeName === 'class' && mutation.target === document.documentElement) { updateGiscusTheme(); } }); });
observer.observe(document.documentElement, { attributes: true });}
使用 MutationObserver
监听文档根元素的 class
属性变化。当检测到变化时,调用 updateGiscusTheme()
函数更新 Giscus 主题。
更新 Giscus 主题
function updateGiscusTheme() { const theme = getGiscusTheme();
const iframe = document.querySelector('iframe.giscus-frame'); if (iframe && 'contentWindow' in iframe) { iframe.contentWindow.postMessage( { giscus: { setConfig: { theme } } }, 'https://giscus.app' ); }}
这个函数通过 postMessage
API 向 Giscus iframe 发送消息,告知其更新主题。
初始化和加载保障
// 尝试立即加载if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initGiscus);} else { // 如果 DOM 已经加载完成,立即执行 initGiscus();}
// 确保在页面完全加载后也能正常初始化(双重保险)window.addEventListener('load', initGiscus);
这部分代码确保 Giscus 在以下情况下都能正确初始化:
- 脚本在 DOM 加载前执行
- 脚本在 DOM 加载后执行
- 页面完全加载后(作为备份机制)
使用方法
将 Giscus 组件添加到您的文章模板或布局中,例如:
---// 其他导入...import Giscus from '../components/comments/Giscus.astro';---
<Layout> <article> <!-- 文章内容 --> </article>
<Giscus /></Layout>
常见问题及解决方案
1. 首次加载时主题不匹配
有时候,当页面首次加载时,Giscus 可能会使用默认主题而不是当前网站主题。这通常是因为 Giscus 脚本在检测到正确主题之前就已加载。
解决方案:我们的代码中已经通过立即执行函数和多重加载检查解决了这个问题。
2. 主题切换后 Giscus 不更新
如果在切换网站主题后 Giscus 主题没有更新,可能是因为 MutationObserver 没有正确设置或者 postMessage 调用有问题。
解决方案:确保 MutationObserver 正确监听了文档根元素的类变化,并且 postMessage 调用格式正确。
3. Giscus 重复加载
在某些情况下,特别是使用 SPA 路由时,Giscus 可能会重复加载。
解决方案:在我们的代码中,通过在添加新的 Giscus 脚本前清空容器内容(container.innerHTML = '';
)解决了这个问题。
结论
通过以上方法,我们成功实现了 Giscus 评论系统在 Astro 中随网站主题自动切换的功能。这不仅提升了用户体验,也保持了整个网站视觉风格的一致性。
Giscus 作为一个基于 GitHub Discussions 的评论系统,为静态网站提供了一个轻量级、无广告的评论解决方案。结合 Astro 的强大功能,我们可以创建一个既美观又实用的博客系统。