2009-05-01 15 views
7

jquery's accordion pluginに似たjqueryでアコーディオンウィジェットを作成しようとしていますが、上記の代わりにそれぞれのコンテンツの下にハンドルを表示したいという違いがあります。私のアコーディオンは、開いているコンテンツセクションの高さを低くすると同時に、クリックされたコンテンツセクションの高さを上げることによって機能します。私はhereの例を掲載しました。私の問題は、アニメーションがまったく同じ時間に開始されず、2番目のアニメーションが開始される少し遅れているために目立つ「ジャンプ」があることです。jqueryにアニメーションを正確に並列に実行させるにはどうすればよいですか?

ScriptaculousにはEffect.Parallelという名前の関数があり、アニメーション効果の配列を作成して並行して実行することができます。残念ながら、私はjqueryと似た何かを見つけることができません。

jqueryの別々のdivで正確な並列アニメーションを実行する方法はありますか?

編集:私はこのアコーディオンウィジェットをコーディングする別の方法に興味があります。だから、もし人々が働くと思う他の方法があれば、私はそれを公開しています。

+0

SGILでこれを行うことができますが、これはマークアップ言語であり、別のトピックです。 –

+0

私の最終的な解決策はこちらをご覧ください:http://stackoverflow.com/questions/811750/how-can-i-get-jquery-to-execute-animations-in-exact-parallel/835362#835362 –

答えて

4

もう一つの答えは、うまくいけば私の最後の1 ...残念ながら

、ジョンResig氏のsyncAnimateの方法がありますアコーディオン型の肛門のための嗅ぎタバコではない私はしたい。 Firefox上でうまく動作しますが、IEやSafariでスムーズに動作させることができませんでした。

これで、私は弾丸に噛み付き、単純な平行アニメーションを行う独自のアニメーションエンジンを作成することにしました。クラスコードはjquery関数を使用しますが、jqueryプラグインではありません。また、サイズ/位置アニメーションだけを設定しました。これはすべて私が必要とするものです。

ParallelAnimations = function(animations, opts){ 
    this.init(animations, opts); 
}; 

$.extend(ParallelAnimations.prototype, { 
    options: { 
     duration: 250 
    }, 
    rules: {}, 

    init: function(animations, opts){ 
     // Overwrite the default options 
     $.extend(this.options, opts); 

     // Create a set of rules to follow in our animation 
     for(var i in animations){ 
      this.rules[i] = { 
       element: animations[i].element, 
       changes: new Array() 
      }; 

      for(var style in animations[i].styles){ 

       // Calculate the start and end point values for the given style change 
       var from = this.parse_style_value(animations[i].element, style, ""); 
       var to = this.parse_style_value(animations[i].element, style, animations[i].styles[style]); 

       this.rules[i].changes.push({ 
        from: from, 
        to: to, 
        style: style 
       }); 
      } 
     } 

     this.start() 
    }, 

    /* 
    * Does some parsing of the given and real style values 
    * Allows for pixel and percentage-based animations 
    */ 
    parse_style_value: function(element, style, given_value){ 
     var real_value = element.css(style); 

     if(given_value.indexOf("px") != -1){ 
      return { 
       amount: given_value.substring(0, (given_value.length - 2)), 
       unit: "px" 
      }; 
     } 

     if(real_value == "auto"){ 
      return { 
       amount: 0, 
       unit: "px" 
      }; 
     } 

     if(given_value.indexOf("%") != -1){ 
      var fraction = given_value.substring(0, given_value.length - 1)/100; 

      return { 
       amount: (real_value.substring(0, real_value.length - 2) * fraction), 
       unit: "px" 
      }; 
     } 

     if(!given_value){ 
      return { 
       amount: real_value.substring(0, real_value.length - 2), 
       unit: "px" 
      }; 
     } 
    }, 

    /* 
    * Start the animation 
    */ 
    start: function(){ 
     var self = this; 
     var start_time = new Date().getTime(); 
     var freq = (1/this.options.duration); 

     var interval = setInterval(function(){ 
      var elapsed_time = new Date().getTime() - start_time; 

      if(elapsed_time < self.options.duration){ 
       var f = elapsed_time * freq; 

       for(var i in self.rules){ 
        for(var j in self.rules[i].changes){ 
         self.step(self.rules[i].element, self.rules[i].changes[j], f); 
        } 
       } 
      } 
      else{ 
       clearInterval(interval); 

       for(var i in self.rules){ 
        for(var j in self.rules[i].changes) 
         self.step(self.rules[i].element, self.rules[i].changes[j], 1); 
       } 
      } 
     }, 10); 
    }, 

    /* 
    * Perform an animation step 
    * Only works with position-based animations 
    */ 
    step: function(element, change, fraction){ 

     var new_value; 
     switch(change.style){ 
      case 'height': 
      case 'width': 
      case 'top': 
      case 'bottom': 
      case 'left': 
      case 'right': 
      case 'marginTop': 
      case 'marginBottom': 
      case 'marginLeft': 
      case 'marginRight': 
       new_value = Math.round(change.from.amount - (fraction * (change.from.amount - change.to.amount))) + change.to.unit; 
       break; 
     } 

     if(new_value) 
      element.css(change.style, new_value); 
    } 
}); 

