このブログでもダークモードに対応してみました!
ども、どもども。
このブログも運営し始めてもうすぐ7年になろうとしています。
その前身となるブログなども含めると、もう20年以上、このように書いていることになります。
さすがにここまで長く運営していると、世の中も変わってきます。
というわけで、少し前から対応しているウェブサイトやアプリも増えた「ダークモード」に、このブログも対応してみました!
なんだか大事のように書いていますが、やってみたかったというのが本音ですね(苦笑)
この記事では、その手順とか対応内容をご紹介したいと思います。
それではさっそく参りましょうー!
ダークモードとは?
ダークテーマとか、ナイトモードなんて呼ばれたりもしていますが、ウェブサイトやブログ、アプリなんかの基調色を「黒っぽい感じ」にする配色デザインのことですね。
暗めの部屋で見る場合など、「目に優しく読みやすい」とか「バッテリーの消費が抑えられる」とか言われています。
それらの真偽の程は分かりませんが、以下の記事などでも検証されています。
最近ワタシが書いている記事の内容にいわゆる技術的なことが増えてきたこともあり、そのような場合は読みやすいのかなと今回の導入に踏み切りました。(ちなみにワタシ自身は読みやすいとか読みにくいとか思ったことがないです汗)
ダークモード用のCSSを用意
まずはダークモードで閲覧する際のCSSを用意しました。
これは特別難しいことなく、bodyタグに class="dark" をつけて対応しました。
body.dark {
background-color: #1c1c1e;
color: #f2f2f2;
}
上記はその一部ですが、ダークモードではいわゆる黒背景(#000)に白文字(#fff)としないほうが良いという記事を見かけ、上記のベースカラーにしています。
黒と白ではコントラスト比が高く、それではあまり意味がないですもんね。
使用した配色はこんな感じにしました。
ダークモードにあわせ、画像も調整
配色だけでなく画像にもグレースケールを適用しました。
ここは好みが分かれるのかもしれませんが、ひとまずワタシは25%に設定、あわせて「はてなブログカード」などへの対応としてiframeも適用するようにしています。
.dark img,
.dark iframe {
filter: grayscale(25%);
}
OSの設定にあわせダークモードのCSSを適用する
先ほど用意したダークモード用CSSを、OSの設定にあわせ適用するようJavaScript(jQuery)を用意しました。
$(function(){
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
$('body').addClass('dark').css('transition','0.3s');
$('body').removeClass('light').css('transition','0.3s');
}else{
$('body').addClass('light').css('transition','0.3s');
$('body').removeClass('dark').css('transition','0.3s');
}
});
JavaScriptで prefers-color-scheme というメディア特性を取得し、darkだったらbodyに class="dark" をつける、そうじゃなければ class="light" をつける感じにしています。
これでひとまずダークモードの対応は完了ですが、もうちょっと手を加えていこうと思います。
モードの切り替えスイッチを用意
OSではダークモードを選択しているけれど通常モードで読みたい、あるいはその逆のパターンもあるでしょう。
というわけで、通常モードのCSSとダークモードのCSSを切り替えるスイッチを用意しました。
スイッチはcheckboxを使用して、ONならダークモードとなるようにしました。
<input type="checkbox" name="view-mode" id="view-mode" />
<label for="view-mode">mode: </label>
$(function(){
$('input[name="view-mode"]').change(function(){
if($(this).is(':checked')) {
$('body').addClass('dark').css('transition','0.3s');
$('body').removeClass('light').css('transition','0.3s');
} else {
$('body').addClass('light').css('transition','0.3s');
$('body').removeClass('dark').css('transition','0.3s');
}
});
});
ただこのままだと、ページを移動したときにその都度、スイッチを操作しなければなりません。
そこで、checkboxの設定をCookieに保存するようにしました。
モードの設定をCookieに保存
Cookieの保存には jQuery.cookie を使用し、モードを切り替えるたびにCookieを保存するようにしました。
(ダークモードのときにON、通常モードのときにOFFを保存しています)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
$(function(){
$('input[name="view-mode"]').change(function(){
if($(this).is(':checked')) {
$('body').addClass('dark').css('transition','0.3s');
$('body').removeClass('light').css('transition','0.3s');
$.cookie('dark', 'on', { expires: 365, path:'/'});
} else {
$('body').addClass('light').css('transition','0.3s');
$('body').removeClass('dark').css('transition','0.3s');
$.cookie('dark', 'off', { expires: 365, path:'/'});
}
});
});
また、ページを読み込んだ際にこのCookieを取得してモードを切り替えるように、メディア特性による分岐の部分に処理を追加、あわせて同じ処理をしているところを関数にまとめました。
ここまでのJavaScriptは以下のようになりました。
$(function(){
// 処理を関数にまとめる
function dark(){
$('body').addClass('dark').css('transition','0.3s');
$('body').removeClass('light').css('transition','0.3s');
$('input[name="view-mode"]').prop('checked', true);
$.cookie('dark', 'on', { expires: 365, path:'/'});
}
function light(){
$('body').addClass('light').css('transition','0.3s');
$('body').removeClass('dark').css('transition','0.3s');
$('input[name="view-mode"]').prop('checked', false);
$.cookie('dark', 'off', { expires: 365, path:'/'});
}
// メディア特性による分岐と、Cookieの内容による分岐処理
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
if($.cookie('dark') === 'off'){
light();
}else{
dark();
}
}else{
if($.cookie('dark') === 'on'){
dark();
}else{
light();
}
}
// スイッチを操作した際の処理
$('input[name="view-mode"]').change(function(){
if($(this).is(':checked')) {
dark();
} else {
light();
}
});
});
モード切り替えスイッチの装飾
checkboxのままだと味気ないので、CSSで装飾し、モード切替にあわせてアニメーションするようにしました。
アイコンはFontAwesomeを利用していますが、疑似要素(::after)を利用していたからなのか、JavaScriptのクリック操作でうまくアニメーションさせることができませんでした。
そこで、checkboxのクリックにあわせその親要素となる部分にclassを追加、そのclassを起点に擬似要素をアニメーションさせるようにしました。
以下のようにすることでこのアニメーションを実現しました。
<div class="mode-change">
<input type="checkbox" name="view-mode" id="view-mode" />
<label for="view-mode">mode: </label>
</div>
$(function(){
$('input[name="view-mode"]').change(function(){
if($(this).is(':checked')) {
$('.mode-change label').addClass('dark-move'); // labelにclassを追加する
$('.mode-change label').removeClass('light-move');
dark();
} else {
$('.mode-change label').removeClass('dark-move');
$('.mode-change label').addClass('light-move'); // labelにclassを追加する
light();
}
});
});
/* ダークモード選択時のCSS部分(通常モードも同じ処理) */
.light .mode-change input[type=checkbox] + label::after {
display: inline-block;
font-family: 'Font Awesome 5 Free';
content: "\f185";
font-weight: 900;
color: orange;
}
.mode-change input[type=checkbox] + label.dark-move::after {
animation: dark 0.5s linear;
}
@keyframes dark {
0% {
content: "\f185";
color: orange;
transform: translateX(0px);
}
50% {
content: "\f185";
color: orange;
transform: translateX(50px);
}
100% {
content: "\f186";
color: yellow;
transform: translateX(0px);
}
}
</style>
Safariでの動作不具合?を回避
Mac Chromeでは問題のなかった上記のアニメーションですが、Safari(モバイル/デスクトップ)ではアニメーションがスタートすると同時にアイコンが変わってしまうという現象になってしまいました。
CSSにあるkeyframeの0%部分では確かにアイコンを指定しているのですが、それが反映されません。
Safari、iOS、JavaScript、keyframe、アニメーションなどさまざまなキーワードで情報を収集してみるも原因は分かりませんでした…。
※Safariと書きましたが、iPhoneのChromeでも同じように動作したので、他の原因と思います。
アニメーションの状況を見る限り、スイッチでbodyに追加されるclassによるアイコン変化が先に適用され、そこからアニメーションがスタートするという順番になっていると思われます。
これがなかなかクリアできなかったのですが、JavaScriptでbodyにclassをつける部分を、setTimeoutで遅らせることで解消することにしました。最終的なJavaScriptは以下です。
JavaScript部分の完成
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
<script>
$(function(){
function dark(){
$('body').addClass('dark').css('transition','0.3s');
$('body').removeClass('light').css('transition','0.3s');
$('input[name="view-mode"]').prop('checked', true);
$.cookie('dark', 'on', { expires: 365, path:'/'});
}
function light(){
$('body').addClass('light').css('transition','0.3s');
$('body').removeClass('dark').css('transition','0.3s');
$('input[name="view-mode"]').prop('checked', false);
$.cookie('dark', 'off', { expires: 365, path:'/'});
}
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
if($.cookie('dark') === 'off'){
light();
}else{
dark();
}
}else{
if($.cookie('dark') === 'on'){
dark();
}else{
light();
}
}
$('input[name="view-mode"]').change(function(){
if($(this).is(':checked')) {
$('.mode-change label').addClass('dark-move');
$('.mode-change label').removeClass('light-move');
setTimeout(function(){
dark();
},250);
} else {
$('.mode-change label').removeClass('dark-move');
$('.mode-change label').addClass('light-move');
setTimeout(function(){
light();
},250);
}
});
});
</script>
ダークモード対応のまとめ
以上で今回のダークモード対応がすべて完了となりました!
実施したのはこんな感じです。
- Cookieにモードの値が保存されている場合はそれでモードを切り替える
- 保存されていない場合は、閲覧デバイスのメディア特性 prefers-color-scheme をJavaScriptで判別し、OS側の設定にあわせてモードを切り替える
- モードの自動切り替え後にモードの変更を行った場合はそれをCookieに保存する
- モード切り替えはcheckboxを利用する
- checkboxのCSS装飾にアニメーションを追加する
軽い気持ちで始めたダークモード対応だったのに思いのほか時間がかかりましたが、そのおかげでお正月休みはヒマしませんでした(笑)
ダークモードの配色についてはまだ検討の余地があるなーと思いますが、ひとまずカタチになってよかったです!