我正在尝试在 jquery 中创建一个类似的手风琴小部件 jquery 的手风琴插件, ,不同之处在于我希望句柄出现在各自内容的下方而不是上方。我的手风琴的工作原理是减小打开内容部分的高度,同时增加单击内容部分的高度。我已经发布了一个例子 这里. 。我的问题是动画不是在完全相同的时间开始的,并且由于第二个动画开始之前的轻微延迟而存在明显的“跳跃”。

Scriptaculous 有一个函数叫做 效果.并行 它允许您创建一系列动画效果并并行执行它们。不幸的是,我似乎找不到与 jquery 类似的东西。

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

编辑:我对编码这个手风琴小部件的替代方法非常感兴趣。因此,如果人们认为还有其他可行的方法,我对此持开放态度。

有帮助吗?

解决方案

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

不幸的是,John 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);
    }
});

然后只需要在animate方法中修改原始的Accordion类以使用新的调用。

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>

我可能会在未来添加一些内容:   - 排队动画   - 其他类型样式(颜色等)的动画

其他提示

John Resig发布了同步动画示例(没有说明,请点击彩色框)。可能需要一些工作才能弄清楚如何将它应用到您的控件中,但它可能是一个很好的起点。

这并不能解决并行运行的动画问题,但它会在没有抖动的情况下重现您的预期行为。我将部分放在手柄内以减少动画的数量。您可以使用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>

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

我认为你的问题不是时间而是像素的分数。如果您尝试使用此代码,它对于句柄1和2看起来很平滑,但在Firefox 3中看起来不是其他代码,但在Chrome中看起来仍然很突出。

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

您是否考虑过将元素的位置设为静态或绝对?如果你只移动两个元素的位置,你不必担心其他的跳跃。给我一点,我会试着做一个例子。

更新:我不再使用 John Resig 的syncAnimate 插件。最终解决方案见我后面的回答

我只是想提供我在项目中使用的最终工作解决方案。它使用 同步动画插件 John Resig 所写(由 Corbin March 发布)。

该代码将:

  • 读取并使用 CSS 中的部分高度
  • 允许您通过选项对象设置动画持续时间和默认活动部分。
  • 自动检测手柄相对于部分的位置并进行相应调整。因此,您可以将句柄移动到标记中某个部分的上方或下方,而不必更改 js 代码。

超文本标记语言

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

用户界面.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");
    }
});

您无法在jquery中使用正确的队列和范围执行并行效果。 Scriptaculous在队列和范围方面做得恰到好处,另一方面jQuery的.queue和.animate基本上是无用的组合。 jQuery唯一有利于开箱即用的东西就是在dom上推出一些样式属性,而Scriptaculous则涵盖了可能产生影响的整个范围。

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top