元のAccordionクラスは、新しい呼び出しを使用するためにanimateメソッドで変更する必要があります。 、他のスタイルの種類(色のアニメーション - キューアニメーション - :

<html> 
<head> 
    <title>Parallel Accordion Animation</title> 
    <script type="text/javascript" src="jquery.js"></script> 
    <script type="text/javascript" src="ui.js"></script> 
    <script type="text/javascript"> 
    $(document).ready(function(){ 
     new Accordion("#accordion"); 
    }); 
    </script> 
    <style type="text/css"> 
    #accordion{ 
     position: relative; 
    } 
    #accordion .handle{ 
     width: 260px; 
     height: 30px; 
     background-color: orange; 
    } 
    #accordion .section{ 
     width: 260px; 
     height: 445px; 
     background-color: #a9a9a9; 
     overflow: hidden; 
     position: relative; 
    } 
    </style> 
</head> 
<body> 

<div id="accordion"> 
    <div class="section"><!-- --></div> 
    <div class="handle">handle 1</div> 
    <div class="section"><!-- --></div> 
    <div class="handle">handle 2</div> 
    <div class="section"><!-- --></div> 
    <div class="handle">handle 3</div> 
    <div class="section"><!-- --></div> 
    <div class="handle">handle 4</div> 
    <div class="section"><!-- --></div> 
    <div class="handle">handle 5</div> 
</div> 

</body> 
</html> 

が、私は将来的に追加することがいくつかあります。

Accordion = function(container_id, options){ 
    this.init(container_id, options); 
} 

