はてなブログの月別アーカイブを滑らかに開閉したり自動で閉じたりする、いわゆるアコーディオンにカスタマイズ
はてなブログの月別アーカイブを表示している方は多いと思います。
このブログでも設置していますが、実は気になるところが二つあり、それは
- 月毎のリストの開閉が急
- 他の年のリスト表示ボタンを押しても、すでに開いているリストが開いたまま
この二つです。
これらはサイトデザインによって気にならないこともあります。2カラムでサイドバーに設定とかね。
でも、サイト下部に設置したり、スマホ向けに1カラムにしたりすると気になる場合も無きにしも非ずです。
どうにかしようと思い、どうにかしたのが今回の記事です。
(、追記・修正)
月別アーカイブのリスト開閉をなめらかにするCSS
まずはボタンをクリックすれば表示される月毎のリストを、滑らかに開閉するようにします。
まあ、いわゆるスムーズに開閉するアコーディオンリストのことですね。
さて、これですが、CSSだけでの設置でも実にいろいろ紹介されています。
その中でも特によいものがこちら。
高さ可変のものにも対応したCSSだけのアコーディオンです。
height
の代わりにpadding
やmargin
、line-height
などで可変式の高さにアコーディオンを実装する。
いやーこんな方法があったのですね、なるほど!
この方法なら、はてなブログの月別アーカイブでもスムーズな開閉が可能になります。
これを参考に作った CSS がこちら。
/* 開いた状態 */
.hatena-urllist .archive-module-year .archive-module-months {
line-height: 1.5;
opacity: 1;
visibility: visible;
transition:
line-height .3s ease-out,
opacity .1s linear .1s,
visibility .1s linear .1s;
}
/* 閉じた状態 */
.hatena-module-body .archive-module-year-hidden .archive-module-months {
display: block;
line-height: 0;
opacity: 0;
visibility: hidden;
transition:
line-height .3s ease-out,
opacity .1s linear,
visibility .1s linear;
}
元の CSS のアニメーションの数値も絶妙なので、ほぼそのまま使わして頂いてます。(笑)
月別アーカイブの開閉は archive-module-year-hidden クラスのあるなしで行っています。
このクラスがあるのが閉じた状態。無いものが開いた状態ですね。
クラスの子孫が多いのは、デフォルトの指定を参照しているからです。
特に「閉じた状態」のほうはdisplay:none
を上書きしないといけません。
そのためには CSS の優先順位を下げてはいけなので、このようになっています。
優先順位で言えば、CSSの記述する順番も重要です。同じ優先順位のレベルで「開いている状態」のほうが後で書かれていると、そちらのほうが優先され開きっぱなしになるからです。
ケースバイケースですが、今回の CSS なら書く順番は「開いている状態」の下に「閉じている状態」を書いたほうが良いです。
また、この CSS は最低限のものです。
リストにpadding
やmargin
、li要素にline-height
が設定されているなど、高さを作るものがあったら、参考先のように個別にアニメーションをするように設定して下さい。
採用しているブログのテーマによっては思わぬところに「高さ」が指定されていることもあります。その場合も個別にアニメーションするように設定しなくては上手く動いてくれません。
コツとしては「閉じた状態」「開いた状態」をデベロッパーツールなどで作ってみて、そこから高さやアニメーションの調整をするとよいでしょう。
ボタンをクリックして開いたら、すでに開いていたリストを閉じる JavaScript
次に閉じている年度のリストと開くためボタンを押したら、すでに開いている年度のリストを自動で閉じる仕組みを考えます。
こちらはいろいろ考えたのですが、CSS だけでは実現不可能だと見ました(ひょっとしたらあるかも知れませんが)。
CSS で無理なら JavaScript です。
するといっても、すでに開いているリストに閉じるためのクラスarchive-module-year-hidden
をくっ付けるだけです。そんなに難しいことはないはずです。
JavaScript と jQuery の両方作りました。以下順番に紹介していきます。
JavaScript
まずは出来たスクリプトを紹介します。その後で解説などをしたいと思います。
window.onload = function(){
var yearList = document.getElementsByClassName('archive-module-year'),
buttonList = document.getElementsByClassName('archive-module-show-button');
if ( ( yearList.length > 1 ) && ( buttonList.length ) ) {
for( var i=0; buttonList.length > i; i++ ) {
buttonList[i].addEventListener( 'click', function(e){
for( var i=0; buttonList.length > i; i++ ) {
if ( buttonList[i] != e.target ) yearList[i].classList.add('archive-module-year-hidden');
}
}, false);
}
}
};
解説
まずwindow.onload
でページのデータが全て読み込まれた後に動作するようにしています。
月別アーカイブはスクリプトで作られています。これらが作られた後でないと要素の取得ができないためです。
もちろん読み込まれた後に作動すればいいので、addEventListener
の'load'
でも良いです。
次にarchive-module-year
とarchive-module-show-button
のクラスがある要素を取得します。
archive-module-year があるクラスはリストの表示・非表示、archive-module-show-button があるクラスはリストが開くときにクリックされるボタンです。
次に、リストが複数あったら、開くボタン(archive-module-show-button
)にクリックイベントとして登録するようにしています。一つの場合いりませんからね。
イベントには「クリックした自身の要素があるリスト以外のリストに、archive-module-year-hidden クラスが無ければそれを付ける」というスクリプトを加えます。
jQuery
次に jQuery です。
実は月別アーカイブのスクリプトは、どうやら jQuery(バージョンはおそらく1.12.4 ) で作られたり動作たりしているようです。
window.onload
とかでページ読み込み後にスクリプトをもっていけば、CDN を入れる必要もありません。
とはいえ、はてなブログの仕様を利用した方法なので、ブログの仕様変更によっては使えなくなる可能性があります。
なので、jQuery を使う場合は CDN など、ちゃんと jQuery 本体をページに読み込ましてから使用したほうが本当は安心感があります。
ただその場合、パソコンでは起こりませんが、iPhone での動かした時に上手く動かないことがたまにあることを確認しています。
これは二重に jQuery が読み込まれるためか、重複するためか、iOS の性能が低いからか、バグなのか原因は分かりません。
ですが、先ほどの CDN などを入れずにwindow.onload
で読み込ませたものは、ちゃんと動作しています。
これは悩むところですが、jQuery で実装する場合はこのようなことが起こる可能性があることを気にとめて頂けたら幸いです。JacaScript でも同じような状態を確認しました。これはスマホの処理能力の問題なのか、そもそもコードがまずいのかもしれません。
話が逸れましたが、作ったスクリプトは、こちらです。
window.onload = function(){
$('.archive-module-show-button').on('click', function(){
$('.archive-module-year').addClass("archive-module-year-hidden");
});
};
スクリプトでやっていることは、先ほどの JavaScript とほぼ同じです。
CDN などが先に読み込まれていたら、$(window).load
などでもいけます。
ただし、$(document).ready
などだと月別アーカイブが作られる前に実行されるようなので、うまくいきません。
追記(2017年10月13日)
上記の方法では、複数の開閉ボタンを連続で交互に押したりすると空いているリストが閉じないという謎挙動がありました。
いろいろ試したのですが、最初に考えた方法のほうが上手く動くみたいです。
window.addEventListener( 'load', function() {
var yearList = document.getElementsByClassName('archive-module-year'),
archButton = document.getElementsByClassName('archive-module-button');
function eventAct( e ) {
for( var i = 0 ; yearList.length > i ; i++ ) {
if ( archButton[i] != e.target.parentNode ) {
yearList[i].classList.add('archive-module-year-hidden');
}
}
}
if ( archButton.length > 1 ) {
for( var i = 0 ; archButton.length > i ; i++ ) {
archButton[i].addEventListener( 'click', eventAct, true);
}
}
});
ボタンの親要素である div 要素にイベントを付けると、閉じるときもイベントが発生するので、どうかなーと思って中の「開く」のボタン(右三角の【▶】ボタン)にイベントを付けたのですが、かえってそれがいけなかったようです。
上記のコードは「アーカイブリストのボタンが押されたら、押したボタン以外のリストに、閉じる用のクラスを付ける」というものです。
念のためaddEventListener
の第三引数にtrue
を指定し、イベント発生をキャプチャフェーズ(親要素からイベント発火)にしています。いらないかも知れませんが。
これならば今のところ動きがあやしくなることはありません。ちなみに jQuery で同じような仕組みで書いてみますと、次のような感じです。
window.addEventListener( 'load', function() {
$('.archive-module-button').on('click', function(){
$('.archive-module-year').not( $(this).parent() ).addClass('archive-module-year-hidden');
});
});
どちらも、もっとスマートな書き方があるかもしれませんが、とりあえず気になた動きは出なくなったと思います。
これで以前から少し気になっていたことが解消しました。
これで月別アーカイブの開閉も良い感じになるのではないのでしょうか。
このアコーディオンの動作確認ですが、このブログのPCとスマホ表示の月別アーカイブで、動くように設定しています。
確認のほうは、そちらでお願いします(笑)