ソースを並べて表示する@はてなブログ

この記事の余談。
同じことをやるふたつの方法の比較なので、ソースを並べて表示したかった。

ソース

HTML
<button id="toggle-h-v">ソースを横に並べる</button>     <!-- button for toggle -->
<div class="source-area-container">

<div class="source-area">   <!-- source-left -->
 <h5>Google Map</h5>
 >|php|
    ...
 ||<
</div>                      <!-- source-left -->

<div class="source-area">   <!-- source-right -->
 <h5>OpenStreet Map (OpenLayers API)</h5>
 >|php|
    ...
 ||<
</div>                      <!-- source-right -->

</div>  <!-- .source-area-container -->

<style id="source-area-style"></style>      <!-- style calculated by javascript -->

ソースコードスーパーpre記法(シンタックスハイライト)。
それぞれにタイトルを付けて、それも含めて横並びにしたいので、div.source-area でくくる。
ふたつの div.source-area を更に div.source-area-container でくくる。
後は、切り替えをするボタンと、動的に値を変えるスタイルを書き込むための style タグを記述。

javascript
window.addEventListener("DOMContentLoaded", function() {
    var btn = document.getElementById("toggle-h-v");
    btn.onclick = function() {
        var e = document.body.querySelector(".source-area-container");
        e.classList.toggle("source-area-container-h");      // クラスの切り替え

        var sty = document.getElementById("source-area-style");     // 計算した値を使うクラスを書き込む
        /*
            Element.classList.toggle() は boolean を返す(true:クラスを追加した)
        */
        if (e.classList.contains("source-area-container-h")) {
            sty.innerHTML = [
                ".source-area-container-h {                             ",
                "   width: " + window.innerWidth + "px;                 ",
                "   left: -" + e.getBoundingClientRect().left + "px;    ",
                "}                                                      ",
                ".source-area-container-h pre.code {                    ",
                "   max-height: " + (window.innerHeight - 24) + "px;    ",
                "}                                                      ",
            ].join("\n");
            btn.original_title = btn.innerHTML;
            btn.innerHTML = "縦に戻す";
        } else {
            sty.innerHTML = "";
            btn.innerHTML = btn.original_title;
        }
    };
});

切り替えのボタンでは、ふたつのソースのエリアをくるんでる div.source-area-container に、横並びのためのスタイルを書いたクラス .source-area-container-h をつけたり、外したりする。
追加するクラスは、ふたつの要素を横並びにするために、幅を 50% より小さくして display: inline-block を指定するのが主体。

記事のエリアをはみ出してウィンドウの幅いっぱいに div.source-area-container のサイズを広げたいのだけれど、値を計算する必要がある(calc() じゃ無理だよね?)ので、javascript で値を求めて、それを style 要素に書き出している。

対象は、.source-area-container-h の左端と幅、それと内側にあるソースコードの pre 要素の高さ。
これ以外のスタイルは、静的に書ける。

本当は Template String を使いたいところだけど、ここは互換性を気にしておく。

CSS
.source-area-container-h {
    position: relative;
    z-index: 999;
    background-color: white;
    border: 2px inset silver;
}
.source-area-container-h .source-area {
    width: 48%;
    display: inline-block;
    vertical-align: top;
}
.source-area-container-h .source-area:first-child {
    margin-right: 1em;
}
.source-area-container-h .source-area pre.code {
    overflow-y: auto;
    font-size: 12px;
}

.source-area-container .source-area h5 {
    margin-top: 1em;
}
#toggle-h-v {
    font-size: 90%;
}

主体は、.source-area-container-h がついている四つのルール。

  • .source-area-container-h …… ふたつのソースを抱える領域
    • 幅を広げるための相対位置
    • サイドバーを隠すために、上に持ってきて背景色をつける
    • 枠などをつけてみる
  • .source-area-container-h .source-area
    • 並べるために幅をそろえて、inline-block にする
    • 縦位置は、上端でそろえる
  • .source-area-container-h .source-area:first-child
  • .source-area-container-h .source-area pre.code
    • 文字を少し小さくして、
    • 縦方向のスクロールバー(高さは、javascript で求める)

後は、使ってるスタイルの h5 の margin-top が空き過ぎてたのと、ボタンの文字が小さかったのを調整。

実は、手こずった

はてな記法を使っているのに、それぞれのソースについている見出しに、なぜ見出し記法 *** を使わずに、h5 を直接 書いているか、という話。

最初はこんな感じで、見出し記法を使ってた。

** まずはソース
<div class="source-area-container">
<div class="source-area">   <!-- source-left -->
*** Google Map
 >|php|
    ...
 ||<
</div>                      <!-- source-left -->

<div class="source-area">   <!-- source-right -->
*** OpenStreet Map (OpenLayers API)
 >|php|
    ...
 ||<
</div>                      <!-- source-right -->
</div>  <!-- .source-area-container -->

軽くプロトタイプを書いてローカルで動作の検証をしてから記事を書き始めたのに、なんかうまく動かなくて、変だなー、変だなー、って。

はてな記法が展開された HTML がこちら(p タグなどを消して、インデントつけたりしてる)。

<div class="section">
    <h4>まずはソース</h4>
    <div class="source-area-container">
        <div class="source-area">       <!-- source-left -->
        |   <div class="section">
        |       <h5>Google Map</h5>
        |       <pre class="code lang-php" data-lang="php" data-unlink="">
        |           ...
        |       </pre>
        |   </div>
        |   <div class="source-area">       <!-- source-right -->
        |   |
        |   |                           <!-- ※ EMPTY ! -->
        |   |
        |   </div>                          <!-- source-right -->
        |   <div class="section">
        |       <h5>OpenStreet Map (OpenLayers API)</h5>
        |       <pre class="code lang-php" data-lang="php" data-unlink="">
        |           ...
        |       </pre>
        |   </div>
        </div>                          <!-- source-left -->
    </div>
</div>

見出し記法は、見出しのタグ h* をつけるだけではなく、適当な範囲を div.section でくくる、などしてくれている。
そのせいで、兄弟になるはずの div.source-area が入れ子になり、右側に来るはずの div はソースを書いた pre が子要素になっていないという。

そりゃあ、動かないわけだ (´・ω・`)

もうひとつの仕込み

を同じことをやっている部分の順番を合わせて、並べたソースを同時スクロールというのも考えたのだけれど、微妙に合わない。
なので、コメントに入れた ■([A]) という部分をクリックすると、もう片方のソースで該当箇所が見えるようにスクロールするようにしてみた。

  • ソース領域の該当するコメントを探して、目印をつけたタグでくくる
  • イベントは Bubbling するので、ソース領域 pre.code の onclick のイベントハンドラで拾う
  • もう片方のソースから同じ記号を持っているところを探して、見えてなかったらそこまでスクロール
  • なんかやってますよ的なアピールとして、コメントの色を変えてみる

ある要素が見えるようにするには、Element.scrollIntoView という便利なメソッドがあるのだけれど、スクロールできる領域が入れ子になってると、外側の body までもスクロールしてくれるので、微妙に見た目が悪いので、自前で scrollTop を指定してるとかなんとかあるけれど、offsetParent とかあって微妙に面倒だった。