$.extend(Accordion.prototype, { 
    container_id: '', 
    options: {}, 
    active_tab: 0,  
    animating: false, 
    button_position: 'below', 
    duration: 250, 
    height: 100, 

    handle_class: ".handle", 
    section_class: ".section", 

    init: function(container_id, options){ 
     var self = this; 
     this.container_id = container_id; 
     this.button_position = this.get_button_position(); 

     // The height of each section, use the height specified in the stylesheet if possible 
     this.height = $(this.container_id + " " + this.section_class).css("height"); 

     if(options && options.duration) this.duration = options.duration; 
     if(options && options.active_tab) this.active_tab = options.active_tab; 

     // Set the first section to have a height and be "open" 
     // All the rest of the sections should have 0px height 
     $(this.container_id).children(this.section_class).eq(this.active_tab) 
      .addClass("open") 
      .css("height", this.height) 
      .siblings(this.section_class) 
      .css("height", "0px"); 

     // figure out the state of the handles 
     this.do_handle_logic($(this.container_id).children(this.handle_class).eq(this.active_tab)); 

     // Set up an event handler to animate each section 
     $(this.container_id + " " + this.handle_class).mouseover(function(){ 

      if(self.animating) 
       return; 

      self.animate($(this)); 
     }); 
    }, 

    /* 
    * Determines whether handles are above or below their associated section 
    */  
    get_button_position: function(){ 
     return ($(this.container_id).children(":first").hasClass(this.handle_class) ? 'above' : 'below'); 
    }, 

    /* 
    * Animate the accordion from one node to another 
    */ 
    animate: function(handle){ 
     var active_section = (this.button_position == 'below' ? handle.prev() : handle.next());  
     var open_section = handle.siblings().andSelf().filter(".open"); 

     if(active_section.hasClass("open")) 
      return; 

     this.animating = true; 

     // figure out the state of the handles 
     this.do_handle_logic(handle); 

     // Close the open section 
     var arr = new Array(); 
     arr.push({ 
      element: open_section, 
      styles: { 
       "height": "0px" 
      } 
     }); 
     arr.push({ 
      element: active_section, 
      styles: { 
       "height": this.height 
      } 
     }); 
     new ParallelAnimations(arr, {duration: this.duration}); 

     var self = this; 
     window.setTimeout(function(){ 
      open_section.removeClass("open"); 
      active_section.addClass("open"); 
      self.animating = false; 
     }, this.duration); 
    }, 

    /* 
    * Update the current class or "state" of each handle 
    */ 
    do_handle_logic: function(handle){ 
     var all_handles = handle.siblings(".handle").andSelf(); 
     var above_handles = handle.prevAll(this.handle_class); 
     var below_handles = handle.nextAll(this.handle_class); 

     // Remove all obsolete handles 
     all_handles 
      .removeClass("handle_on_above") 
      .removeClass("handle_on_below") 
      .removeClass("handle_off_below") 
      .removeClass("handle_off_above"); 

     // Apply the "on" state to the current handle 
     if(this.button_position == 'below'){ 
      handle 
       .addClass("handle_on_below"); 
     } 
     else{ 
      handle 
       .addClass("handle_on_above"); 
     } 

     // Apply the off above/below state to the rest of the handles 
     above_handles 
      .addClass("handle_off_above"); 

     below_handles 
      .addClass("handle_off_below"); 
    } 
}); 

は、HTMLはまだ同じように呼ばれています等)

+0

もっと簡単な方法は、ステップ関数をアニメーションに渡すことです。詳細はこちらをご覧ください: http://docs.jquery.com/Release:jQuery_1.2/Effects#Extensible_Animations – johjoh

3

John Resigさんがsynchronized animation sampleを投稿しました(説明は表示されません、色付きのボックスをクリックしてください)。それをあなたのコントロールに適用する方法を理解するにはいくつかの作業が必要かもしれませんが、それを開始するのが良い場所になる可能性があります。

+0

私のアコーデオンでこれを使うのは比較的簡単で、本当にうまくいきます。追加された利点は、それは私がマークアップからすべてのロジックを保つことができ、それがすべてjavascriptにあるということです。 –

0

私はあなたの問題がタイミングではなく、ピクセルの分数分割だと思います。このコードを試してみると、ハンドル1と2はスムーズに見えますが、Firefox 3では他のものは見えませんが、クロムにはまだまだびっくりしています。

active 
    .animate({ height: "100px" }) 
    .siblings(".section") 
    .animate({ height: "0px" }); 

要素の位置を静的または絶対的にすることについて考えましたか?あなたが2つの要素の位置を動かすだけであれば、他の要素が飛んでくるのを心配する必要はありません。もう少し私を与えて、私は例を挙げてみよう。

2

これは実行中のアニメーションを並列では解決しませんが、期待される動作をジッタなしで再現します。アニメーションの数を減らすために、ハンドルの内側にセクションを配置しました。あなたはコードを小さくするためにandSelf()を使うことができますが、読むのは難しくなります。いくつかのスタイルを微調整する必要があります。

