折腾评论区的时候发现一个事:站点的评论区不会跟随主题变色,一直白底。查阅资料发现giscus官方配置中的data-theme="preferred_color_scheme"表示跟随系统变色,我把系统颜色从浅色改成深色后,评论区就变成了深色,但是我希望评论区跟随主题变色,所以需要做一些调整。
然后我给giscus的配置做了一些改动,让它跟随主题变色,但是出错了,切换主题后评论区并没有变色。
一开始我怀疑是监听写错了。评论区的主题同步逻辑大概是这样的:监听某个元素的变化,然后往 giscus 的 iframe 里发消息换主题。我监听的是 body 上的 class:
1
2
| // 我原来这么写的
return document.body.classList.contains('dark') ? 'transparent_dark' : 'light';
|
结果怎么试都不生效。后来翻了 PaperMod 的源码才发现,它的主题切换根本不是给 body 加 class,而是改 <html> 元素上的 data-theme 属性:
1
| document.documentElement.dataset.theme = 'dark';
|
所以 body.classList.contains('dark') 永远都是 false,自然永远走亮色分支,监听的 class 变化也永远不会触发。
另外还有个坑:giscus 开了 data-loading="lazy",iframe 要滚动到评论区附近才创建。如果在这之前切主题,postMessage 发出去没人接收,等 iframe 加载的时候只能用 script 标签上旧的 data-theme 初始化,主题还是错的。
检测和监听都改成 data-theme:
1
2
3
4
5
6
7
8
9
10
11
12
13
| function getGiscusTheme() {
return document.documentElement.dataset.theme === 'dark'
? '{{ absURL "css/giscus-dark.css" }}'
: '{{ absURL "css/giscus-light.css" }}';
}
const observer = new MutationObserver(() => {
sendGiscusTheme(getGiscusTheme());
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['data-theme']
});
|
发消息的时候顺手把 script 标签和 iframe 上的 data-theme 属性也同步掉,就算 iframe 还没创建,等它加载的时候也能用对主题:
1
2
3
4
5
6
7
8
9
10
11
12
| function sendGiscusTheme(giscusTheme) {
const script = container.querySelector('script[src*="giscus"]');
if (script) script.setAttribute('data-theme', giscusTheme);
const iframe = document.querySelector('iframe.giscus-frame');
if (!iframe || !iframe.contentWindow) return;
iframe.dataset.theme = giscusTheme;
iframe.contentWindow.postMessage(
{ giscus: { setConfig: { theme: giscusTheme } } },
'https://giscus.app'
);
}
|
风格不统一的问题#
主题能切换之后,又发现明暗两边的观感差太多。原因是我当初为了"融入暗色背景"选了 transparent_dark(透明主题),而亮色用的官方 light(自带背景和边框)。做完之后发现,暗色背景的评论区背景和边框是透明的,跟暗色背景融入在一起,跟正文的背景和边框完全不搭。
干脆统一成卡片风格,给明暗各写一个自定义主题,颜色直接抄 PaperMod 的 CSS 变量,这样评论区跟正文就是一个色系的了。建了两个文件 static/css/giscus-light.css 和 static/css/giscus-dark.css,主要改这几个变量:
--color-canvas-default(卡片背景):亮色 #ffffff,暗色 #1d1e20,对应 PaperMod 的 --theme--color-canvas-subtle(输入框背景):亮色 #f5f5f5,暗色 #37383e,对应 --code-bg--color-border-default(边框):亮色 #eeeeee,暗色 #333333,对应 --border--color-fg-default / --color-fg-muted(文字):对应 --primary / --secondary- 按钮背景用
--code-bg / --entry,提交按钮保留 giscus 官方那个绿色
有一点要注意:自定义主题必须以 main 选择器包裹变量,而且 giscus 的语法高亮(--color-prettylights-*)、加载动画那些定义最好别删,不然评论区会缺样式。
部署时的坑#
giscus 的 iframe 是跨域加载自定义 CSS 的,服务器必须返回 Access-Control-Allow-Origin 头,不然评论区直接加载不出来。我是 Nginx 部署,加了一段:
1
2
3
| location /css/ {
add_header Access-Control-Allow-Origin *;
}
|
用 GitHub Pages 的话不用管,它自动带这个响应头。
改完的代码#
layouts/partials/comments.html 完整内容(数据源配置换成你自己的):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
| <div class="giscus" id="giscus-container"></div>
<script>
(function() {
const container = document.getElementById('giscus-container');
// 主题状态在 <html data-theme="..."> 上,PaperMod 切换主题就是改这个属性
function getGiscusTheme() {
return document.documentElement.dataset.theme === 'dark'
? '{{ absURL "css/giscus-dark.css" }}'
: '{{ absURL "css/giscus-light.css" }}';
}
// 同步 script/iframe 上的 data-theme + 发消息,懒加载下也不怕丢消息
function sendGiscusTheme(giscusTheme) {
const script = container.querySelector('script[src*="giscus"]');
if (script) script.setAttribute('data-theme', giscusTheme);
const iframe = document.querySelector('iframe.giscus-frame');
if (!iframe || !iframe.contentWindow) return;
iframe.dataset.theme = giscusTheme;
iframe.contentWindow.postMessage(
{ giscus: { setConfig: { theme: giscusTheme } } },
'https://giscus.app'
);
}
const script = document.createElement('script');
script.src = 'https://giscus.app/client.js';
script.crossOrigin = 'anonymous';
script.async = true;
script.setAttribute('data-loading', 'lazy');
// 替换成你自己的配置
script.setAttribute('data-repo', '用户名/仓库名');
script.setAttribute('data-repo-id', '你的repo-id');
script.setAttribute('data-category', 'Announcements');
script.setAttribute('data-category-id', '你的category-id');
script.setAttribute('data-mapping', 'pathname');
script.setAttribute('data-strict', '0');
script.setAttribute('data-reactions-enabled', '1');
script.setAttribute('data-reactions-layout', 'compact');
script.setAttribute('data-emit-metadata', '0');
script.setAttribute('data-input-position', 'top');
script.setAttribute('data-theme', getGiscusTheme()); // 加载前就设置好主题
script.setAttribute('data-lang', 'zh-CN');
container.appendChild(script);
// 监听 <html> 的 data-theme 变化
const observer = new MutationObserver(() => {
sendGiscusTheme(getGiscusTheme());
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['data-theme']
});
// 系统深色自动切换也要跟上
window.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', () => sendGiscusTheme(getGiscusTheme()));
})();
</script>
|
主题状态在 <html> 的 data-theme 属性上,不关 body 的事,监听对准它就行。懒加载丢消息那个问题也是顺手一起解决的。自定义主题的配色是照着 PaperMod 的 CSS 变量一个个抄的,弄完整体还算统一。
折腾了这么久,评论区总算能跟着主题跑了。