2009-05-01 15 views
7

Estoy tratando de crear un widget de acordeón en jquery similar a jquery's accordion plugin, con la diferencia de que quiero que los controladores aparezcan debajo de su contenido respectivo en lugar de arriba. Mi acordeón funciona disminuyendo la altura de la sección de contenido abierto y, al mismo tiempo, aumentando la altura de la sección de contenido cliqueada. He publicado un ejemplo here. Mi problema es que las animaciones no se inician exactamente al mismo tiempo, y hay un "salto" notable debido a la ligera demora antes de que comience la segunda animación.¿Cómo puedo obtener jquery para ejecutar animaciones en paralelo exacto?

Scriptaculous tiene una función llamada Effect.Parallel que le permite crear una matriz de efectos de animación y ejecutarlos en paralelo. Lamentablemente, parece que no puedo encontrar algo similar con jquery.

¿Hay alguna manera de ejecutar animaciones paralelas precisas en divs separados en jquery?

Editar: Estoy tan interesado en métodos alternativos de codificación de este widget de acordeón. Entonces, si hay algún otro método que la gente piense que funcionaría, estoy abierto a eso.

+0

puedes hacer esto en SGIL, creo, pero ese es un lenguaje de marcado y un tema un poco diferente. –

+0

Vea mi solución final aquí: http://stackoverflow.com/questions/811750/how-can-i-get-jquery-to-execute-animations-in-exact-parallel/835362#835362 –

Respuesta

4

una respuesta más, espero que mi último ...

Por desgracia, el método syncAnimate de John es Resig no muy hasta rapé para el ani tipo acordeón mación que quiero hacer. Si bien funciona bien en Firefox, no pude hacerlo funcionar sin problemas en IE o Safari.

Con eso dicho, decidí morder la bala y escribir mi propio motor de animación que hace animaciones paralelas simples. El código de clase usa funciones de jquery, pero no es un plugin de jquery. Además, solo lo configuré para hacer animaciones de tamaño/posición, que es todo lo que necesito.

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); 
    } 
}); 

Luego, la clase original Accordion solo necesita modificarse en el método animado para hacer uso de la nueva llamada.

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"); 
    } 
}); 

El HTML todavía se llama de la misma manera:

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

Hay algunas cosas que me permite añadir en el futuro: - cola Animaciones - Animaciones para otros tipos de estilos (colores, etc.)

+0

Un enfoque mucho más fácil es pasar una función de paso a la animación. Lea aquí para obtener más detalles: http://docs.jquery.com/Release:jQuery_1.2/Effects#Extensible_Animations – johjoh

3

John Resig ha publicado un synchronized animation sample (sin instrucciones, haga clic en un cuadro de color). Podría tomar algún trabajo averiguar cómo aplicarlo a su control, pero podría ser un buen lugar para comenzar.

+0

Resultó ser Es relativamente fácil de usar esto con mi acordeón, y funciona muy bien. El beneficio adicional es que me permite mantener toda la lógica fuera del marcado y tener todo en javascript. –

0

Creo que su problema no es la sincronización sino la división fraccionaria de un píxel. Si prueba este código, se ve sin problemas para los manejadores 1 y 2, pero no para los demás en Firefox 3, pero todavía se ve nervioso en Chrome.

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

¿Ha pensado en hacer que la posición de los elementos sea estática o absoluta? Si solo mueve la posición de dos elementos, no tiene que preocuparse de que los otros salten. Dame un segundo y trataré de dar un ejemplo.

2

Esto no resuelve las animaciones en ejecución en paralelo, sin embargo, reproduce el comportamiento esperado sin el jitter. Coloqué la sección dentro del mango para reducir el número de animaciones. Podría usar andSelf() para hacer el código más pequeño, pero sería más difícil de leer. Tendrá que hacer algunos ajustes de estilo.

<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

¡Este código funciona! Es suave y sin movimientos no deseados. –

+0

Después de jugar con esto por un tiempo, he decidido usar la solución publicada por Corbin March. –

0

Actualización: Ya no estoy usando el plugin de syncAnimate de John Resig. Vea mi respuesta posterior para la solución final

Solo quería proporcionar la solución de trabajo final que estoy empleando en mi proyecto. Utiliza el syncAnimate plugin que John Resig escribió (publicado por Corbin March).

Este código:

  • Leer y utilizar la altura de la sección de CSS
  • le permiten establecer la duración de la animación, y la sección activa por defecto a través de un objeto opciones.
  • Detecta automáticamente la posición del mango en relación con la sección y lo ajusta en consecuencia. Así que mueve las manijas encima o debajo de una sección en el marcado y no tiene que cambiar el código 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

Después de algunas comprobaciones más, esto todavía no funciona muy bien con los navegadores que no son FF. Todavía hay una inestabilidad notable. Pude hacerlo con éxito con la función scriptaculous 'Effect.Parallel'. Pero todavía estoy buscando una manera de hacerlo con jquery. –

0

No se puede hacer un efecto paralelo en jquery con la cola y el alcance adecuados. Scriptaculous lo hizo bien con la cola y el alcance donde jQuery, por otro lado, tiene .queue y .animate que son básicamente inútiles combinados. Lo único que jQuery es bueno para la salida de la caja está impulsando algunos atributos de estilo en el dom mientras que Scriptaculous cubre todo el espectro de lo que es posible con los efectos.

Necesita usar Scriptaculous y John Resig debe reconsiderar jQuery.fx, debería echarle un vistazo a scripty2.com mientras lo hace.

1

Gracias a Adam Plumb por una gran solución para animaciones paralelas. Sin embargo, tuve un pequeño problema y fue que, de alguna manera, guardé las funciones de animaciones anteriores que arreglé estableciendo las reglas en {} antes de agregarlas a la función init. Sin embargo, probablemente se pueda hacer de una mejor manera. También agregué una función de devolución de llamada que se llama cuando la animación ha terminado.

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); 
    } 
}); 
Cuestiones relacionadas