整理と雑記

気が向いたら書く

【Javascript】はてなブログの目次を開閉式にする

はてなブログの目次を開閉するようにする。使用するのはJavascriptとcssのみ。

サンプル

完成のイメージは以下。

やり方

以下のJavascriptをヘッダーかフッターに貼り、cssをデザインcssに貼る。cssはテーマによって要調整。Javascriptの各関数の説明は後述。

Javascript・css

Javascript

<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
<script>
addEventListener("DOMContentLoaded", function() {
    const cls = '.table-of-contents';
    const header = '<div class="accordion"><span>目次</span><i class="fa fa-chevron-down"></i></div>';
    createAccordion(cls,header);
});

this.createAccordion = function(cls,header){ 
    const contents = document.querySelectorAll(cls);
    let j = 0;
    for (let i in [...contents]) {
        const content = contents[i];
        if (hasContent(content)) {
            const height = calcHeight(content);
            content.insertAdjacentElement('beforeBegin', createElementFromHTML(header));
            const accordionList = document.querySelectorAll('.accordion');
            setClickEvent(content,accordionList[j],height);
            j++;
        }
    }

    function hasContent(content) {
        return content.querySelectorAll('li:first-child').length;
    }

    function calcHeight(content) {
        content.classList.add('active');
        height = content.clientHeight + 'px';
        content.style.height = "0";
        content.classList.remove('active');
        return height;
    }

    function setClickEvent(content,accordion,height) {
        const arrow = accordion.querySelector('.fa-chevron-down')
        accordion.onclick = function(){
            toggle(content,arrow,height);
        }
    }

    function toggle(content,arrow,height) {
        if (content.classList.contains('active')) {
            slideUp(content,arrow);
        } else {
            slideDown(content,arrow,height);                
        }
    }

    function slideUp(content,arrow) {
        content.style.height = '0';
        arrow.style.transform = 'rotate(0)';
        content.addEventListener('transitionend', function () {
            content.classList.remove('active');
        }, {
            once: true
        });
    }

    function slideDown(content,arrow,height) {
        content.classList.add('active');
        arrow.style.transform = 'rotate(180deg)';
        setTimeout(function () {
            content.style.height = height;
        }, 0);
    }

    function createElementFromHTML(html) {
        const tempEl = document.createElement('div');
        tempEl.innerHTML = html;
        return tempEl.firstElementChild;
    }
}
</script>

css

 .accordion {
    cursor: pointer;
    width: 70%;
    padding: 5px 10px 5px 10px;
    background-color: #FFF;
    border: 1px solid #4D4D4D;
    display: table;
 }

.accordion span {
    width: 99%;
    font-size: 16px;
    display: table-cell;
}

.accordion i.fa-chevron-down {
    font-size: 16px;
    display: table-cell;
}

.table-of-contents {
    overflow: hidden;
    margin: 0!important;
    font-size: 16px;
    border-width: 0 1px 1px 1px;
    border-radius: 0;
    border-style: solid;
    border-color: #4D4D4D;
    width: calc(70% - 17px);
    padding: 15px 0 10px 36px;
    background-color: #f0f0f0;
    transition: height 0.4s ease-in-out;
}

@media (max-width: 767px) {
    .accordion{
        width: 95%;
    }
    .table-of-contents{
        width: calc(95% - 17px);
    }
}

.table-of-contents .active {
    display: block;
}

.table-of-contents:not(.active) {
    display: none;
}

各関数の説明

createAccordion

clsで指定された要素を取得して、hasContentを呼び出す。結果がtrueだった場合は、calcHeightで目次の高さを計算、headerを目次の上に挿入、setClickEventでonclickイベントの設定の順で処理を実行する。要素が複数あった場合はすべての要素に対して呼び出す。

hasContent

content(目次)にliタグが存在するかを確認し、その結果を返却する。

calcHeight

content(目次)の高さを計算して返却する。cssでactiveが付与されている時のみ目次が表示されるので、高さの計算をする前にactiveを挿入する。最初は目次を隠すので返却前にcontent(目次)高さは0にする。

setClickEvent

accordion(目次の要素で挿入した要素)にから矢印のアイコンを取得してaccordionに対してonclickイベントとしてtoggleを呼び出す。

toggle

content(目次)にactiveが含まれるかを判定、含まれていればslideUp、含まれていなければslideDownを呼び出す。

slideUp

content(目次)の高さを0にして、arrow(矢印)を元に戻す。トランジション(スライドのアニメーション)が終わったらcontent(目次)からactiveを除外する。

slideDown

content(目次)にactiveを付与、高さをcalcHeightで計算した高さにしてarrow(矢印)を180度反転させる。

createElementFromHTML

HTML文字列をHTML要素に変換する。

参考:HTML文字列をElementに変換する - Qiita

その他

  • 矢印のアイコンはfontawesomeを参照。

fontawesome.com

  • スライド時のアニメーションはcssのtransitionでease-in-out アニメーションを指定。

developers.google.com