GitHubのREADMEの中の画像(SVG)をダークモードに対応させる

公開:2021/01/31
更新:2021/02/06
8 min読了の目安(約7400字TECH技術記事 2

TL;DR

SVGの中のstyleタグの中では実はMedia Queryが使えるので、

@media (prefers-color-scheme: dark) {
	/* Dark Mode用の設定をここに書く */
}

とやると、かんたんにダークモードに対応した画像が作成できる。
もちろんCSS Variablesを使ったりもできる。
もしかするとdisplay: none;を使うこともできるかもしれないので、SVGの中に画像を埋め込み、ダークモードをライトモードで出し分ける、なんてこともできるかもしれない。

https://zenn.dev/qsf/articles/a4c1b527e77bf6#comment-372e543446ecb0
追記: 上記コメントも参考になるので合わせて確認してください。

ChromeもFirefoxもSafariも、Developer Toolsでのemulationによるテーマの切り替えでは、SVGに適用することはできなかった。バグかも知れないが、揃いも揃ってなので仕様かもしれない(調べてないです、すみません…)。
なのでテストするときは、端末の設定(Macなら一般 → 外観モードで変更してタブをリロードする必要がある)を変更しなければならない。

現在Android / iOSのGitHub Mobileアプリではこの方法がうまくいかず、ダークモードでも画像はライトモードとして表示されてしまう。

暇な人向け

事の発端は @treastrain が公開している、 Japan NFC Reader という各種ICカードの残高等データの読み取りがiPhoneでできるアプリの核に当たる部分のOSSがありまして、

https://github.com/treastrain/TRETJapanNFCReader

これに使用しているトップの画像がダークモードでうまいこと表示できないか、という話になったことからはじまりました。

要はGitHubでCSSが使えればいいのですががそうはいかないので、SVGの中に閉じさせて、それをimgで読み込めばいいじゃない、というのが今回の発想です。
短く書いたものの、まさかSVGの中でMedia Queryが使えるとはまっっっったく思わず時間がかかってしまいました……。

今回参考にさせていただいたのはこちら。ありがとうございます。

https://coliss.com/articles/build-websites/operation/work/svg-favicon-in-dark-mode.html

https://blog.tomayac.com/2019/09/21/prefers-color-scheme-in-svg-favicons-for-dark-mode-icons/

ですが調べてもなぜかGitHubのREADMEで使う、という話がなかったので、記事に起こしてみることにした、というのが今回のモチベーションです。
そして、これらを参考に作ったのがこのrepository

https://github.com/Qs-F/readme-darkmode-svg

うまくいっていれば、それぞれ、

ダークモード使用時:

ダークモード使用時 ロゴが白色 テキストが白地に黒文字で表示されている

ライトモード使用時:

ライトモード使用時 ロゴが黒色 テキストが黒地に白文字で表示されている

このように表示されているはずです。細かい部分はぜひRepositoryのほうを参考にしていただきたいですが、それぞれのコードを貼って少し解説しますと、

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 800 800" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
    <g transform="matrix(1,0,0,1,-7737.59,0)">
        <g id="b" transform="matrix(1,0,0,1,4849.31,-1866.02)">
            <rect x="2888.28" y="1866.02" width="800" height="800" style="fill:none;"/>
            <g transform="matrix(1.49842,0,0,1.49785,-1619.95,-1034.65)">
                <style>
                    path {
                        fill: rgb(0,20,51);
                    }
                    @media (prefers-color-scheme: dark) {
                        path {
                            fill: rgb(221, 235, 255);
                        }
                    }
                </style>
                <path d="M3277.17,2298.36C3277.03,2298.34 3276.25,2298.26 3276.1,2298.25C3275.06,2298.88 3274.69,2298.96 3273.58,2299.53C3269.77,2301.48 3261.81,2304.43 3253.71,2307.39C3244.82,2310.65 3236.62,2312.43 3229.34,2312.05C3216.68,2311.38 3208.82,2304.48 3208.82,2292.95C3208.82,2291.6 3209.1,2289.25 3209.68,2285.89C3210.26,2282.53 3211.03,2279.5 3211.99,2276.81L3211.41,2276.81C3208.72,2280.08 3205.64,2283.68 3202.19,2287.62C3198.73,2291.56 3194.84,2295.21 3190.51,2298.57C3186.19,2301.93 3181.43,2304.72 3176.25,2306.93C3171.06,2309.14 3165.29,2310.24 3158.95,2310.24C3154.92,2310.24 3151.03,2309.52 3147.28,2308.08C3143.53,2306.64 3140.22,2304.43 3137.34,2301.45C3134.45,2298.47 3132.2,2294.68 3130.56,2290.07C3128.93,2285.45 3128.11,2279.98 3128.11,2273.64C3128.11,2265.95 3129.65,2258.27 3132.72,2250.58C3135.8,2242.89 3139.78,2235.54 3144.68,2228.53C3149.59,2221.52 3155.25,2215.03 3161.69,2209.08C3168.13,2203.12 3174.66,2197.93 3181.29,2193.51C3187.92,2189.09 3194.45,2185.63 3200.89,2183.13C3207.33,2180.64 3213.04,2179.39 3218.04,2179.39C3223.8,2179.39 3228.7,2181.02 3232.74,2184.29C3236.77,2187.55 3238.79,2191.88 3238.79,2197.26L3239.37,2197.26L3276.84,2091.77L3232.73,2091.14L3234.27,2087.11L3298.45,2087.73L3233.03,2270.47C3231.3,2275.46 3229.81,2280.27 3228.56,2284.88C3227.31,2289.49 3226.75,2293.62 3226.69,2297.27C3226.61,2301.63 3227.41,2308.04 3233.29,2308.77C3239.27,2309.51 3247.2,2307.26 3253.24,2304.46C3261.48,2300.64 3266.08,2294.05 3269.14,2290.19C3275.5,2282.18 3281.88,2264.05 3285,2249.98C3287.2,2240.09 3301.19,2152.94 3301.19,2152.94L3356.21,2151.93L3351.3,2181.81L3352.93,2181.81C3362.35,2159.7 3386.72,2150.69 3403.48,2149.88C3412.43,2149.44 3435.13,2150.35 3448.45,2159.2L3408.41,2205.34C3402.25,2201.31 3388.58,2196.88 3379.14,2197.37C3362.78,2198.22 3348.43,2209.24 3345.15,2227.26L3331.64,2308.12L3275.14,2309.14L3277.17,2298.36ZM3230.72,2220.89L3215.45,2263.26C3214.48,2265.95 3212.27,2269.7 3208.82,2274.5C3205.36,2279.31 3201.22,2284.11 3196.42,2288.91C3191.62,2293.72 3186.33,2297.85 3180.57,2301.31C3174.81,2304.77 3169.14,2306.5 3163.56,2306.5C3157.99,2306.5 3154,2304.57 3151.6,2300.73C3149.2,2296.89 3148,2291.89 3148,2285.74C3148,2280.36 3148.86,2274.12 3150.59,2267.01C3152.32,2259.9 3154.77,2252.55 3157.94,2244.96C3161.11,2237.37 3164.91,2229.88 3169.33,2222.48C3173.75,2215.08 3178.6,2208.5 3183.88,2202.73C3189.17,2196.97 3194.84,2192.31 3200.89,2188.76C3206.94,2185.2 3213.14,2183.42 3219.48,2183.42C3222.94,2183.42 3225.72,2184.05 3227.84,2185.3C3229.95,2186.55 3231.63,2188.03 3232.88,2189.76C3234.13,2191.49 3235,2193.37 3235.48,2195.38C3235.96,2197.4 3236.2,2199.28 3236.2,2201C3236.2,2202.93 3235.86,2205.18 3235.19,2207.78C3234.52,2210.37 3233.03,2214.74 3230.72,2220.89Z" />
            </g>
        </g>
    </g>
</svg>

これの、

                <style>
                    path {
                        fill: rgb(0,20,51);
                    }
                    @media (prefers-color-scheme: dark) {
                        path {
                            fill: rgb(221, 235, 255);
                        }
                    }
                </style>

ここが重要です。

また、テキストの方は、

<svg fill="none" viewBox="0 0 800 400" width="800" height="400" xmlns="http://www.w3.org/2000/svg">
	<foreignObject width="100%" height="100%">
		<div xmlns="http://www.w3.org/1999/xhtml">
			<style>
				.root {
					--whitish: #fff;
					--blackish: #000;
					--bg: var(--blackish);
					--color: var(--whitish);
				}
				@media (prefers-color-scheme: dark) {
					.root {
						--bg: var(--whitish);
						--color: var(--blackish);
					}
				}

				p {
					color: var(--bg)
				}

				span {
					font-size: 32px;
				}

				.support-reverse-darkmode {
					background: var(--bg);
					color: var(--color);
				}

			</style>
			<div class="root">
				<p>Test</p>
				<p><a href="https://de-liker.com">Hello Link</a></p>
				<div class="support-reverse-darkmode">
					<span>This text should be rendered Black Text on White Background in dark mode.</span>
					<br />
					<span>This text should be rendered White Text on Black Background in light mode.</span>
				</div>
			</div>
		</div>
	</foreignObject>
</svg>

こんな感じで、CSS VariablesとDark Mode対応を組み合わせた、よくある感じのセットアップになってます(インデントが雑で申し訳ないです、これの実験中にDevToolsのemulationだとうまく切り替わらないことに偶然気がついた関係でデバッグの修正忘れて.rootにしてますが、たぶんふつうに:rootが使えると思います)。

あとはこれを <img src="hogehoge.svg" /> で普通に画像として読み込めば完了です。

ぜひ応用して、GitHubのREADMEをもっと素晴らしく、読みやすいものにしていきましょう!!
(くれぐれもSVGにテキストを入れるならaltの設定を忘れずに!!)

おわり