2009-05-01 21 views
7

我试图在jQuery中创建类似于jquery's accordion plugin的手风琴窗口小部件,区别在于我希望手柄出现在它们各自的内容下面而不是上面。我的手风琴通过降低开放内容部分的高度,同时增加点击内容部分的高度来实现。我已经发布了一个示例here。我的问题是,动画并非完全同时启动,并且由于第二个动画开始之前的轻微延迟而出现明显的“跳跃”。如何让jquery以完全平行的方式执行动画?

Scriptaculous有一个叫做Effect.Parallel的函数,它允许你创建一个动画效果数组,并行执行它们。不幸的是,我似乎无法找到类似jQuery的东西。

有没有一种方法可以在jquery中的单独div上运行精确的并行动画?

编辑:我对代码这个手风琴小部件的替代方法非常感兴趣。所以如果有其他任何人们认为可行的方法,我都会接受。

+0

你可以在SGIL中做到这一点我认为,但这是一种标记语言和一点不同的话题。 – 2009-05-01 16:51:59

+0

在这里看到我的最终解决方案:http://stackoverflow.com/questions/811750/how-can-i-get-jquery-to-execute-animations-in-exact-parallel/835362#835362 – 2009-05-07 16:57:54

回答

4

还有一个答案,希望我的最后一个...

不幸的是,约翰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方法中修改以利用新的调用。

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仍然被称为以同样的方式:

<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> 

有几件事情我可以在将来添加: - 排队动画 - 动画对于其他类型的样式(颜色,等)

3

John Resig发布了synchronized animation sample(没有说明,点击一个彩色框)。可能需要一些工作来弄清楚如何将它应用于你的控制,但它可能是一个开始的好地方。

+0

原来是使用手风琴相对容易使用,效果非常好。额外的好处是,它允许我将所有逻辑放在标记之外,并将所有的逻辑都放在javascript中。 – 2009-05-04 18:02:06

0

我认为你的问题不是时间,而是一个像素的分数部分。如果你尝试使用这段代码,它对于句柄1和2看起来很平滑,但在Firefox 3中看起来并不平滑,但在Chrome中看起来仍然很刺眼。

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

你有没有想过让元素的位置是静态的还是绝对的?如果你只是移动两个元素的位置,你不必担心其他的跳跃。给我一下,我会试着举个例子。

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

此代码有效!它是光滑的,没有不必要的移动。 – 2009-05-04 12:42:20

+0

玩了一段时间后,我决定使用Corbin March发布的解决方案。 – 2009-05-04 18:01:23

0

更新:我不再使用John Resig的syncAnimate插件。看到我后来的答案为最终解决方案

我只是想提供我在我的项目上使用的最终工作解决方案。它使用John Resig写的syncAnimate plugin(由Corbin March发布)。

这段代码:

  • 读取和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

你不能在jquery中做适当的队列和范围的并行效果。 Scriptaculous在队列和范围上正确地做了正确的事情,jQuery另一方面有.queue和.animate,这些基本上是无用的组合。 jQuery对于开箱即用的唯一好处就是在dom上推动一些风格属性,而Scriptaculous涵盖了可能带来的所有效果。

你需要使用Scriptaculous,John Resig应该重新思考jQuery.fx,他应该看看scripty2.com。

1

感谢Adam 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); 
    } 
}); 
相关问题