Domanda

Sto cercando di creare un widget per fisarmonica in jquery simile a plugin di fisarmonica di jquery , con la differenza che voglio che le maniglie appaiano sotto il loro rispettivo contenuto anziché sopra. La mia fisarmonica funziona diminuendo l'altezza della sezione dei contenuti aperti e allo stesso tempo aumentando l'altezza della sezione dei contenuti cliccati. Ho pubblicato un esempio qui . Il mio problema è che le animazioni non vengono avviate esattamente nello stesso momento e c'è un notevole "salto" a causa del leggero ritardo prima dell'inizio della seconda animazione.

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

Esiste un modo per eseguire animazioni parallele precise su div separati in jquery?

Modifica: sono altrettanto interessato ai metodi alternativi per codificare questo widget per fisarmonica. Quindi, se esiste un altro metodo che la gente pensa possa funzionare, sono aperto a questo.

È stato utile?

Soluzione

Un'altra risposta, si spera la mia ultima ...

Sfortunatamente, il metodo syncAnimate di John Resig non è all'altezza per l'animazione del tipo di fisarmonica che voglio fare. Mentre funziona alla grande su Firefox, non sono riuscito a farlo funzionare senza problemi su IE o Safari.

Detto questo, ho deciso di mordere il proiettile e scrivere il mio motore di animazione che fa semplici animazioni parallele. Il codice di classe utilizza le funzioni jquery ma non è un plugin jquery. Inoltre, l'ho impostato 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");
    }
});

L'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 potrei aggiungere in futuro:  - Animazioni in coda  - Animazioni per altri tipi di stili (colori, ecc.)

Altri suggerimenti

John Resig ha pubblicato un esempio di animazione sincronizzata (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.

Questo non risolve le animazioni in esecuzione in parallelo ma riproduce il comportamento previsto senza il jitter. Ho inserito la sezione all'interno della maniglia per ridurre il numero di animazioni. È possibile utilizzare andSelf () per ridurre il codice, ma sarebbe più difficile da leggere. Dovrai apportare alcune modifiche allo 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>

Grazie Adam Plumb per un'ottima soluzione per animazioni parallele. Tuttavia, ho avuto un piccolo problema e questo è stato che in qualche modo ha salvato i ruoli delle animazioni precedenti, ho risolto il problema impostando le regole su {} prima di aggiungerle nella funzione init. Probabilmente può 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);
    }
});

Penso che il tuo problema non sia il tempismo ma la divisione frazionaria di un pixel. Se provi questo codice sembra liscio per gestire 1 e 2 ma non altri in Firefox 3 ma sembra ancora nervoso in Chrome.

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

Hai pensato di rendere la posizione degli elementi statica o assoluta? Se stai spostando solo la posizione di due elementi, non devi preoccuparti che gli altri saltino. Dammi un secondo e proverò a fare un esempio.

Aggiornamento: non sto più usando il plugin syncAnimate di John Resig. Vedi la mia risposta successiva per la soluzione finale

Volevo solo fornire la soluzione di lavoro finale che sto impiegando nel mio progetto. Utilizza il plugin syncAnimate che John Resig ha scritto (pubblicato da Corbin March).

Questo codice sarà:

  • Leggi e usa l'altezza della sezione dal CSS
  • Consenti di impostare la durata dell'animazione e la sezione attiva predefinita tramite un oggetto opzioni.
  • Rileva automaticamente la posizione della maniglia rispetto alla sezione e si 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");
    }
});

Non è possibile eseguire un effetto parallelo in jquery con la coda e l'ambito corretti. Scriptaculous ha fatto bene con la coda e l'ambito in cui jQuery ha invece .queue e .animate che sono fondamentalmente inutili combinati. L'unica cosa per cui jQuery è utile è spingere alcuni attributi di stile sul dom mentre Scriptaculous copre l'intero spettro di ciò che è possibile con gli effetti.

Devi usare Scriptaculous e John Resig dovrebbe ripensare jQuery.fx, dovrebbe dare un'occhiata a scripty2.com mentre ci sta.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top