狐の王国

人は誰でも心に王国を持っている。

Bootstrap で手抜きしながらオシャレなはてなブログのデザインを作る方法

というわけではてなブログに移行しようと思ったんだけれども、どうも気に入ったデザインがない。はてなダイアリーでもそれなりにカスタマイズして作ってたのだが、そのまま使えるわけもないしデザイン自体も古くなってしまってる。かといってゼロからデザインを起こす暇など……ってマテ、俺達には Bootstrap があるじゃないか!

Bootstrap ってなに?

いわゆる CSS フレームワークです。Twitter が作って自分たちで使ってるやつです。

なにができるの?

HTMLにクラス属性をいろいろ足してやるだけでいい感じに見た目を整えてくれるいい奴なんです。

example

例えばこんな感じ。

ボタンとか
<button type="button" class="btn btn-default">btn-default</button>
<button type="button" class="btn btn-primary">btn-primary</button>
<button type="button" class="btn btn-success">btn-success</button>
<button type="button" class="btn btn-info">btn-info</button>
<button type="button" class="btn btn-warning">btn-warning</button>
<button type="button" class="btn btn-danger">btn-danger</button>

こんな感じに書くだけで、

こんな感じになっちゃうんです!

これはてなブログで使えるの?

えっといろいろ問題はありますが一応使えます。というか使えました。

何事も先人というのはいるもので、以下のサイトを参考にしました(感謝)。

blog.sfpgmr.net

blog.manaten.net

せっかくなのでどうやって使うかというのをここで共有しておく。

カラムの設定

上記サイトにあるように、カラムを作るには less で書く必要がある。Bootstrap 自体が less で書かれてるからなのだが、そのうち sass に移行するとかなんとか。とりあえず現時点の最新版である 3.3.6 は less で書かれてるので less で書いていく。どうせあとから sass に変換もできるはず。

getbootstrap.com

Bootstrap のサイトからソースコードを zip でダウンロードしてきて、適当なディレクトリに展開。

$ wget https://github.com/twbs/bootstrap/archive/v3.3.6.zip
$ unzip v3.3.6.zip
$ touch hatenablog.less
$ ls
bootstrap-3.3.6 hatenablog.less

bootstrap-3.3.6 というディレクトリ以下に展開されたソースコードと、hatenablog.less(名前は何でもいい)が見えます。 この hatenablog.less を編集していく。

@import "bootstrap-3.3.6/less/bootstrap.less";
#main {
    .make-md-column(9);
}
    
#box2 {
    .make-md-column(3);
}

#footer, #bottom-editarea {
    .make-md-column(12);
}

これで2カラムでサイドバーが本文の1/3、フッタは全体幅の構成になる。

@import "bootstrap-3.3.6/less/bootstrap.less";
#main {
    .make-md-column(9);
    background-color: blue;
}
    
#box2 {
    .make-md-column(3);
    background-color: yellow;
}

#footer, #bottom-editarea {
    .make-md-column(12);
    background-color: green;
}

とでもして色分けされてることを確認するといい。

そして less ファイルを css ファイルにコンパイルする。

$ gem install less
$ lessc hatenablog.less > hatenablog.css

動作確認

さて便利な Bootstrap なのだが、どうもはてなブログCSS 貼り付けは容量制限があるようで、Bootstrap を含めた CSS をアップロードすることは難しいようだ。

というわけで以下のサイトを参考に Google Drive を利用する。

wedges.hatenablog.com

アップロードしたら、はてなブログのデザインCSSには、

/* Responsive: yes */
@import url('https://www.googledrive.com/host/0Bxp6_1slWwEfNlhPY1N5MmpPZGM/hatenablog.css');

とだけ書いておく。 URL の途中にある謎の文字列は人それぞれ違うので上記サイトを参考にして欲しい。

さらに一部の機能は JavaScript を使うので、CDN で配布されてる bootstrap.js を使う。Bootstrap は jQuery 必須なので、それも CDN のものを使う。

はてなブログのフッタあたりにでも、

<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha256-KXn5puMvxCw+dAYznun+drMdG1IFl3agK0p/pqT9KAo= sha512-2e8qq0ETcfWRI4HJBzQiA3UoyFk6tbNyG+qSaIBZLyW9Xf3sWZHN/lxe9fTh1U45DpPf07yj94KsUHHWe4Yk1A==" crossorigin="anonymous"></script>

と入れておく。

ついでに Bootstrap で使うアイコンフォントも CDN から import してしまおう。

@import "bootstrap-3.3.6/less/bootstrap.less";
@icon-font-path: 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/fonts/';

としておけばテストでも本番でもそのままフォントファイルを読んでくれる。

デザイン設定画面にあるスマートフォン向けのタブに「レスポンシブデザイン」のチェックボックスがあるので、それをオンにしておくとスマホ向けにも同じデザインで表示される。

ちなみに markdown を使いたいのではてなブログのサンプルエントリーmarkdownに書き直してちょっと付け加えたものを置いておいた。

デザイン開発用のtips

