ぐっちょむの開発にっき

プログラミングの話をするブログ

はてなブログの見た目を綺麗にした話

僕はフロントエンドエンジニアと名乗ったりUI/UXを構築しているとかWebの画面を作っているなどと自己紹介する人間なので、もちろんその当人のブログは綺麗で使いやすくなければいけないと数年間思っていました。

というわけで今回ははてなブログの見た目を綺麗にするにあたって苦労した点と楽勝だった点をお送りします。

綺麗にする前と後の比較

PC版はもともと綺麗な気がしたので、主にレスポンシブデザインによるスマートフォン表示の改善を頑張りました。

レスポンシブデザイン

f:id:gutcho:20170929101708p:plainf:id:gutcho:20170929101657p:plain
はてブロのモバイル版は何もしないとめちゃくちゃ殺風景なのでまずはレスポンシブモードをオンにしてユーザー定義のCSSを当てられるようにします。

開閉式のサイドバー

f:id:gutcho:20171027131235g:plainf:id:gutcho:20170929101702p:plain
通常のモバイル版だとブログ下端に現れるサイドバーですがボタンによる開閉式としました。もちろんno jQueryでstatelessなJavaScriptです。

はてブコメント欄

f:id:gutcho:20170929101724p:plainf:id:gutcho:20170929101719p:plain
はてなブックマークを垣間見れるブコメ欄、これ綺麗にするのにめちゃくちゃ苦労しました。

メニューボタンで開閉するメニューは15分で仕上がってしまい拍子抜けでしたが、ブコメ欄の改修はiframe罰非同期罰を食らったため2時間分の歯ごたえがありました。

カスタマイズの手順

f:id:gutcho:20171027200555p:plainf:id:gutcho:20171027200605p:plainf:id:gutcho:20171027200612p:plain
赤丸で囲われた部分を左から順にクリックしていきブログのヘッダおよびフッタのカスタマイズ画面に進みます。この2つにはHTMLを注入できるのでつまりCSSもJavaScriptも記述可能なのです。

なお、これからソースコードを交えた解説を行いますがあくまで僕の環境においての検証しか行なっておりませんのでもし皆様のブログに適用する際にはこの記事の内容をよしなに読み替えてください。

ソースコードと解説

開閉式のサイドバー

まずはソースをまるごと。これをヘッダのカスタム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);

以下、手順をおさらいです。

  1. id指定でスタイルタグ(<style id="js-bukome-style">)への参照を取得。
  2. ページ内に複数あるブコメ欄のiframeへの参照をclass名指定で全て取得。
  3. iframeへの参照を格納したオブジェクトをreduceメソッドの使用可能なArrayに変換。
  4. スタイル注入失敗時にtrueを返すreduceメソッドの初期値として第二引数にfalseを指定。
  5. 上書きに失敗するとtrueを返すoverwrite関数を配列の各要素に適用。
  6. reduceメソッドがtrueを返した場合、指定した間隔を置いてloop関数を再帰呼び出し。
  7. 全てのiframeに対してスタイル注入が成功するまで手順3-6の繰り返し。

感想

0からブログを作るよりは楽かなと思いました。