bindingknockout.jsbindinghandlers

knockout js fade transition binding handler when image src binding changes


if I have the following simple js/knockout code:

.js (viewModel):

var image = ko.observable('http://placehold.it/300x150');

view:

<img data-bind={attr: {src: image}} />

How do I create a ko binding handler that will perform a simple cross-fade when the image property changes?


Solution

  • I think the best solution is to create another image and fade it in.

    Here is a simple binding handler that will create a new image (or a div with a background image in this case) within the bound element every time the source observable is updated and fade to it with a CSS3 animation:

    ko.bindingHandlers.transitionImage = {
        init: function (element, valueAccessor) {
            // create a holder for the images
            var holder = document.createElement('div');
            holder.style['position'] = 'relative';
            holder.style['height'] = '100%';
            holder.style['width'] = '100%';
            element.appendChild(holder);
    
            // create initial image
            var url = ko.utils.unwrapObservable(valueAccessor());
            var fore = document.createElement('div');
            fore.style['background-image'] = 'url(' + url + ')';
            fore.style['position'] = 'absolute';
            fore.style['height'] = '100%';
            fore.style['width'] = '100%';
            fore.style['background-size'] = '100% 100%'
            holder.appendChild(fore);
        },
        update: function (element, valueAccessor) {
            var url = ko.utils.unwrapObservable(valueAccessor());
    
            // retrieve the holder element
            var holder = element.childNodes[0];
    
            // create new image to transition to
            var fore = document.createElement('div');
            fore.className = 'transition';
            fore.style['background-image'] = 'url(' + url + ')';
            fore.style['position'] = 'absolute';
            fore.style['height'] = '100%';
            fore.style['width'] = '100%';
            fore.style['background-size'] = '100% 100%'
            fore.style['opacity'] = '0'; // ensure it is hidden
            holder.appendChild(fore);
    
            // CSS3 animate opacity
            setTimeout(function () {
                fore.style['opacity'] = '1';
            }, 0);
    
            // dispose of unnecessary hidden images eventually
            setTimeout(function () {
                var toRemove = holder.childNodes[0];
                var removed = holder.removeChild(toRemove);
                delete removed;
            }, 10000);
        }
    };
    

    It requires some css in place for the opacity to get animated:

    div.transition {
        -webkit-transition: opacity 0.5s linear;
        -moz-transition: opacity 0.5s linear;
        -o-transition: opacity 0.5s linear;
        transition: opacity 0.5s linear;
    }
    

    To use, bind it to a div container:

    <div data-bind="transitionImage: image" style="width: 100px; height: 100px"></div>
    

    The advantage with this approach is that it is quite easy to substitute in more complex animations like sliding by just playing with the css.

    Here is a fiddle for it!