さて less は以上のようにコンパイルしないといけないので動作確認が面倒くさそうだが、実は less コンパイラJavaScript で書かれている。若干遅いが充分実用的だ。

というわけで上のサンプルエントリーを放り込んだ記事をそのまま wgetcurl でダウンロードして、

<link rel="stylesheet" type="text/css" href="http://blog.hatena.ne.jp/-/blog_style/13208692334729909600/2cf1108ea98e4d24edaf9abdb60ae1d97e804f1d"/>    <script>    </script>

というようなところを、

      <link rel="stylesheet" type="text/less" href="hatenablog.less"/>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/less.js/2.5.3/less.min.js">    </script>

と書き換える。これでこのファイルをブラウザに読ませれば表示されるはず。あとはエディタで保存したら自動でブラウザをリロードする設定でもしておけば非常に捗る。 作り始めた頃は記事も少なくてサイドバーのデザインなどがうまくいかないので、他人の記事をダウンロードしてデザインに使うなどするとさらに捗る。

ナビゲーションバー

さてせっかくヘッダにいろいろ追加できるので、ナビゲーションバーを追加することにしよう。

はてなブログのデザイン設定からヘッダのところを開き、うちでは以下のように入力してみた。

<nav class="navbar navbar-default" style="border-radius: 0px;">
  <div class="container-fluid">
    <div class="navbar-header">
      <button type="button"
              class="navbar-toggle collapsed"
              data-toggle="collapse"
              data-target="#navbar-collapse-1"
              aria-expanded="false">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="http://www.foxking.org/"><span class="glyphicon glyphicon-king"></span> 狐の王国</a>
    </div>
    <div class="collapse navbar-collapse" id="navbar-collapse-1">
      <ul class="nav navbar-nav">
        <li><a href="http://koshian.hateblo.jp/"><span class="glyphicon glyphicon-link"></span> Blog</a></li>
        <li><a target="_blank" href="http://koshian.hatenablog.com/"><span class="glyphicon glyphicon-link"></span> Tech Log</a></li>
      </ul>
      <ul class="nav navbar-nav navbar-right">
        <li role="separator" class="divider"></li>
        <li><a target="_blank" href="http://tumblr.foxking.org"><i class="fa fa-tumblr"></i> Tumblr</a></li>
        <li><a target="_blank" href="http://twitter.com/koshian"><i class="fa fa-twitter"></i>
 Twitter</a></li>
      </ul>
    </div>
  </div>
</nav>

Twitter/Tumblr のアイコンは font awesome を使用している。これも less ファイルに、

@import url('https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css');

と CDN から import してある。

配色

Bootstrap のナビゲーションバーを配色するツールになってる TWBSColor というサイトがあるので、そこを使うと簡単。

work.smarchal.com

テーブルを Bootstrap らしく

さて細かいデザインは自力でやるとしても、table なんかの表示は Bootstrap を使ってラクがしたい。

.entry-content {
    table {
        &:extend(.table all);
        &:extend(.table-striped all);
        &:extend(.table-hover all);
        width: auto;
    }
}

こんな感じで table クラスを table タグに継承してやればうまく行く。そのままだと width が 100% になっててウェブアプリならともかくブログには向かなそうだったので width を auto に上書き設定した。

配色は変数があるのでそれを使う。

@table-bg:                      transparent;
@table-bg-accent:               @accent-bg;
@table-bg-hover:                @accent-color;
@table-bg-active:               @table-bg-hover;
@table-border-color:            @heading-color;

他にも bootsrap-3.3.6/less/variables.less を見るといろいろな変数があるので、boottrap 本体を import した後にそれらの変数を書き換えてやるといろいろとカラーを変えられる。

検索フォーム

さてこれでだいぶ Bootstrap を活用できてる感じになってきたのだが、検索フォームのボタンに Bootstrap のスタイルがうまく適用できない。

#box2 {
    .search-module-input {
        &:extend(.form-control);
    }
    
    .search-module-button {
        &:extend(.btn);
        &:extend(.btn-default);
    }
}

としてしまえば一応適用されるのだが、フォームの横に検索ボタンが行かない。 しょうがないので提供されてる検索フォームを使うのをやめて、サイドバーに HTML を直接放り込むことにした。

<form class="search-form" role="search" action="/search" method="get">
  <div class="input-group">
    <input name="q" class="search-module-input form-control" value="" placeholder="ブログ内検索" required="" type="text">
    <span class="input-group-btn">
      <input value="検索" class="search-module-button btn btn-default" type="submit">
    </span>
  </div>
</form>

こんな感じで input-group をまとめてやれば綺麗に配置される。

サイドバーのリスト

アーカイブやらなにやらは、やはり Bootstrap の list-group を使ってうまく処理したい。

#box2 {
    .hatena-urllist,
    .urllist-with-thumbnails {
        &:extend(.list-group all);

        li,
        .urllist-item {
            &:extend(.list-group-item all);
        }
    }
}

こんな感じで綺麗に表示された。

だがここで問題が。はてなブログアーカイブのリスト表示は、その年や月に書いた記事の数が出るのだが、これが括弧でベタ書きされてて Bootsrap らしくない表示になってしまってる。数字を badge クラスの span で囲んであげれば綺麗に表示されるのだが……。