<html> 
<head> 
    <title>Accordion Test</title> 
    <script type="text/javascript" src="jquery.js"></script> 
    <script type="text/javascript"> 

    $(document).ready(function(){ 
     $("#accordion .handle").click(function(){ 
      var open = $(this).parent().children(".section, .open"); 
      var active = $(this); 

      if (!active.hasClass("open")) 
      { 
       if (active.hasClass("up")) 
       { 
        console.log("up"); 
        active.animate({top:"+=100"}).removeClass("up"); 
        active.nextAll(".handle").andSelf().filter(".up").animate({top:"+=100"}).removeClass("up"); 
        $(".section", active).slideUp(); 
        $(".section", active.nextAll()).slideUp(); 
        $(".section", active.prev()).slideDown(); 
       } 
       else 
       { 
        active.prevAll(".handle").not(".up").animate({top:"-=100"}).addClass("up"); 
        $(".section", active.prev()).slideDown(); 
       } 

       open.removeClass("open"); 
       active.addClass("open"); 
      } 
     }); 
    }); 

    </script> 
    <style type="text/css"> 
     #accordion{ 
      width: 200px; 
      position:relative; 
     } 
     #accordion .section{ 
      width: 196px; 
      margin-left: 2px; 
      height: 100px; 
      background-color: #b9b9b9; 
      display:none; 
     } 
     #accordion .handle{ 
      width: 200px; 
      height: 30px; 
      background-color: #d9d9d9; 
      border: 1px solid black; 
      cursor: pointer; 
      cursor: hand; 
      position: absolute; 
     } 
     #accordion .handle .header { 
      height: 30px; 
     } 
    </style> 
</head> 
<body> 

<div id="accordion"> 
    <div id="s1" class="section open" style="display:block">This is section 1</div> 

    <div class="handle open" style="top:100;"> 
     <div class="header">handle 1</div> 
     <div class="section">This is section 2</div> 
    </div> 

    <div class="handle" style="top:130;"> 
     <div class="header">handle 2</div> 
     <div class="section">This is section 3</div> 
    </div> 

    <div class="handle" style="top:160;"> 
     <div class="header">handle 3</div> 
     <div class="section">This is section 4</div> 
    </div> 

    <div class="handle" style="top:190;"> 
     <div class="header">handle 4</div> 
     <div class="section">This is section 5</div> 
    </div> 

    <div class="handle" style="top:220;"> 
     <div class="content">handle 5</div> 
    </div> 
</div> 

</body> 
</html> 
+0

このコードは機能します!それは滑らかで、望ましくない動きがありません。 –

+0

これをしばらく再生した後、私はCorbin Marchが投稿したソリューションを使用することに決めました。 –

0

アップデート:私は、もはやジョンResig氏のsyncAnimateプラグインを使用しています。最終的な解決策についての私の後の回答を参照してください

私は自分のプロジェクトで採用している最終的な作業ソリューションを提供したがっています。それは、John Resigが書いた(Corbin Marchによって投稿された)syncAnimate pluginを使用します。

このコードはします:

  • 読むとCSS
  • から断面高さを使用するには、あなたがオプションオブジェクトを介してアニメーションの再生時間、およびデフォルトのアクティブセクションを設定することができます。
  • セクションに対するハンドルの位置を自動的に検出し、それに応じて調整します。したがって、マークアップ内のセクションの上または下にハンドルを移動し、jsコードを変更する必要はありません。

HTML

<script type="text/javascript" src="jquery.js"></script> 
<script type="text/javascript" src="ui.js"></script> 

<script type="text/javascript"> 
$(document).ready(function(){ 
    new Accordion("#accordion", {active_tab: 0}); 
}); 
</script> 
<style type="text/css"> 
#accordion .handle{ 
    width: 260px; 
    height: 30px; 
    background-color: orange; 
} 
#accordion .section{ 
    width: 260px; 
    height: 445px; 
    background-color: #a9a9a9; 
    overflow: hidden; 
    position: relative; 
} 

</style> 

<div id="accordion"> 
    <div class="section">Section Code</div> 
    <div class="handle">handle 1</div> 

    <div class="section">Section Code</div> 
    <div class="handle">handle 2</div> 

    <div class="section">Section Code</div> 
    <div class="handle">handle 3</div> 

    <div class="section">Section Code</div> 
    <div class="handle">handle 4</div> 

    <div class="section">Section Code</div> 
    <div class="handle">handle 5</div> 
