highlight.jsで長いコードを折りたたむ

highlight.js でコードが長い場合折りたたみ機能を付けるメモです。

私のサイトではコードハイライトに highlight.js を使用していますが、全ての行が表示されるので長いコードは折りたたむようにしました。

サンプル

一番下のCSS のコードをご覧ください。

highlight.jsの導入

先に highlight.js の導入方法を簡単に説明します。

ダウンロード

highlight.js

ダウンロードする時に使用したい言語でカスタマイズすることができます。

読み込む

<link href="js/highlight/atom-one-light.css" rel="stylesheet" type="text/css">
<script src="js/highlight/highlight.pack.js"></script>

jQuery プラグインではないのでこれだけで動作します。

highlight.js で提供している API を使って処理させているのでサンプルにある hljs.initHighlightingOnLoad() は不要です。

JavaScriptの記述

jQuery版

$('pre>code:not(.hljs)').each(function(i, elem) {
    hljs.highlightBlock(elem);
    $(elem).css({
        'max-height': 'none'
    });
    if (!$(elem).hasClass('noclose') && $(elem).height() - 300 > 100) {
        $(elem).attr('data-orgheight', $(elem).height()).attr('data-viewmode', 'is_close');
        $(elem).parent('pre').append('<div class="hljs_more"></div>');
    }
});
$(document).on('click', '.hljs_more', function() {
    var elem = $(this).prev();
    if (elem.attr('data-viewmode') == 'is_close') {
        elem.attr('data-viewmode', 'is_open').css('height', $(this).prev().attr('data-orgheight'));
    } else {
        elem.attr('data-viewmode', 'is_close').css('height', 300);
    }
});

この例では高を 300px にした時に隠れる長さが 100px 以上あるコードを折りたたみます。微妙な高さだからたたみたくないなぁという場合は code に .noclose を付けておくとスルーします。

ネイティブJavaScript版

個人的な事情でネイティブでも書きました。これは jQuery やめられなくなるね!Vue 完全に移行すればもっと短くなるはずだけど…。

(function() {
    var codelist = document.querySelectorAll('pre>code:not(.hljs)');
    for (var i = 0; i < codelist.length; i++) {
        var elem = codelist[i];
        hljs.highlightBlock(elem);
        elem.style.maxHeight = 'none';
        var height = Math.floor(elem.clientHeight);
        if (!elem.className.match(/\bnoclose\b/) && (height - 300) > 100) {
            elem.setAttribute('data-orgheight', height);
            elem.setAttribute('data-viewmode', 'is_close');
            var eClose = document.createElement('div');
            eClose.className = 'hljs_more';
            elem.parentElement.appendChild(eClose);
            eClose.addEventListener('click', function(e) {
                var elem = e.target.parentElement.childNodes[0];
                if (elem.getAttribute('data-viewmode') == 'is_close') {
                    elem.style.height = elem.getAttribute('data-orgheight') + 'px';
                    elem.setAttribute('data-viewmode', 'is_open');
                } else {
                    elem.style.height = '300px';
                    elem.setAttribute('data-viewmode', 'is_close');
                }
            }, false);
        }
    }
})();

スタイルを追加で記述

highlight.js のテーマファイルでも自分の CSS でもどこでも良いのですべて読むボタン用のスタイルを追加します。予め max-height を設定しておいて処理開始時に削除することで完了までのブレを少なくしています。

pre>code:not(.hljs) {
    max-height: 300px;
}
.hljs {
    overflow-y: hidden;
    transition: .8s height;
    position: relative;
}
.hljs[data-viewmode="is_close"] {
    overflow: hidden;
    height: 300px;
}
.hljs_more {
    position: relative;
    margin-top: -1px;
    background: #ffffff;
    border: 1px solid #eee;
    border-top: 0px;
    border-radius: 0 0 4px 4px;
    color: #2f9bb4;
    line-height: 30px;
    text-align: center;
    cursor: pointer;
}
.hljs:not([data-viewmode]) .hljs_more {
    display: none;
}
.hljs[data-viewmode="is_close"] ~ .hljs_more::after {
    content: '▼コードを全て見る';
}
.hljs[data-viewmode="is_open"] ~ .hljs_more::after {
    content: '▲コードを閉じる';
}

このサイトでは「atom-one-light.css」というテーマを使用しているのでそれに合わせていますが、適用に調整してみてくださいね(•ө•)ノ♪
閉じている時は.is_closeというクラスを付けているので、それを使ってコード本体やすべて読むボタンにアニメーション効果を付けることが出来ます。