スライド・メニューを作る

/web/javascript

Note: この記事は、3年以上前に書かれています。Webの進化は速い!情報の正確性は自己責任で判断してください。

ボタン・クリックで滑り落ちるメニューを作ってみた。昔のShaunInman.comとか、今で言うjek2k.comにあるようなやつ。大きめのフッタとセットで提供されることが多いように思います。

今回のポイントは3つ。

  • 開ききった時の「高さ」を取得
  • 現時点での「高さ」を取得
  • 連打時の処理中断(重複)への対応

Sample

Test.

起動部分

起動時のメニューの高さから、これから開くか閉じるかを判別する。「timer」の値を処理開始と連動させることで重複を避けています。

  1. var timer = null;
  2.  
  3. slide = function(objID) {
  4. var obj = document.getElementById(objID);
  5. obj.style.overflow = 'hidden';
  6. var o = getElementSize(obj);
  7. var slow = 3;
  8. var speed = 30;
  9.  
  10. if(obj.offsetHeight == 0) {
  11. obj.style.display = 'block';
  12. obj.style.height = 0;
  13.  
  14. if(timer == null) this.down(obj,o.oh,slow);
  15. }
  16.  
  17. else {
  18. if(timer == null) this.up(obj,o.oh,speed);
  19. }
  20. }

たとえば処理が完了する前にボタンを押した場合、前の処理が終了していないために動作が被り、小刻みに震えるだけになってしまう。clearTimeoutで処理を中断することもできますが、その方法だと連打時にあまり信頼できない結果でした。

結局、処理が完了するまで操作をロックしておく方法に落ち着きました。

要素の高さを取得

メニューの開放時および現時点での高さを取得する。特に開放時の高さは、メニューを開くときの限界点として必要になってきます(無いと永遠に落ち続けることになる)。

  1. getElementSize = function(elm) {
  2. var oSize = new Object();
  3. var elm2 = elm.cloneNode(true);
  4.  
  5. if(-1 == navigator.userAgent.indexOf('MSIE 5')){
  6. elm2.style.padding = '0';
  7. elm2.style.border = '0';
  8. }
  9. elm2.style.display = 'block';
  10. elm2.style.position = 'absolute';
  11.  
  12. elm.parentNode.appendChild(elm2);
  13. oSize.oh = elm2.clientHeight;
  14. oSize.ow = elm2.clientWidth;
  15. elm.parentNode.removeChild(elm2);
  16.  
  17. return oSize;
  18. }

height値を直接取得するのではなく、要素のクローンを作成してシミュレートする手法をとっています。よって、明示的にheightを指定していない場合でも値が取れるようになっています。

開放部分

メニューが滑り落ちる部分。静止するポイントと、スムーズに静止するための減速率を必要とする。

  1. this.down = function(obj,limit,slow) {
  2. var present = getElementSize(obj).oh;
  3. var distance = limit-present;
  4. var moveValue = Math.max(Math.floor(distance / slow),slow);
  5.  
  6. if(present < limit) {
  7. obj.style.height = (present+moveValue) + 'px';
  8. timer = setTimeout(function(){ this.down(obj,limit,slow) }, 10);
  9. }
  10.  
  11. else {
  12. obj.style.height = limit + 'px';
  13. timer = null;
  14. };
  15. }

閉鎖部分

メニューがせりあがる部分。減速率と同じように、速度もカスタム可能。

  1. this.up = function(obj,ori,speed) {
  2. var present = getElementSize(obj).oh;
  3. var moveValue = speed + 3;
  4.  
  5. present = present-moveValue;
  6.  
  7. if(present > 0) {
  8. obj.style.height = present + 'px';
  9. timer = window.setTimeout(function(){ this.up(obj,ori,speed) }, 10);
  10. }
  11.  
  12. else {
  13. obj.style.height = ori + 'px';
  14. obj.style.display = 'none';
  15. timer = null;
  16. };
  17. }

JQuery

実は、同じ動作をJQueryでやると3行で済んでしまう。

  1. $(document).ready(function(){
  2. $("a.showMenu1").click( function() {$("#menu").slideToggle("normal");} );
  3. });

ただし、速度の調節はできない。

memo: ちなみに、このスクリプトとjQuery版とを比べてみた場合、ある環境において明らかにパフォーマンス的に劣る。いろいろ無駄な処理が多いからだろうけど、そこら辺が今後の課題。

Note: スパム対策が面倒なので、コメント投稿を廃止しました。以前のコメントは残します。
ご意見・ご要望はtwitter@sigwygかはてブコメントにて。