</div> 

ui.js

Accordion = function(container_id, options){ 
    this.init(container_id, options); 
} 

$.extend(Accordion.prototype, { 
    container_id: '', 
    options: {}, 
    active_tab: 0,  
    animating: false, 
    button_position: 'below', 
    duration: 250, 
    height: 100, 

    handle_class: ".handle", 
    section_class: ".section", 

    init: function(container_id, options){ 
     var self = this; 
     this.container_id = container_id; 
     this.button_position = this.get_button_position(); 

     // The height of each section, use the height specified in the stylesheet if possible 
     this.height = $(this.container_id + " " + this.section_class).css("height"); 

     if(options && options.duration) this.duration = options.duration; 
     if(options && options.active_tab) this.active_tab = options.active_tab; 

     // Set the first section to have a height and be "open" 
     // All the rest of the sections should have 0px height 
     $(this.container_id).children(this.section_class).eq(this.active_tab) 
      .addClass("open") 
      .css("height", this.height) 
      .siblings(this.section_class) 
      .css("height", "0px"); 

     // figure out the state of the handles 
     this.do_handle_logic($(this.container_id).children(this.handle_class).eq(this.active_tab)); 

     // Set up an event handler to animate each section 
     $(this.container_id + " " + this.handle_class).mouseover(function(){ 

      if(self.animating) 
       return; 

      self.animate($(this)); 
     }); 
    }, 

    /* 
    * Determines whether handles are above or below their associated section 
    */  
    get_button_position: function(){ 
     return ($(this.container_id).children(":first").hasClass(this.handle_class) ? 'above' : 'below'); 
    }, 

    /* 
    * Animate the accordion from one node to another 
    */ 
    animate: function(handle){ 
     var active_section = (this.button_position == 'below' ? handle.prev() : handle.next());  
     var open_section = handle.siblings().andSelf().filter(".open"); 

     if(active_section.hasClass("open")) 
      return; 

     this.animating = true; 

     // figure out the state of the handles 
     this.do_handle_logic(handle); 

     // Close the open section 
     open_section 
      .syncAnimate(active_section, {"height": "0px"}, {queue:false, duration:this.duration}, '') 
      .removeClass("open"); 

     // Open the new section 
     active_section 
      .syncAnimate(open_section, {"height": this.height}, {queue:false, duration:this.duration}, '') 
      .addClass("open"); 

     var self = this; 
     window.setTimeout(function(){ 
      self.animating = false; 
     }, this.duration); 
    }, 

    /* 
    * Update the current class or "state" of each handle 
    */ 
    do_handle_logic: function(handle){ 
     var all_handles = handle.siblings(".handle").andSelf(); 
     var above_handles = handle.prevAll(this.handle_class); 
     var below_handles = handle.nextAll(this.handle_class); 

     // Remove all obsolete handles 
     all_handles 
      .removeClass("handle_on_above") 
      .removeClass("handle_on_below") 
      .removeClass("handle_off_below") 
      .removeClass("handle_off_above"); 

     // Apply the "on" state to the current handle 
     if(this.button_position == 'below'){ 
      handle 
       .addClass("handle_on_below"); 
     } 
     else{ 
      handle 
       .addClass("handle_on_above"); 
     } 

     // Apply the off above/below state to the rest of the handles 
     above_handles 
      .addClass("handle_off_above"); 

     below_handles 
      .addClass("handle_off_below"); 
    } 
}); 
+0

もう少し調べてみると、これは非FFブラウザではうまく機能しません。それでもなお目立つジッタがあります。私はscriptaculous 'Effect.Parallel関数でこれを成功させることができました。しかし、私はまだjqueryでそれを行う方法を探しています。 –

0

適切なキューとスコープでjqueryで並列エフェクトを実行することはできません。 Scriptaculousは、jQueryがキューとスコープを持っていればそれを得ました.queueと.animateは、基本的に役に立たないものです。 jQueryがすぐに使えるのは、DOM上でいくつかのスタイル属性をプッシュしているのに対し、Scriptaculousはエフェクトで可能なすべての範囲をカバーしています。

