Search code examples
javascriptknockout.jsknockout-mapping-plugin

Fading in container elements in Knockout.js after image has loaded


I have an images and some text that is being generated by Knockout.js. The text shows up immediately while the images takes longer to load.

I want to have a class on the container that houses each image/text pair and when the image finishes loading then toggle that class and have the image/text pair fade in at the same time.

I know there is an afterRender callback, I'm having difficulty writing the function that gets called to target the element and manipulate the class.

http://jsfiddle.net/ClydeGrey/mfjv5j2c/4/

function SomeModel() {
  this.Firstname = ko.observable();
  this.Lastname = ko.observable();
  this.fadeIn = ko.observable();
}


function SomeViewModel() {
  var self = this;

  this.ArrayOfModels = ko.mapping.fromJS([]);
  this.dataArray = new Array;

  this.GetModelsByAjax = function() {
    $.ajax({
      type: 'POST',
      url: '/echo/json/',
      data: {
        json: JSON.stringify([{
            loaded: false,
            img: "https://source.unsplash.com/random",
            Firstname: "Bob",
            Lastname: "Smith"
          },
          {
            loaded: false,
            img: "https://source.unsplash.com/user/erondu",
            Firstname: "Ted",
            Lastname: "Smith"
          },

          {
            loaded: false,
            img: "https://source.unsplash.com/user/erondu/1600x900",
            Firstname: "Jim",
            Lastname: "Gump"
          }
        ]),
        delay: 0
      },
      context: this,
      success: function(data) {
        for (var x = 0; x < data.length; x++) {
          self.dataArray.push(data[x]);
        }
        /* self.dataArray.push(data); */
        console.log(self.dataArray);
        console.log(typeof(self.dataArray));
        console.log(data);
        console.log(typeof(data));
        self.SuccessfullyRetrievedModelsFromAjax(self.dataArray);
      },
      dataType: 'json'
    });
  };

  this.SuccessfullyRetrievedModelsFromAjax = function(models) {
    ko.mapping.fromJS(models, self.ArrayOfModels);
  };
}

ko.applyBindings(new SomeViewModel());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.microsoft.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://rawgit.com/SteveSanderson/knockout.mapping/master/build/output/knockout.mapping-latest.debug.js"></script>
<button data-bind="click: GetModelsByAjax">Go</button>
<div data-bind="template: {name: 'model-template', foreach: ArrayOfModels}"></div>

<script id="model-template" type="text/html">
  <div data-bind="css: { fadeIn: loaded() === true }">
    <div data-bind="text: Firstname"></div>
    <div data-bind="text: Lastname"></div>
    <img width="100" height="100" data-bind="attr: { src: img }">
  </div>
</script>


Solution

  • You already have a loaded property that is initialized as false and observable. We'll use this property in two ways:

    1. In a css binding, to add an is-visible class to the img + label wrapper that we can style
    2. In an event binding, to set the value when the <img> tag fires a load event.

    Your new template, without any other changes:

    <div class="Img" data-bind="css: { 'is-visible': loaded }">
        <div data-bind="text: Firstname"></div>
        <div data-bind="text: Lastname"></div>
        <img  width="100" height="100" 
              data-bind="attr: { src: img }, 
                         event: { load: loaded.bind($data, true) }">
    </div>
    

    The css for the fade, that you can change to your liking:

    .Img {
      opacity: 0;
      transition: opacity 1s ease-in-out;
    }
    
    .Img.is-visible { opacity: 1; }
    

    In an updated version of your fiddle: http://jsfiddle.net/L7cn6y5r/