はてなブログの見た目を綺麗にした話
僕はフロントエンドエンジニアと名乗ったりUI/UXを構築しているとかWebの画面を作っているなどと自己紹介する人間なので、もちろんその当人のブログは綺麗で使いやすくなければいけないと数年間思っていました。
というわけで今回ははてなブログの見た目を綺麗にするにあたって苦労した点と楽勝だった点をお送りします。
綺麗にする前と後の比較
PC版はもともと綺麗な気がしたので、主にレスポンシブデザインによるスマートフォン表示の改善を頑張りました。
レスポンシブデザイン
開閉式のサイドバー
はてブコメント欄
メニューボタンで開閉するメニューは15分で仕上がってしまい拍子抜けでしたが、ブコメ欄の改修はiframe罰と非同期罰を食らったため2時間分の歯ごたえがありました。
カスタマイズの手順
なお、これからソースコードを交えた解説を行いますがあくまで僕の環境においての検証しか行なっておりませんのでもし皆様のブログに適用する際にはこの記事の内容をよしなに読み替えてください。
ソースコードと解説
開閉式のサイドバー
まずはソースをまるごと。これをヘッダのカスタムHTML記述欄に書きます。
<!DOCTYPE html> <!-- スマホ版の追従メニュー --> <input class="sp-menu-toggle" id="sp-menu-toggle" type="checkbox"> <label class="sp-menu-toggle" for="sp-menu-toggle">menu</label> <div class="sp-menu-wrapper" id="sp-menu-wrapper"></div> <style> .sp-menu-toggle { display: none; } @media screen and (max-width: 960px) { label.sp-menu-toggle { display: block; position: fixed; right: 12px; bottom: 36px; color: #fff; background: #ea80a8; padding: 4px 12px; border-radius: 9px; opacity: 0.75; z-index: 9999; } .sp-menu-wrapper { box-sizing: border-box; position: fixed; top: 0; right: -240px; width: 240px; padding: 12px; background: #fff; height: 100%; overflow: scroll; -webkit-overflow-scrolling: touch; transition: 0.3s; box-shadow: 0 0 12px 4px rgba(127, 127, 127, 0.3); visibility: hidden; z-index: 100; } input.sp-menu-toggle:checked ~ .sp-menu-wrapper { right: 0; visibility: visible; } } </style> <script type="text/javascript"> ;(function(window) { window.onload = replaceSidebar window.onresize = replaceSidebar function replaceSidebar() { var wrapper = document.getElementById('sp-menu-wrapper'); var sidebar = document.getElementById('box2'); var menu = document.getElementById('box2-inner'); if (window.innerWidth < 960) { wrapper.appendChild(menu); } else { sidebar.appendChild(menu); } } })(window); </script>
こちらの構成としては
- 開閉メニュー用のマークアップ
- 開閉メニューのスタイル定義
- サイドバーのDOMを開閉メニュー内に移設するスクリプト
となっております。なかでも見所はこちらのCSS。
input.sp-menu-toggle:checked ~ .sp-menu-wrapper { right: 0; visibility: visible; }
皆さんこの間接セレクタfoo ~ bar
をお使いでしょうか?直後の要素を指定するfoo + bar
や1階層直下を指定するfoo > bar
は見かけますかね。間接セレクタを使うと同じDOMツリー内の後続要素を指定することができます。上記CSSはチェックされたinput要素に後続する.sp-menu-wrapper
を指定しています。
対応するマークアップはこちらです。
<input class="sp-menu-toggle" id="sp-menu-toggle" type="checkbox"> <label class="sp-menu-toggle" for="sp-menu-toggle">menu</label> <div class="sp-menu-wrapper" id="sp-menu-wrapper"></div>
先述のCSSと組み合わせることによりチェックが入っているか否かをフラグにJSで状態を保持することなくサイドバーの開閉を実現できます。
ブコメ欄へのスタイル反映
こちらはフッタに記述しています。まずは全体のソース
<!DOCTYPE html> <!-- ブコメ欄のスタイル上書き --> <style id="js-bukome-style"> #hatena-bookmark-container { background: #fffee4 !important; border: none !important; overflow: scroll !important; } #hatena-bookmark-container a { color: #8a5c2e !important; } #hatena-bookmark-container .hatena-bookmark-title a, #hatena-bookmark-container .hatena-bookmark-addcomment a { color: #fff !important; } #hatena-bookmark-container .hatena-bookmark-footer * { color: #ea80a8 !important; } .hatena-bookmark-title { background: #4db399 !important; border: none !important; } .hatena-bookmark-addcomment { background: #ea80a8 !important; border-radius: 9px !important; border: none !important; } .hatena-bookmark-addcomment a { border: none !important; } .hatena-bookmark-item { font-size: 75% !important; } .hatena-bookmark-footer * { margin-top: 0.3em !important; } .hatena-bookmark-showall-comment { float: none !important; text-align: right !important; } </style> <script type="text/javascript"> ;(function(interval) { var iframes = document.getElementsByClassName('hatena-bookmark-comment-iframe'); (function loop() { if (Array.apply(null, iframes).reduce(overwrite, false)) { setTimeout(loop, interval); } })(); function overwrite(failure, iframe) { if (failure) { return true; } var container = iframe.contentDocument.getElementById('hatena-bookmark-container'); if (!failure && container) { var style = document.getElementById('js-bukome-style'); iframe.contentDocument.head.appendChild(style.cloneNode(true)); iframe.contentDocument.body.style.fontFamily = "Helvetica, Arial, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', メイリオ, Meiryo, sans-serif" console.log('ブコメCSSの上書きに成功'); return false; } console.log('ブコメCSSの上書きを再試行'); return true; } })(640); </script>
最初は普通にCSSを定義すればブコメ欄にも適用されると考えていたんですが、どうもブログテーマのCSSカスタマイズからではブコメ欄のスタイルを変更できない。何故だろうとHTMLをよく見てみると、ブコメ欄はiframeによる独立したページな上に非同期でコンテンツが読み込まれていたのです。これは大変でした。
非同期読み込みの完了イベントをlistenしようと試みましたがイベントが発火しないため断念し、結局ブコメ欄にスタイルが当たるまで処理の再試行を繰り返すコードとなりました。
;(function(interval) { // styleタグへの参照を取得 var style = document.getElementById('js-bukome-style'); // 全てのブコメ欄iframeへの参照を取得。 var iframes = document.getElementsByClassName('hatena-bookmark-comment-iframe'); // 再帰呼び出し可能な関数を定義 (function loop() { // "Array like object"をArrayに変換しreduceメソッドにoverwrite関数と失敗フラグ初期値のfalseをセット if (Array.apply(null, iframes).reduce(overwrite, false)) { setTimeout(loop, interval); // 失敗フラグがtrueを返した場合この関数を指定時間後に再帰呼び出し } })(); // 失敗フラグと個別のiframeを受け取り上書き処理を行う関数 function overwrite(failure, iframe) { // 失敗フラグがtrueもしくは"#hatena-bookmark-container"要素が存在しない場合 if (failure || !iframe.contentDocument.getElementById('hatena-bookmark-container')) { console.log('ブコメCSSの上書きを再試行'); return true; // 失敗フラグを返す } // styleタグを複製してiframe内のheadタグに挿入 iframe.contentDocument.head.appendChild(style.cloneNode(true)); // 何故かこれが必要なのでfont-familyを指定 iframe.contentDocument.body.style.fontFamily = "Helvetica, Arial, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', メイリオ, Meiryo, sans-serif" console.log('ブコメCSSの上書きに成功'); return false; } })(640);
以下、手順をおさらいです。
- id指定でスタイルタグ(
<style id="js-bukome-style">
)への参照を取得。 - ページ内に複数あるブコメ欄の
iframe
への参照をclass名指定で全て取得。 iframe
への参照を格納したオブジェクトをreduce
メソッドの使用可能なArray
に変換。- スタイル注入失敗時に
true
を返すreduce
メソッドの初期値として第二引数にfalseを指定。 - 上書きに失敗すると
true
を返すoverwrite
関数を配列の各要素に適用。 reduce
メソッドがtrue
を返した場合、指定した間隔を置いてloop
関数を再帰呼び出し。- 全てのiframeに対してスタイル注入が成功するまで手順3-6の繰り返し。
感想
0からブログを作るよりは楽かなと思いました。