Scriptaculousを使用する必要があります.Joh ResigはjQuery.fxを再考する必要があります。scripty2.comを見ているうちに、彼はそこにいるはずです。

1

ありがとうアダムPlumbは、並列アニメーションの本当に素晴らしい解決策です。私はそれに少し問題がありました。それは、以前のアニメーションから何らかの形でロールを保存していたことです。私は、ルールを{}に設定してinit関数に追加する前にそれらを修正しました。それはおそらくより良い方法で行うことができます。また、アニメーションが終了したときに呼び出されるコールバック関数も追加しました。

ParallelAnimations = function(animations, opts){ 
    this.init(animations, opts); 
}; 

$.extend(ParallelAnimations.prototype, { 
    options: { 
     duration: 250, 
     callback: null 
    }, 
    rules: {}, 

    init: function(animations, opts){ 
     // Overwrite the default options 
     $.extend(this.options, opts); 

     // Create a set of rules to follow in our animation 
     this.rules = {}; // Empty the rules. 
     for(var i in animations){ 
      this.rules[i] = { 
       element: animations[i].element, 
       changes: new Array() 
      }; 

      for(var style in animations[i].styles){ 

       // Calculate the start and end point values for the given style change 
       var from = this.parse_style_value(animations[i].element, style, ""); 
       var to = this.parse_style_value(animations[i].element, style, animations[i].styles[style]); 

       this.rules[i].changes.push({ 
        from: from, 
        to: to, 
        style: style 
       }); 
      } 
     } 

     this.start() 
    }, 

    /* 
    * Does some parsing of the given and real style values 
    * Allows for pixel and percentage-based animations 
    */ 
    parse_style_value: function(element, style, given_value){ 
     var real_value = element.css(style); 

     if(given_value.indexOf("px") != -1){ 
      return { 
       amount: given_value.substring(0, (given_value.length - 2)), 
       unit: "px" 
      }; 
     } 

     if(real_value == "auto"){ 
      return { 
       amount: 0, 
       unit: "px" 
      }; 
     } 

     if(given_value.indexOf("%") != -1){ 
      var fraction = given_value.substring(0, given_value.length - 1)/100; 

      return { 
       amount: (real_value.substring(0, real_value.length - 2) * fraction), 
       unit: "px" 
      }; 
     } 

     if(!given_value){ 
      return { 
       amount: real_value.substring(0, real_value.length - 2), 
       unit: "px" 
      }; 
     } 
    }, 

    /* 
    * Start the animation 
    */ 
    start: function(){ 
     var self = this; 
     var start_time = new Date().getTime(); 
     var freq = (1/this.options.duration); 

     var interval = setInterval(function(){ 
      var elapsed_time = new Date().getTime() - start_time; 

      if(elapsed_time < self.options.duration){ 
       var f = elapsed_time * freq; 

       for(var i in self.rules){ 
        for(var j in self.rules[i].changes){ 
         self.step(self.rules[i].element, self.rules[i].changes[j], f); 
        } 
       } 
      } 
      else{ 
       clearInterval(interval); 

       for(var i in self.rules){ 
        for(var j in self.rules[i].changes) 
         self.step(self.rules[i].element, self.rules[i].changes[j], 1); 
       } 
       if(self.options.callback != null) { 
        self.options.callback(); // Do Callback 
       } 
      } 
     }, 10); 
    }, 

    /* 
    * Perform an animation step 
    * Only works with position-based animations 
    */ 
    step: function(element, change, fraction){ 

     var new_value; 
     switch(change.style){ 
      case 'height': 
      case 'width': 
      case 'top': 
      case 'bottom': 
      case 'left': 
      case 'right': 
      case 'marginTop': 
      case 'marginBottom': 
      case 'marginLeft': 
      case 'marginRight': 
       new_value = Math.round(change.from.amount - (fraction * (change.from.amount - change.to.amount))) + change.to.unit; 
       break; 
     } 

     if(new_value) 
      element.css(change.style, new_value); 
    } 
}); 
関連する問題