最上-川 - RAIN
848 字
4 分钟
Prism.JS 实现黑暗模式切换
说明
Prism 的主题部分和做代码分析的部分,实际上是分开。
从文件上看,能简单理解为:
- JS 部分会负责整理识别各语言,整理并将内部的代码进行分类,打上对应的标签。
- CSS部分负责染色
既然染色部分是独立的,那么自然也可以实现黑暗模式的颜色切换了。
⚠️使用 AI 得到的糟糕方案
由于并没有修改 Prism 的经验,所以在询问 GPT 后得到了一个令人_匪夷所思_的答案:
<link rel="stylesheet" id="prism-theme" href="path/to/prism-light-theme.css">
<script>  const darkTheme = 'path/to/prism-dark-theme.css';  const lightTheme = 'path/to/prism-light-theme.css';
  if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {    // 用户偏好 Dark Mode,使用 Dark 主题    document.getElementById('prism-theme').setAttribute('href', darkTheme);  }
  // 监听系统主题切换事件  window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {    const theme = event.matches ? darkTheme : lightTheme;    document.getElementById('prism-theme').setAttribute('href', theme);  });</script>之所以说的上是匪夷所思,是因为这个方案的核心思想,使用不同的 schema,然后监听对应的时间切换,来实现。这个方案看起来实现倒是简单,但是小问题非常多:
- 每次进行切换的时候,由于时间的延后,以及需要重新加载 CSS,所以代码框的绘制永远是延后的。
- 这个方案默认是监听prefers-color-schema这个系统事件的,对于一些通过修改 html 属性的手动切换,代码的侵入性实在是过大。
这个方案,在最后不得不废弃。
重写 CSS
尽管这个说起来十分简单,但是不知道为什么,在网上并没有找到现成可以用的相关代码(可能大家都没兴趣?)
在多方查阅后,某个 Codepen 提供的代码改动给了我一些启发。并完成了如下的内容:
/* PrismJS - used for code highlighting */
[class*="language-"] ::selection {  color: #000;  text-shadow: none;}
.token.important,.token.bold {  font-weight: bold;}.token.italic {  font-style: italic;}.token.entity {  cursor: help;}.namespace {  opacity: 0.7;}
/* Default colors (Light mode) */:not(pre) > code[class*="language-"],pre[class*="language-"] {  --color: #33a;  --textShadow: #fff;  --comment: #6e6e6e;  --punctuation: #4e4e4e;  --property: #905;  --operator: #70b;  --selector: #487b00;  --url: #8d6640;  --urlBg: hsla(0, 0%, 100%, .5);  --boolean: #905;  --atrule: #0075a8;  --keyword: #0075a8;  --function: #c93654;  --regex: #860;  --boxShadow: hsla(0,0%,0%,.3);  --prism-background: #f5f2f0;
}
/* If the OS has dark mode set then... *//* Change dark to light to test */
@media (prefers-color-scheme: dark) {  html:not([data-theme="light"]) code[class*="language-"]:not([contenteditable]),  html:not([data-theme="light"]) pre[class*="language-"] {    --color: #6ae;    --textShadow: #000;    --comment: #9ab;    --punctuation: #999;    --property: #e70;    --operator: #d7f;    --selector: #8b2;    --url: #cde;    --urlBg: rgba(0,0,0,.5);    --boolean: #a8f;    --atrule: #f00;    --keyword: #f00;    --function: #f55;    --regex: #f91;
    --boxShadow: #000;    --prism-background: #f5f2f0;  }}
/* Manual switch mode - where implemented */html[data-theme="dark"] code[class*="language-"]:not([contenteditable]),html[data-theme="dark"] pre[class*="language-"] {
  /* Exactly the same as prefers-color-scheme: dark */  --color: #6ae;  --textShadow: #000;  --comment: #9ab;  --punctuation: #999;  --property: #e70;  --operator: #d7f;  --selector: #8b2;  --url: #cde;  --urlBg: rgba(0,0,0,.5);  --boolean: #8af;  --atrule: #ffb;  --keyword: #fe6;  --function: #f55;  --regex: #f91;  --prism-background: #2a2a2a;  --boxShadow: #000;
}
.token.comment,.token.prolog,.token.doctype,.token.cdata {  color: var(--comment);}.token.punctuation {  color: var(--punctuation);}.token.property,.token.symbol,.token.tag,.token.constant,.token.deleted {  color: var(--property);}.token.boolean,.token.number {  color: var(--boolean);}.token.selector,.token.attr-name,.token.string,.token.char,.token.builtin,.token.inserted {  color: var(--selector);}.token.operator {  color: var(--operator);}.token.url,.token.entity,.language-css .token.string,.style .token.string {  color: var(--url);  background-color: var(--urlBg);}.token.atrule,.token.attr-value {  color: var(--atrule);}.token.keyword {  color: var(--keyword);}.token.function {  color: var(--function);}.token.regex,.token.important,.token.variable {  color: var(--regex);}:not(pre) > code {  background: #f5f2f0;  padding: 2px 0;}html[data-theme="dark"] :not(pre) > code {  background-color: #2a2a2a;}
:not(pre) > code[class*="language-"],pre[class*="language-"] {  background: var(--prism-background);}
@media print {  code[class*="language-"],  pre[class*="language-"] {    text-shadow: none;
  }}这段代码主要还是通过声明具体的 class 的颜色为CSS变量来实现的。通过监听html 标签中data-theme属性是否为dark来判断当前使用的主题颜色。
这个方案是本站目前正在使用的方案,相比重新加载 CSS 会有更好的性能和显示效果。

