[{"data":1,"prerenderedAt":379},["ShallowReactive",2],{"content:\u002F2025\u002Fdark-mode-guide":3,"surround:\u002F2025\u002Fdark-mode-guide":367},{"id":4,"title":5,"body":6,"categories":340,"date":342,"description":343,"draft":344,"extension":345,"image":346,"meta":347,"navigation":349,"path":350,"permalink":351,"published":351,"readingTime":352,"recommend":351,"references":351,"seo":357,"sitemap":358,"stem":359,"tags":360,"type":364,"updated":365,"__hash__":366},"content\u002Fposts\u002F2025\u002Fdark-mode-guide.md","深色模式开发的最佳实践",{"type":7,"value":8,"toc":325},"minimark",[9,13,18,22,33,49,52,66,72,82,85,91,95,98,111,118,127,130,133,151,154,157,187,190,196,212,231,234,243,262,268,272,275,281,289,297,303,311,319],[10,11,12],"h2",{"id":12},"自动深色模式",[14,15,17],"h3",{"id":16},"使用-css-媒体查询","使用 CSS 媒体查询",[19,20,21],"p",{},"相信大家在查阅深色模式实现时，都遇到过这种写法：",[23,24,30],"pre",{"className":25,"code":27,"language":28,"meta":29},[26],"language-css","@media (prefers-color-scheme: dark) {\n\tbody {\n\t\tbackground-color: black;\n\t\tcolor: white;\n\t}\n}\n","css","",[31,32,27],"code",{"__ignoreMap":29},[34,35,37],"alert",{"title":36},"参阅文档",[19,38,39],{},[40,41,45,48],"a",{"href":42,"rel":43},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FWeb\u002FCSS\u002F@media\u002Fprefers-color-scheme",[44],"nofollow",[31,46,47],{"code":47},"prefers-color-scheme"," 媒体特性 - CSS | MDN",[14,50,51],{"id":51},"渐进增强而非并列查询",[19,53,54,55,57,58,61,62,65],{},"有些同学会说，诶，我有个好主意，我了解 ",[31,56,47],{"code":47}," 的值还有 ",[31,59,60],{"code":60},"light"," 和 ",[31,63,64],{"code":64},"no-preference","，我把基础样式并列地写进这三种媒体查询里。",[19,67,68,69,71],{},"这对吗？Chrome 76 起才支持 ",[31,70,47],{"code":47}," 媒体特性，放在某些兼容性差的浏览器里，这些规则都不会生效。",[34,73,75],{"title":74},"参阅 Issue",[19,76,77],{},[40,78,81],{"href":79,"rel":80},"https:\u002F\u002Fgithub.com\u002Fxaoxuu\u002Fhexo-theme-stellar\u002Fissues\u002F477",[44],"移动端夸克浏览器适配问题 · Issue #477 · xaoxuu\u002Fhexo-theme-stellar",[19,83,84],{},"所以最好采用「渐进增强」的策略，即先写基础样式，再写深色模式样式，这样可以保证低版本内核的浏览器正常显示。",[23,86,89],{"className":87,"code":88,"language":28,"meta":29},[26],"body {\n\tbackground-color: white;\n\tcolor: black;\n}\n\n@media (prefers-color-scheme: dark) {\n\tbody {\n\t\tbackground-color: black;\n\t\tcolor: white;\n\t}\n}\n",[31,90,88],{"__ignoreMap":29},[14,92,94],{"id":93},"使用-css-变量","使用 CSS 变量",[19,96,97],{},"又有同学要头疼了，按钮、弱文本也要适应深色模式，那我岂不得维护许多深色模式下的 CSS 声明块？",[19,99,100,101,106,107,110],{},"这个时候，",[40,102,105],{"href":103,"rel":104},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FWeb\u002FCSS\u002FUsing_CSS_custom_properties",[44],"CSS 变量"," 就派上用场了。我们可以维护一个普通模式和深色模式的「调色盘」，其他声明块中需要使用颜色，只需要通过 ",[31,108,109],{"code":109},"var(--my-color-1)"," 的形式引用即可。",[23,112,116],{"className":113,"code":114,"filename":115,"language":28,"meta":29},[26],":root {\n\t--bg1: #f7f9ff;\n\t--bg2: #fff;\n\t--text1: #334;\n\t--text2: #556;\n\t--text3: #aab;\n\t--line: hsl(240 33% 60% \u002F 0.2);\n\t--ac1: #37f;\n\t--ac2: hsl(210 100% 50% \u002F 0.5);\n\t--ac3: hsl(210 100% 50% \u002F 0.2);\n}\n\n@media (prefers-color-scheme: dark) {\n\t:root {\n\t\t--bg1: #111;\n\t\t--bg2: #222;\n\t\t--text1: #eee;\n\t\t--text2: #ccc;\n\t\t--text3: #777;\n\t\t--ac1: #fc2;\n\t--ac2: hsl(45 100% 50% \u002F 0.5);\n\t--ac3: hsl(45 100% 50% \u002F 0.2);\n\t}\n}\n","https:\u002F\u002Fcooo.site\u002Fasset\u002Findex.css",[31,117,114],{"__ignoreMap":29},[19,119,120,121,126],{},"另外，要注意各个颜色模式下的对比度，在确保文字清晰的同时，不要使用过高的对比度（纯黑、纯白），让用户的体验更舒适。收起 ",[122,123,125],"tip",{"tip":124},"OLED 屏幕显示纯黑色几乎不费电，OLED 手机引入市场时不少 APP 做了深色模式纯黑色背景的适配","「A屏黑」"," 的想法，纯黑色省电的代价是黑暗环境中高对比度对用户视觉的刺激。",[10,128,129],{"id":129},"手动深色模式",[19,131,132],{},"又有同学在想了：我们做出的自动深色模式只是设备支持下的自动切换小彩蛋，如何显式展示技术力，比如放个深色模式按钮呢？",[19,134,135,136,139,140,146,147,150],{},"假设我们已经通过 ",[31,137,138],{"code":138},"localStorage"," 存储了用户主题并在加载时给 ",[31,141,144],{"className":142,"code":144,"language":145},[143],"language-html","\u003Chtml>","html"," 添加了 ",[31,148,149],{"code":149},"data-theme"," 属性，看起来完美实现了颜色模式切换，其实还会遇到几个降低用户体验的问题。",[14,152,153],{"id":153},"浏览器生成的自动深色模式",[19,155,156],{},"你的浅色模式在设备的浅色模式下表现很好，但是当设备在深色模式时，你的浅色模式可能表现得一团糊涂。",[34,158,159],{"title":36},[160,161,162,170,180],"ul",{},[163,164,165],"li",{},[40,166,169],{"href":167,"rel":168},"https:\u002F\u002Fdeveloper.chrome.google.cn\u002Fblog\u002Fauto-dark-theme",[44],"自动深色主题 | Blog | Chrome for Developers",[163,171,172],{},[40,173,176,179],{"href":174,"rel":175},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FWeb\u002FCSS\u002Fcolor-scheme",[44],[31,177,178],{"code":178},"color-scheme"," - CSS | MDN",[163,181,182],{},[40,183,186],{"href":184,"rel":185},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FWeb\u002FHTML\u002FReference\u002FElements\u002Fmeta\u002Fname#%E5%85%B6%E4%BB%96%E8%A7%84%E8%8C%83%E4%B8%AD%E5%AE%9A%E4%B9%89%E7%9A%84%E6%A0%87%E5%87%86%E5%85%83%E6%95%B0%E6%8D%AE%E5%90%8D%E7%A7%B0",[44],"CSS 颜色调整规范定义的标准元数据名称 - HTML | MDN",[19,188,189],{},"怎会如此！？Chrome 的贴心设计优化了不支持深色模式的网页显示，但成为了开发者在深色模式下展示浅色网页的心智负担。",[19,191,192,193,195],{},"我们需要通过 ",[31,194,178],{"code":178}," 告诉浏览器网页支持深色和浅色模式，不要让浏览器扳倒我们自己的适配。两种方法二选一即可：",[160,197,198,205],{},[163,199,200,201],{},"在 HTML 头部通过元数据声明 ",[31,202,204],{"className":203,"code":204,"language":145},[143],"\u003Cmeta name=\"color-scheme\" content=\"light dark\">",[163,206,207,208],{},"在 CSS 中声明 ",[31,209,211],{"className":210,"code":211,"language":28},[26],":root { color-scheme: light dark; }",[19,213,214,215,219,220,224,225,230],{},"记得在手动深色模式（类名而非媒体查询的实现）下设置 ",[31,216,218],{"className":217,"code":218,"language":28},[26],"[data-theme=\"dark\"] { color-scheme: dark; }","。不然想一想 ",[31,221,223],{"className":222,"code":223,"language":28},[26],"color-scheme: light dark"," 会怎么表现？滚动条、按钮等元素在 ",[40,226,229],{"href":227,"rel":228},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FWeb\u002FCSS\u002FCSS_cascade\u002FCascade#css_%E5%A3%B0%E6%98%8E%E7%9A%84%E6%BA%90",[44],"用户代理样式表"," 给出的是浅色的样式！这和手动深色模式的预期不一致。",[14,232,233],{"id":233},"页面初载时的闪烁",[19,235,236,237,242],{},"你的深色模式在设备深色模式下表现很好，但是当设备在浅色模式下，你的深色模式在初载时可能闪白，这对于用户眼睛的伤害是巨大的（点名批评 ",[40,238,241],{"href":239,"rel":240},"https:\u002F\u002Fdocs.github.com\u002Fzh",[44],"GitHub Docs","）。",[19,244,245,246,251,252,256,257,261],{},"我曾使用 Pinia 管理用户主题，但 Pinia 的挂载需要时间，产生了页面闪白问题。我留意到一些网站是没有的，比如 VitePress，我研究了其代码实现（",[40,247,250],{"href":248,"rel":249},"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fvitepress\u002Fblob\u002Fmain\u002Fsrc\u002Fnode\u002Fconfig.ts#L360",[44],"vitepress\u002Fsrc\u002Fnode\u002Fconfig.ts#L360","）。它在 ",[31,253,255],{"className":254,"code":255,"language":145},[143],"\u003Chead>"," 中添加了同步的 ",[31,258,260],{"className":259,"code":260,"language":145},[143],"\u003Cscript>","，通过 IIFE 立即执行函数，在 DOM 构建完成前就完成了主题切换，避免了页面闪白。",[23,263,266],{"className":264,"code":265,"language":145,"meta":29},[143],"\u003Chead>\n\t\u003Cscript>\n\t\tif (localStorage.getItem('theme') === 'dark')\n\t\t\tdocument.documentElement.dataset.theme = 'dark'\n\t\u003C\u002Fscript>\n\u003C\u002Fhead>\n",[31,267,265],{"__ignoreMap":29},[14,269,271],{"id":270},"一致性和-css-代码冗余","一致性和 CSS 代码冗余",[19,273,274],{},"代码冗余会轻松地创建不一致，而不一致带来潜在的破坏力是惊人的：",[23,276,279],{"className":277,"code":278,"language":28,"meta":29},[26],":root {\n\t--c-bg: white;\n\t--c-text: black;\n}\n\n@media (prefers-color-scheme: dark) {\n\t\u002F* 原本是 :root[data-theme=\"auto\"]\n\t * 但下一个选择器应该有相同或更高的优先级 *\u002F\n\t:root {\n\t\t--c-bg: #111;\n\t\t--c-text: #eee;\n\t}\n}\n\n[data-theme=\"dark\"] {\n\t--c-bg: black;\n\t--c-text: white;\n\tcolor-scheme: dark;\n}\n",[31,280,278],{"__ignoreMap":29},[19,282,283,284,288],{},"这样，你的深色模式定义了两次，你需要小心地维护这些代码。也许可以用 ",[31,285,287],{"className":286,"code":287,"language":28},[26],"@mixin"," 来减少冗余？但终归不是完美的解决方案。",[19,290,291,292,296],{},"这时，抛弃掉 ",[31,293,295],{"className":294,"code":295,"language":28},[26],"[data-theme=\"auto\"]"," 就显得有用了——通过在 JS 中提前让「自动」的状态「塌缩」为确定的「浅色」或「深色」，CSS 中只需维护两种模式下的对应样式即可。",[19,298,299,300,302],{},"不要忘记监听 ",[31,301,47],{"code":47}," 的变化：",[23,304,309],{"className":305,"code":307,"language":308,"meta":29},[306],"language-ts","const colorSchemeQuery = window.matchMedia('(prefers-color-scheme: dark)')\n\nfunction setTheme(theme?: 'light' | 'dark' | 'auto') {\n\tif (theme)\n\t\tlocalStorage.setItem('theme', theme)\n\n\tconst targetTheme = theme || localStorage.getItem('theme') || 'auto'\n\tconst isDark = targetTheme === 'dark' || (targetTheme === 'auto' && colorSchemeQuery.matches)\n\n\tdocument.documentElement.dataset.theme = isDark ? 'dark' : 'light'\n\t\u002F\u002F 如果使用类名实现，则\n\t\u002F\u002F document.body.classList.toggle('dark', isDark ? 'dark' : 'light')\n}\n\ncolorSchemeQuery.addEventListener('change', () => setTheme());\n\nsetTheme()\n","ts",[31,310,307],{"__ignoreMap":29},[19,312,313,314,318],{},"这里的代码只是给出实现思路，具体实现一个完美的深色模式还要多费些心思。什么，竟然有人操作 ",[31,315,317],{"className":316,"code":317,"language":308},[306],"document.getElementsByTagName('html')[0].className","？回家吧孩子。",[320,321,322],"quote",{},[19,323,324],{},"Take care of users, and code well. ☝️",{"title":29,"searchDepth":326,"depth":326,"links":327},4,[328,335],{"id":12,"depth":329,"text":12,"children":330},2,[331,333,334],{"id":16,"depth":332,"text":17},3,{"id":51,"depth":332,"text":51},{"id":93,"depth":332,"text":94},{"id":129,"depth":329,"text":129,"children":336},[337,338,339],{"id":153,"depth":332,"text":153},{"id":233,"depth":332,"text":233},{"id":270,"depth":332,"text":271},[341],"开发","2025-04-14 10:07:16","前端深色模式不止于 prefers-color-scheme，完整解析自动检测、手动切换的实现方案，解决闪屏、样式冲突、变量管理等常见痛点，提供稳健的深色模式最佳实践。",false,"md","https:\u002F\u002Fassets.zhilu.cyou\u002Fcover4\u002Fdark-mode-guide.jpg",{"slots":348},{},true,"\u002F2025\u002Fdark-mode-guide",null,{"text":353,"minutes":354,"time":355,"words":356},"7 min read",6.8,408000,1360,{"title":5,"description":343},{"loc":350},"posts\u002F2025\u002Fdark-mode-guide",[361,362,363],"前端","深色模式","CSS","tech","2025-04-14 18:45:28","9gQPpScrbELkcLpjFo5HyA-qkqC6OierP4bSG4li4Qw",[368,374],{"title":369,"path":370,"stem":371,"date":372,"type":373,"children":-1},"寻不回手工油糕","\u002F2025\u002Foil-cakes","posts\u002F2025\u002Foil-cakes","2025-02-16 12:30:24","story",{"title":375,"path":376,"stem":377,"date":378,"type":364,"children":-1},"前端字体二三事","\u002F2025\u002Ffont-tips","posts\u002F2025\u002Ffont-tips","2025-04-16 08:49:50",1782091372927]