jQueryのanimate()で上下開閉の表示・非表示の概要
注意 ※バージョンや環境によっては正常に動作しない可能性があります。
fadeToggleとか、便利なんですけど、display:noneになるので、レイアウト要素が無くなる瞬間ががくっとします。
ゆっくり下から閉じたいと思い、slideUp・slideDownとかも考えたんですが、ちょっと別のアプローチをしたくて、animate()を試してみた形です。
ブートストラップもそうだったんですが、開閉の時に影響を及ぼすmargin・paddingは中々に苦労するな~と、animate()でも思いました。
その1 paddingとmarginが設定されているデフォルトのパターン
ここをクリック(trigger1)
triggerをクリックするとjQueryのanimateでheightとopacityを切り替えて表示非表示を切り替える。paddingとmarginがいろんなところに設定されているので、消えるときにガクッと画面が動くする
使用しているワードプレスのテーマに、コンテンツやpタグにデフォルトでmarginやpaddingが指定されています。
それらが全て設定されている形です。また、borderなどの見た目も設定しています。
動作サンプルを見てもらうとわかりやすいですが、余白が入っていると、アニメーションさせた時にがくっとします。アニメーションが利かずに、表示と非表示が一瞬で切り替わるからです。
動作はとても簡単で、jQueryでanimate()を使っています。heightとopacityにtoggleを設定して、表示と非表示を切り替えています。時間は1秒です。
ソースコード
HTML
<div class="trigger1"> <p>ここをクリック(trigger1)</p> </div> <div class="contents1"> <p>triggerをクリックするとjQueryのanimateでheightとopacityを切り替えて表示非表示を切り替える。paddingとmarginがいろんなところに設定されているので、消えるときにガクッと画面が動くする<p> </div>
CSS
<pre>.contents1 { display:none; background-color: #fff; border: 1px solid rgba(0, 0, 0, 0.125); border-radius: 0.25rem; padding: .5rem; margin-top: .5rem; } .trigger1{ background-color: #bdffc6; }</pre>
jQuery
$(function() { $('.trigger1').click(function() { $('.contents1').stop().animate({ height: 'toggle', opacity: 'toggle', }, 1000); }); });
説明
トリガー1がクリックされた時animateが動作して、heightとopacityを切り替えています。
heightを切り替えているので、上から下に要素か出てきて、下から上に要素が閉じていきます。
その2 関係するpaddingとmarginを全部0にしたパターン
ここをクリック(trigger2)
triggerをクリックするとjQueryのanimateでheightとopacityを切り替えて表示非表示を切り替える。関連するタグのpaddingとmarginを全て0にしているため、がくがくしない。ただ余白やborderなど何もない状態でのみ、がくがくしない形。
ワードプレスのテーマに設定されているものも含めて全てmarginとpaddingをゼロにしています。
borderもなくしています。
こうすると、がくっとしません。
ソースコード
HTML
<div class="trigger2"> <p>ここをクリック(trigger2)</p> </div> <div class="contents2"> <p>triggerをクリックするとjQueryのanimateでheightとopacityを切り替えて表示非表示を切り替える。関連するタグのpaddingとmarginを全て0にしているため、がくがくしない。ただ余白やborderなど何もない状態でのみ、がくがくしない形。<p> </div>
CSS
.contents2 { background-color: #fff; display:none; margin-top: 0; margin-bottom: 0; padding-top: 0px; padding-bottom: 0px; padding-right: .5rem; padding-left: .5rem; } .trigger2{ background-color: #bdffc6; margin-top: 0; margin-bottom: 0; } .contents2 p{ margin-top: 0; margin-bottom: 0; } .trigger2 p{ margin-top: 0; margin-bottom: 0; }
jQuery
$(function() { $('.trigger2').click(function() { $('.contents2').stop().animate({ height: 'toggle', opacity: 'toggle', }, 1000); }); });
説明
がくがくさせたくない、という目的のために、問題点を切り分けて、関連する余白などを全て取り除いた形です。
縦の移動しかさせていないので、縦側が問題だろうとにらんで、topとbottomを0にしています。
borderも消して、余計なものを無くしています。
また、コンテンツ要素だけでなく、トリガーの要素もmarginを0しています。自分の環境ではトリガー予想のmarginも影響するからです。
このあたり、ブートストラップのcollapsでも似たようなことが起きました。bootstrap使用にはjQueryが必要ですが、似たような現象ですかね。
とりあえず、これで余白が原因ということは分かりました。
思いつく解決策としては、余白もアニメーションさせるか、アニメーションを指定するクラスの中に、ネストした要素を作ってそっちのクラスで余白を設定する、とかでしょうか、と考えて、次のパターンになります。
その3 paddingとmarginもanimateでアニメーションさせるパターン
ここをクリック(trigger3)
triggerをクリックするとjQueryのanimateでheightとopacityを切り替えて表示非表示を切り替える。関係するpaddingとmarginを0にしてあるが、heightを指定していないため、開く時に最後がくっとする
paddingとmarginもアニメーションさせている形です。
ソースコード
HTML
<div class="trigger3"> <p>ここをクリック(trigger3)</p> </div> <div class="contents3"> <p>triggerをクリックするとjQueryのanimateでheightとopacityを切り替えて表示非表示を切り替える。関係するpaddingとmarginを0にしてあるが、heightを指定していないため、開く時に最後がくっとする<p> </div>
CSS
.contents3 { background-color: #fff; display:none; margin-top: 0; margin-bottom: 0; padding-top: 0; padding-bottom: 0; padding-left:.5rem; padding-right:.5rem; border: 1px solid rgba(0, 0, 0, 0.125); } .trigger3{ background-color: #bdffc6; margin-top: 0; margin-bottom: 0; } .contents3 p{ margin-top: 0; margin-bottom: 0; } .trigger3 p{ margin-top: 0; margin-bottom: 0; }
jQuery
$(function() { $('.trigger3').click(function() { if($(".contents3").css('padding-top') == "0px"){ $(".contents3").stop().animate({ height: 'show', opacity: 'show', 'padding-top':"12px", 'padding-bottom':"12px", 'margin-top':"12px", 'margin-bottom':"12px", },1000 ); } else{ $(".contents3").stop().animate({ height: 'hide', opacity: 'hide', 'padding-top':0, 'padding-bottom':0, 'margin-top':0, 'margin-bottom':0, },1000); } }); });
説明
まず、animateでtoggleを使うのをやめて、showとhideの処理にしました。
showの時にpaddingとmarginをオンにし、hideの時に0にする、という形をとりました。
判定はif文を使っています。条件式はちょっと適当ですが、contentsに設定している初期値のpadding-topを使用しています。
後から例としてあげるものは、クラス名を切り替える形で対応していますが、cssの値でも判定式を作れるので、ちょっと試してみた形です。
この方法はある程度いいところまで行ってまして、動作の最初のがくっと感が無くなっています。ただ、表示の最後がまだ駄目です。
色々調べた結果、高さが指定されていないとダメっぽいというのを見つけたため、高さを指定する形を試すことにしました。
その4 paddingとmarginもanimateでアニメーションで制御したうえで、高さを自動で取得して設定する方法
ここをクリック(trigger4)
triggerをクリックするとjQueryのanimateでheightとopacityを切り替えて表示非表示を切り替える。隠れている要素のheightをjQueryで取得して初期値としてcontentsに設定することで、高さ未指定で起こるがくがくを無くしている。ただ、ボーダー分の2pxが最後ちょっとだけがくっとする
ソースコード
HTML
<div class="trigger4"> <p>ここをクリック(trigger4)</p> </div> <div class="contents4"> <p>triggerをクリックするとjQueryのanimateでheightとopacityを切り替えて表示非表示を切り替える。隠れている要素のheightをjQueryで取得して初期値としてcontentsに設定することで、高さ未指定で起こるがくがくを無くしている。ただ、ボーダー分の2pxが最後ちょっとだけがくっとする<p> </div>
CSS
.contents4 { background-color: #fff; display:none; margin-top: 0; margin-bottom: 0; padding-top: 0; padding-bottom: 0; padding-left:.5rem; padding-right:.5rem; border: 1px solid rgba(0, 0, 0, 0.125); } .trigger4{ background-color: #bdffc6; margin-top: 0; margin-bottom: 0; } .contents4 p{ margin-top: 0; margin-bottom: 0; } .trigger4 p{ margin-top: 0; margin-bottom: 0; }
jQuery
var autoHeight = $('.contents4').height(); $('.contents4').css('height', autoHeight + 24); $(function() { $('.trigger4').click(function() { if($(".contents4").css('padding-top') == "0px"){ $(".contents4").stop().animate({ height: 'show', opacity: 'show', 'padding-top':"12px", 'padding-bottom':"12px", 'margin-top':"12px", 'margin-bottom':"12px", },1000 ); } else{ $(".contents4").stop().animate({ height: 'hide', opacity: 'hide', 'padding-top':0, 'padding-bottom':0, 'margin-top':0, 'margin-bottom':0, },1000); } }); });
説明
最初にcontentsにheightで高さを指定たところ、pタグ内の文字数などで高さが可変する場合、めんどくさいな~となりました。要素の大きさにあわせて、都度都度cssの高さを変えるのは面倒です。
さらなる問題点として、手動で高さを設定すると、枠線で囲ったときに要素を真ん中に持ってくる調整がめんどくさいな~となりました。
なんとかならんかと思ったら、jQueryで高さを取得できる方法を以下で見つけました。
DOMはページを読み込んだ時に、div要素の高さを取得している、とのことでした。
ということで、ページ読み込み時にheight()でcontentsの高さを取得して、その高さをそのままcssで設定する形をとりました。
24を足しているのは、手動で設定しているpaddingが12pxだから、その上下分も加えている形です。
そのおかげでかなり良くなりましたが、ボーダー分だけほんのすこし、かくっとして非表示になります。
これはborderのサイズもアニメーションさせればよくね?とおもって設定したのですが、これが利かない。topとbottomのwidthで指定しても利かない。
solidが残っていると、border-widthでアニメーションさせても反映されないみたいなんですよね~。
自分としてはこの消え方が一番好きなんですけどね。
ということで、アニメーションさせる要素をコンテンツの子要素にしてみたのが、以下のその5です。
その5 HTMLのコンテンツ要素を分離し、borderやpaddingを設定するクラスを別に用意する方法
ここをクリック(trigger5)
ソースコード
HTML
<div class="trigger5"> <p>ここをクリック(trigger5)</p> </div> <div class="contents5 hide"> <div class="border5"> <p>triggerをクリックするとdisplay noneがdisplay blockにかわり、opasityが0から1なる。表示されている状態でクリックすると、opasityが1から0になった後に、display noneになる。<p> </div> </div>
CSS
.contents5 { background-color: #fff; display:none; margin: 0; padding: 0; } .border5{ border: 1px solid rgba(0, 0, 0, 0.125); padding: 1rem; } .trigger5{ background-color: #bdffc6; margin-top: 0; margin-bottom: 0; } .border5 p{ margin-top: 0; margin-bottom: 0; } .trigger5 p{ margin-top: 0; margin-bottom: 0; }
jQuery
var autoHeight = $('.contents5').height(); $('.contents5').css('height', autoHeight); $(function() { $('.trigger5').click(function() { $('.contents5'). toggleClass('show'); $('.contents5'). toggleClass('hide'); if($(".show").length){ $(".contents5").stop().animate({ height: 'show', opacity: 'show', 'margin-top':"1em", 'margin-bottom':"1em", },1000 ); } else{ $(".contents5").stop().animate({ height: 'hide', opacity: 'hide', 'margin-top':0, 'margin-bottom':0, },1000); } }); });
説明
まず、HTMLを変えています。
contentsには見た目の設定をし、子要素としてborderを作りました。その中にpタグを入れています。
CSSでは、contentsからボーダーの要素を外して、背景色とdisplay:noneと余白等0にするだけにしています。
borderクラスにborderとpaddingを設定しました。borderにmarginを設定したらがくがくしたので、marginはアニメーションで対応することにしました。
paddingをborderに設定出来たので、アニメーションからはなくしました。高さの取得にpadding要素を足す必要もなくなりました。
判定文も変えています。
表示と非表示のクラスを作り、初期値にhideを指定します。
クリックされたら初期値のhideがshowクラスに変わり、showなら表示のアニメーションが、それ以外なら非表示のアニメーションが動作するような設定になっています。
これでがくっとしない上下開閉が完成しました。
ただ、その4と違ってボーダー要素も上から徐々に出てくる形になります。その4は、枠が最初に出てきて、枠が広がっていくように見えます。この辺りは好みかな~と。
がくっとしないならばその4を選びたいのですが。その4で他にいい方法がないかな~と考え中です。
まとめ
display:noneを使用する表示非表示のアニメーションは、どうしても画面表示でがくがくすることが多く、CSSでもjQueryでもなかなか良いものが見つからなかったのですが、やっと一つなんとかなる方法が見つかったかな~という印象です。
display:noneを使わなければ、選択肢はかなりたくさんあるので、あんまり困らないんですけど。
その4はなんとなく全体が消えていって、ボーダーも最後まで全体が残っているのですが、最後に2px分かくっとする、という形でして、それでもいいんですけど、なんとかかくっとならない方法はないかな~と探した結果がその5のボーダーを子要素にしてしまう、という形でした。
ボーダーってpaddingの外にあってheightとwidthにふくまれない値ですが、box-sizing:border-boxでheightとwidthにボーダーを含むことができます。だいたい枠線をつけるときはこれを使うんですけど、これ使っても自分の環境ではかくっとが消えなかったんですよね~。じゃ子要素にしてみるか、という単純な形でした。ただ、子要素にしたせいでボーダーの見え方がちょっと変わっています。
まあ、今回のアニメーションに関わらず、実際はbox-sizing:border-boxを適用しておくのほうが管理は楽だと思います。
とりあえず、今回はいろんなパターンを試せたので良かったです。