敲除绑定中的同步动画

Sychronous animations in knockout binding

我想在某些属性随我的击倒模型(特别是运动)发生变化时应用动画。我需要这些动画是同步的,如果有多个动画在进行,用户会感到非常困惑。

我想使用 knockout 自定义绑定来执行此操作,因为它应该使我的代码更易于理解,但如果这样做,我无法为 jquery 动画提供回调函数。我知道由于 javascript 限制,我无法拥有真正的同步行为,但我不知道如何伪造它。

我想要的行为: http://jsfiddle.net/3fLvpxLc/2/

$("#e1").animate({left: 50}, "slow", function() {
    // more animations
}

存在同步问题的版本: http://jsfiddle.net/hrwsd1z3/1/

ko.bindingHandlers.position = {
    update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        var value = valueAccessor();
        var valueUnwrapped = ko.unwrap(value);
        $(element).animate({left: valueUnwrapped}, "slow");
    }
}

jQuery queues 是你的朋友。使用它们,您可以序列化异步动画。

通常它们隐含地用于您在一个元素上执行的所有动画效果,即绑定到动画元素本身:

$("element").show("slow").animate({left: 25});

但是您也可以明确地使用它们。 queue 将动画添加到队列中,next 回调将下一个动画从队列中取出(您可以方便地将其作为 complete 处理程序传递)。这样您就可以将动画绑定到与动画元素不同的元素:

$("#container").queue(function (next) {
    $("element").show("slow", next);
}
$("#container").queue(function (next) {
    $("element").animate({left: 25}, next);
}

有了这些知识,任务就变得简单了:

ko.bindingHandlers.syncPosition = {
    init: function(element, valueAccessor) {
        var newPosition = ko.toJS(valueAccessor());

        // set element to its initial position
        $(element).css(newPosition);
    },
    update: function(element, valueAccessor) {
        var newPosition = ko.toJS(valueAccessor());

        // queue position update as animation to a common element, e.g. the body
        $(document.body).queue(function( next ) {
            $(element).animate(newPosition, "slow", next);
        });
    }
};

function Item(id, top, left) {
    this.id = ko.observable(id);
    this.position = {
         top: ko.observable(top),
         left: ko.observable(left)
    };
}
function VM(params) {
    var self = this;
    
    self.elements = ko.observableArray([
        new Item("e1"),
        new Item("e2"),
        new Item("e3")
    ]);
}

var vm = new VM();
ko.applyBindings(vm);

vm.elements()[0].position.left(50);
vm.elements()[1].position.left(75);
vm.elements()[2].position.left(25);
vm.elements()[1].position.left(125);
vm.elements()[2].position.top(10);
vm.elements()[1].position.top(20);
vm.elements()[0].position.top(30);
vm.elements()[0].position.left(0);
vm.elements()[1].position.left(0);
vm.elements()[2].position.left(0);
div.container {
    position: relative;
}
div.container > div {
    width: 20px;
    height: 20px;
    position: absolute;
}
#e1 {
    background-color: blue;
}
#e2 {
    background-color: red;
}
#e3 {
    background-color: green;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

<div class="container" data-bind="foreach: elements">
    <div data-bind="syncPosition: position, attr: {id: id}"></div>
</div>