背景
最近のWebサイトではOSのダークモード設定をそのまま反映しているものもある. 僕は普段からOSの設定をダークモードにしているので,previmでダークモード対応を行うことを考えた.
デモ
以下のようになった. ダークモードとライトモードの切り替えも示している.
実装方針
実装方針としては下記のようにした.
- 本体はいじらずカスタムCSSとアドオンのみで対応する
- ダークモードとライトモードを切り替え可能にする
- このため,メディアクエリを用いたCSSだけでの対応は不可
- 方法としてはプレビュー上にボタン追加やクエリパラメータではなく,開発者モードでの関数呼び出しでよい
applyLightTheme()
でライトモードに,applyDarkThene()
でダークモードに
実装
カスタムCSS
ライトモード,ダークモード用の色はCSS変数を用いて管理する. ライトモードの色はprevim付属のデフォルトのCSSに従う. ダークモードの色はGitHubの配色を参考に,デフォルトのCSSを置き換えることにした.
コードブロックの配色に関して,ライトモードはprevim付属のhighlight.jsのCSSではなく, ダークモードともども最新のhighlightjs/highlight.jsの下記CSSを用いることにした
モード | CSS |
---|---|
ライトモード | github.css |
ダークモード | github-dark.css |
previmの仕組み上,付属のhighlight.jsのCSSはアドオンとして追加されるため,カスタムCSSより後に読み込まれる. そのため,CSSの優先順位を利用し,カスタムCSSの配色を優先させる.
/* Color for initial loading */ :root { --color-fg-normal: #333; --color-bg-normal: white; transition: 0.5s; } @media (prefers-color-scheme: dark) { :root { --color-fg-normal: #e6edf3; --color-bg-normal: #0d1117; } } /* Color for light mode */ :root.theme-light { --color-fg-normal: #333; --color-bg-normal: white; --color-bg-second: #f8f8f8; --color-fg-head: black; --color-border-code: #eaeaea; --color-blockquote: #777777; /* Colors from highlightjs/highlight.js/src/styles/github.css */ --color-fg-hljs: #24292e; --color-bg-hljs: #f8f8f8; --color-fg-hljs-keyword: #d73a49; --color-fg-hljs-title: #6f42c1; --color-fg-hljs-variable: #005cc5; --color-fg-hljs-string: #032f62; --color-fg-hljs-symbol: #e36209; --color-fg-hljs-comment: #6a737d; --color-fg-hljs-name: #22863a; --color-fg-hljs-subst: #24292e; --color-fg-hljs-section: #005cc5; --color-fg-hljs-bullet: #735c0f; --color-fg-hljs-emphasis: #24292e; --color-fg-hljs-strong: #24292e; --color-fg-hljs-addition: #22863a; --color-bg-hljs-addition: #f0fff4; --color-fg-hljs-deletion: #b31d28; --color-bg-hljs-deletion: #ffeef0; } /* Color for dark mode */ :root.theme-dark { --color-fg-normal: #e6edf3; --color-bg-normal: #0d1117; --color-bg-second: #161b22; --color-fg-head: #e6edf3; --color-border-code: #111111; --color-blockquote: #e6edf3; /* Colors from highlightjs/highlight.js/src/styles/github-dark.css */ --color-fg-hljs: #c9d1d9; --color-bg-hljs: #161b22; --color-fg-hljs-keyword: #ff7b72; --color-fg-hljs-title: #d2a8ff; --color-fg-hljs-variable: #79c0ff; --color-fg-hljs-string: #a5d6ff; --color-fg-hljs-symbol: #ffa657; --color-fg-hljs-comment: #8b949e; --color-fg-hljs-name: #7ee787; --color-fg-hljs-subst: #c9d1d9; --color-fg-hljs-section: #1f6feb; --color-fg-hljs-bullet: #f2cc60; --color-fg-hljs-emphasis: #c9d1d9; --color-fg-hljs-strong: #c9d1d9; --color-fg-hljs-addition: #aff5b4; --color-bg-hljs-addition: #033a16; --color-fg-hljs-deletion: #ffdcd7; --color-bg-hljs-deletion: #67060c; } html { background: var(--color-bg-normal); } body { color: var(--color-fg-normal); } h1 { color: var(--color-fg-head); } h2 { color: var(--color-fg-head); } pre, .highlight pre { background-color: var(--color-bg-second); } code { border-color: var(--color-border-code); background-color: var(--color-bg-second); } blockquote { color: var(--color-blockquote); } table tr { background-color: var(--color-bg-normal); } table tr:nth-child(2n) { background-color: var(--color-bg-second); } /* * Overwrite higilight.css */ code.hljs { color: var(--color-fg-hljs); background: var(--color-bg-hljs); } span.hljs-doctag, span.hljs-keyword, span.hljs-meta .hljs-keyword, span.hljs-template-tag, span.hljs-template-variable, span.hljs-type, span.hljs-variable.language_ { /* prettylights-syntax-keyword */ color: var(--color-fg-hljs-keyword); } span.hljs-title, span.hljs-title.class_, span.hljs-title.class_.inherited__, span.hljs-title.function_ { /* prettylights-syntax-entity */ color: var(--color-fg-hljs-title); } span.hljs-attr, span.hljs-attribute, span.hljs-literal, span.hljs-meta, span.hljs-number, span.hljs-operator, span.hljs-variable, span.hljs-selector-attr, span.hljs-selector-class, span.hljs-selector-id { /* prettylights-syntax-constant */ color: var(--color-fg-hljs-variable); } span.hljs-regexp, span.hljs-string, span.hljs-meta .hljs-string { /* prettylights-syntax-string */ color: var(--color-fg-hljs-string); } span.hljs-built_in, span.hljs-symbol { /* prettylights-syntax-variable */ color: var(--color-fg-hljs-symbol); } span.hljs-comment, span.hljs-code, span.hljs-formula { /* prettylights-syntax-comment */ color: var(--color-fg-hljs-comment); } span.hljs-name, span.hljs-quote, span.hljs-selector-tag, span.hljs-selector-pseudo { /* prettylights-syntax-entity-tag */ color: var(--color-fg-hljs-name); } span.hljs-subst { /* prettylights-syntax-storage-modifier-import */ color: var(--color-fg-hljs-subst); } span.hljs-section { /* prettylights-syntax-markup-heading */ color: var(--color-fg-hljs-section); } span.hljs-bullet { /* prettylights-syntax-markup-list */ color: var(--color-fg-hljs-bullet); } span.hljs-emphasis { /* prettylights-syntax-markup-italic */ color: var(--color-fg-hljs-emphasis); } span.hljs-strong { /* prettylights-syntax-markup-bold */ color: var(--color-fg-hljs-strong); } span.hljs-addition { /* prettylights-syntax-markup-inserted */ color: var(--color-fg-hljs-addition); background-color: var(--color-bg-hljs-addition); } span.hljs-deletion { /* prettylights-syntax-markup-deleted */ color: var(--color-fg-hljs-deletion); background-color: var(--color-bg-hljs-deletion); }
上記のCSSは ~/.vim/previm/custom.css
として保存する.
アドオン設定
Previmの設定は下記のようにしておく. js のコードは url や path が無くても差し込めるとのこと.
アドオンの 'code'
に記載したコードはプレビュー更新時に毎回実行されるものである.
今回実行したい処理はワンタイム実行のみでよく,本来は外部ファイルとすべきではあるが,めんどくさくてサボっている.
また,プレビュー初回読み込みまではモードなしとなるため,CSS側でデフォルトの配色設定を行っている. (特にダークモードについてはメディアクエリで前景色と背景色のCSS変数のみ設定している)
let g:previm_custom_css_path = '~/.vim/previm/custom.css' let g:previm_extra_libraries = [ \ { \ 'name': 'theme', \ 'files': [ \ { \ 'type': 'js', \ 'code': [ \ '(function(global, doc) {', \ " 'use strict';", \ ' function applyTheme(themeName) {', \ ' doc.documentElement.className = themeName;', \ ' }', \ " if (doc.documentElement.className === '') {", \ " var prefersColorSchemeDark = typeof matchMedia === 'function' && matchMedia('(prefers-color-scheme: dark)').matches;", \ " applyTheme(prefersColorSchemeDark ? 'theme-dark' : 'theme-light');", \ ' }', \ ' global.applyLightTheme = function() {', \ " applyTheme('theme-light');", \ ' };', \ ' global.applyDarkTheme = function() {', \ " applyTheme('theme-dark');", \ ' };', \ '})(window, document);' \ ] \ } \ ] \ } \]