2009-05-01 22 views
7

Sto provando a creare un widget di fisarmonica in jQuery simile a jquery's accordion plugin, con la differenza che voglio che gli handle vengano visualizzati sotto il loro rispettivo contenuto anziché sopra. La mia fisarmonica funziona diminuendo l'altezza della sezione di contenuto aperto e allo stesso tempo aumentando l'altezza della sezione di contenuto cliccato. Ho pubblicato un esempio here. Il mio problema è che le animazioni non sono iniziate esattamente nello stesso momento, e c'è un "salto" evidente a causa del leggero ritardo prima dell'avvio della seconda animazione.Come posso ottenere jQuery per eseguire animazioni in parallelo esatto?

Scriptaculous ha una funzione denominata Effect.Parallel che consente di creare una serie di effetti di animazione ed eseguirli in parallelo. Sfortunatamente, non riesco a trovare qualcosa di simile con jquery.

C'è un modo per eseguire animazioni parallele precise su div separate in jquery?

Modifica: Sono interessato ai metodi alternativi di codifica di questo widget per fisarmonica. Quindi, se c'è qualche altro metodo che la gente pensa possa funzionare, sono aperto a questo.

+0

Si può fare questo in SGIL, ma questo è un linguaggio di markup e un altro argomento. –

+0

Vedere la mia soluzione finale qui: http://stackoverflow.com/questions/811750/how-can-i-get-jquery-to-execute-animations-in-exact-parallel/835362#835362 –

risposta

4

più una risposta, si spera il mio ultimo ...

Purtroppo, il metodo syncAnimate di John Resig è non proprio per fiutare per il tipo di fisarmonica ani mazione che voglio fare Sebbene funzioni alla grande su Firefox, non ho potuto farlo funzionare senza problemi su IE o Safari.

Con ciò detto, ho deciso di mordere il proiettile e scrivere il mio motore di animazione che esegue semplici animazioni parallele. Il codice classe usa le funzioni jQuery ma non è un plugin jQuery. Inoltre, l'ho configurato solo per animazioni di dimensioni/posizione, che è tutto ciò di cui ho bisogno.

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

Quindi la classe di Fisarmonica originale deve essere modificata solo nel metodo animato per utilizzare la nuova chiamata.

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

Il codice HTML è ancora chiamato allo stesso modo:

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

ci sono alcune cose che posso aggiungere in futuro: - coda Animazioni - Animazioni per altri tipi di stili (colori, ecc.)

+0

Un approccio molto più semplice è passare una funzione di passaggio all'animazione. Leggi qui per maggiori dettagli: http://docs.jquery.com/Release:jQuery_1.2/Effects#Extensible_Animations – johjoh

3

John Resig ha pubblicato un synchronized animation sample (nessuna istruzione, fare clic su una casella colorata). Potrebbe essere necessario del lavoro per capire come applicarlo al tuo controllo, ma potrebbe essere un buon punto di partenza.

+0

Si è rivelato essere relativamente facile da usare questo con la mia fisarmonica, e funziona davvero bene. Il vantaggio ulteriore è che mi permette di mantenere tutta la logica dal markup e di avere tutto in javascript. –

0

Penso che il tuo problema non sia la temporizzazione ma la divisione frazionaria di un pixel. Se provi questo codice sembra agevole per gli handle 1 e 2 ma non per altri in Firefox 3, ma sembra ancora nervoso in chrome.

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

Hai mai pensato di rendere statica o assoluta la posizione degli elementi? Se muovi solo la posizione di due elementi non ti devi preoccupare degli altri che saltano. Dammi un secondo e cercherò di fare un esempio.

2

Questo non risolve l'esecuzione di animazioni in parallelo, tuttavia riproduce il comportamento previsto senza il jitter. Ho inserito la sezione all'interno dell'impugnatura per ridurre il numero di animazioni. Puoi usare andSelf() per rendere il codice più piccolo ma sarebbe più difficile da leggere. Sarà necessario apportare alcune modifiche di stile.

<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

Questo codice funziona! È liscio e senza movimenti indesiderati. –

+0

Dopo aver giocato con questo per un po 'ho deciso di usare la soluzione pubblicata da Corbin March. –

0

Update: Non sono più utilizzando plugin di syncAnimate di John Resig. Vedere la mia risposta successiva per la soluzione finale

Volevo solo fornire la soluzione di lavoro finale che sto impiegando sul mio progetto. Usa lo syncAnimate plugin scritto da John Resig (pubblicato da Corbin March).

Questo codice sarà:

  • leggere e utilizzare l'altezza di sezione da CSS
  • consentono di impostare la durata dell'animazione, e la sezione attiva di default attraverso un oggetto opzioni.
  • Rileva automaticamente la posizione della maniglia rispetto alla sezione e regola di conseguenza. Quindi muovi le maniglie sopra o sotto una sezione del markup e non devi cambiare il codice 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

Dopo alcuni controlli, questo non funziona ancora molto bene con i browser non FF. C'è ancora un notevole jitter. Sono stato in grado di farlo correttamente con la funzione Effect.Parallel scriptaculous. Ma sto ancora cercando un modo per farlo con jquery. –

0

Non è possibile eseguire un effetto parallelo in jquery con coda e ambito appropriati. Scriptaculous ha capito bene con la coda e l'ambito in cui jQuery d'altra parte ha .queue e .animate che sono fondamentalmente inutili. L'unica cosa che jQuery è buona per out-of-the-box sta spingendo alcuni attributi di stile in giro sul dom mentre Scriptaculous copre l'intero spettro di ciò che è possibile con gli effetti.

È necessario utilizzare Scriptaculous e John Resig dovrebbero ripensare jQuery.fx, dovrebbe dare un'occhiata a scripty2.com mentre lui è a questo.

1

Grazie Adam Plumb per una soluzione davvero eccezionale per le animazioni parallele. Ho avuto un piccolo problema con esso e questo è che in qualche modo ha salvato i ruoli dalle animazioni precedenti, l'ho risolto impostando le regole su {} prima di aggiungerle nella funzione init. Può probabilmente essere fatto in un modo migliore però. Ho anche aggiunto una funzione di callback che viene chiamata al termine dell'animazione.

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