Skip to content Skip to sidebar Skip to footer

Transitions Between Different Knockout Components

I'm trying to apply CSS transitions as I switch between knockout components but I'm not having much joy in achieving this. Essentially I want to have a div with a fixed width, but

Solution 1:

You're actually switching the component meaning the DOM is being re-constructed, so I don't see a way to animate using CSS.

What you can do is build your own binding handler which does the animating for you using Javascript:

ko.bindingHandlers.animatingComponent = {
    init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        var value = valueAccessor();
        var componentName = value.name;
        // create a new observable so we can delay the moment ko's component // binding builds the new componentvar actualComponentName = ko.observable(componentName());
        componentName.subscribe(function(newComponent) {
            $(element).hide(500, function() {
                actualComponentName(newComponent);
                $(element).show(500);
           });
        });

        ko.bindingHandlers.component.init(element, function() { 
            return { name: actualComponentName, params: value.params}; 
        }, allBindings, viewModel, bindingContext);
    }
};

ko.components.register("big", {
    viewModel: function (vm) {
        this.items = vm.value.items;
    },
    template: '<div class="big box" data-bind="foreach: items"><p class="item" data-bind="text: name"></p></div>'
});

ko.components.register("small", {
    viewModel: function (vm) {        
        this.items = vm.value.items;
    },
    template: '<div class="small box" data-bind="foreach: items"><span class="item" data-bind="text: name"></span></div>'
});


var vm = {};
vm.componentName = ko.observable("small");
vm.items = ko.observableArray([{ name: "A" }, { name: "B" }, { name: "C" }]);
ko.applyBindings(vm);

setInterval(function() {
    if(vm.componentName() === "small") { vm.componentName("big"); }
    else { vm.componentName("small"); }
}, 3000);
.box {
    width: 200px;
}
.big {
    border: thin solid black;
}
.small {
    border: thin solid black;
    padding: 10px10px10px10px;
}
.item {
    padding-left: 10px;
}
<scriptsrc="https://code.jquery.com/jquery-1.11.3.min.js"></script><scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script><divdata-bind="animatingComponent: { name: componentName, params: { value: $data } }"></div>

Solution 2:

It only took most of the day, but I got the transition to work. I have a variable that remembers the last display size (which is where it will start when the component changes). When the component changes, I set visibility to hidden and sizes to default so I can get the intended size of the div. Then I size it back to the last display size and make it visible, then size it to the new size, and the transition happens.

Note that I also had to change the CSS a bit.

ko.components.register("big", {
viewModel: function (vm) {
    this.items = vm.value.items;
},
template: '<div class="big box" data-bind="style:$root.boxSize, foreach: items, sizeGet:$root.boxSize"><p class="item" data-bind="text: name"></p></div>'
});

ko.components.register("small", {
viewModel: function (vm) {
    this.items = vm.value.items;
},
template: '<div class="small box" data-bind="style:$root.boxSize, foreach: items, sizeGet:$root.boxSize"><span class="item" data-bind="text: name"></span></div>'
});


var unclipped;
ko.bindingHandlers.sizeGet = {
init: function (element, valueAccessor) {
    var sizer = valueAccessor();
    sizer({
        height: '',
        width: '',
        visibility: 'hidden'
    });
    var nextUnclipped = {
        height: element.scrollHeight + 'px',
        width: element.scrollWidth + 'px',
        visibility: 'visible'
    };

    if (unclipped) sizer(unclipped);
    unclipped = nextUnclipped;
    setTimeout(function () {
        sizer(unclipped);
    }, 0);
}
};

var vm = (function () {
var activeComponent = ko.observable('small'),
    defaultSize = {
        width: '',
        height: ''
    },
    boxSize = ko.observable(defaultSize);

return {
    componentName: activeComponent,
    boxSize: boxSize,
    items: ko.observableArray([{
        name: "A"
    }, {
        name: "Big"
    }, {
        name: "Cat"
    }, {
        name: "Dropping"
    }])
};
}());
ko.applyBindings(vm);

var i = setInterval(function () {
if (vm.componentName() === "small") {
    vm.componentName("big");
} else {
    vm.componentName("small");
}
}, 3000);

setTimeout(clearInterval.bind(null, i), 45000);
.box {
    width:200px;
    -webkit-transition: height 2s, width 2s;
    transition: height 2s, width 2s;
    overflow:hidden;
}
.big {
    border: thin solid black;
}
.small {
    border: thin solid black;
    padding: 10px10px10px10px;
}
.item {
    padding-left: 10px;
}
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script><divdata-bind="component: { name: componentName, params: { value: $data } }"></div>

Post a Comment for "Transitions Between Different Knockout Components"