しょうがない、ここは JavaScript の出番といこう。

しかし、どうもこのアーカイブJavaScript で読み込まれるようになってるようで、その処理の後にやってもらわないといけない。しょうがないので load イベントで実行するようにしてみた。

window.addEventListener("load", function(){
    $('.hatena-urllist li').each(function() {
        var r = $(this).html().replace(/(\()(\d+)(\))(.*)/g,
                                       "<span class=\"badge\">$2</span></li>");
        $(this).html(r);
    });
}, false);

というような JavaScript を書いて css と同じように Google Drive に置いてみた。フッタの jquery の後ろに script タグを配置。

f:id:KoshianX:20160104214548p:plain

おお、かわいく表示されてる!

しかしどうもなあ……。本来この badge は右端に表示されるはずなのだが。ああ、そうか、数字が a 要素の内側にあるからか。これは兄弟要素にしてあげなくてはならない。

しかも三角マークで内側の要素を表示非表示を切り替えたり、現在開いてる記事の年月のところは開くなど細かい処理がある。要素を移動した後でもう一度その設定をしてあげなくては動作しないのだが、現在の日月などを把握するのがめんどくさい。

というわけではてなブログがデフォルトで読んでる hatenablog.js からそのへんの部分をパチってきて以下のように。

window.addEventListener("load", function(){
    /* from hatenablog.js (official) */
    function detectDate() {
        // archiveのとき, 一番上の日付から年を読む
        var page = window.location.pathname.split('/')[1];
        if (page === 'archive') {
            var $entry = $('section.archive-entry:first');
            if ($entry.length > 0) {
                var year_month_day = $entry.find('div.date > a > time').attr('datetime');
                var year_str = year_month_day.split('-')[0];
                var month_str = year_month_day.split('-')[1];

                return { year: year_str, month: month_str };
            }

            return null;
        }
    }

    function setupCalendar($archive) {
        var $selector = $archive.find('.js-archive-module-calendar-selector');

        var updateCalendar = function updateCalendar() {
            var $date = $selector.find('option:selected');
            var year = $date.data('year');
            var month = $date.data('month');
            $.ajax({
                type: 'get',
                url: URLGenerator.user_blog_url('/archive_module_calendar'),
                data: { month: month, year: year }
            }).done(function (res) {
                // days object
                $archive.find('.js-archive-module-calendar-container').html(res);
            });
        };

        $selector.change(function () {
            updateCalendar();
        });

        // 表示ページに合わせてカレンダーを初期化
        var date = detectDate();
        if (date) {
            $selector.val(date['year'] + ' ' + date['month']);
        }
        updateCalendar();
    };

    function setupDefault($archive) {
        var $open_year;
        var date = detectDate();
        if (date) {
            var year = date['year'];
            if (year) {
                var $year = $archive.find('li.archive-module-year[data-year="' + year + '"]');

                $open_year = $year.length > 0 ? $year : null;
            }
        }

        $open_year = $open_year || $('li.archive-module-year:first');
        $open_year.removeClass('archive-module-year-hidden');

        $archive.find('.archive-module-button').click(function (e) {
            e.preventDefault();

            var $year = $(this).parent('.archive-module-year');
            $year.toggleClass('archive-module-year-hidden');
        });
    }
    /* end hatenablog.js ( official ) */
    
    $('.hatena-urllist li').each(function() {
        var r = $(this).html().replace(/(\()(\d+)(\))(.*)/g,
                                       "<span class=\"badge\">$2</span></li>");
        $(this).html(r);
    });

    $('a .badge').each(function(){
        var p = $(this).parent()
        $(this).insertAfter(p);

    });

    $('.hatena-module-archive').each(function(){
        var archive = $(this);
        if (archive.data('archiveType') == 'calendar') {
                setupCalendar(archive);
            } else {
                setupDefault(archive);
            }
    });
}, false);

detectDate、setupCalendar、setupDefault の三つの関数をコピーさせてもらい、数字を badge クラスの span 要素に書き換え、badge クラスは単純に親クラスの兄弟要素に。そしてアーカイブの表示非表示を切り替える設定を施す setupDefault を実行し直す、という感じ。

f:id:KoshianX:20160104214547p:plain

これで見事に右側によってくれた。

そんな感じでできあがったうちのはてなブログ用デザインは、github に置いてあるのでもしよければ参考にしてくれたりツッコミ入れてくれたりするとうれしい。

github.com

え? 出来上がったデザインがタイトルと違って全然オシャレじゃないぞって? えーといやその、すみません……。

enjoy!

2016-11-25 追記

hatenablog.js が minify されるようになったようで hatenablog.js が動作しなくなっていた。

        if (Hatena.Diary.data('page') === 'archive') {

の部分を

        var page = window.location.pathname.split('/')[1];
        if (page === 'archive') {

と書き換えて動作するようになった。上のコードは修正済み。

Sugano `Koshian' Yoshihisa(E) <koshian@foxking.org>