title: Performance issues when loading local images into the browser
body: <p>I'm using <a href="https://developer.mozilla.org/en-US/docs/Web/API/FileReader" rel="noreferrer">FileReader</a> to display local images in a Backbone application. Smaller images are displayed immediately without any issues, but when the images are several megabytes or larger, it takes Chrome 2 or 3 seconds to load the image (on a brand new MacBook Pro with 8 GB RAM).</p>
  
  <p>As pointed out in the comments, large images load instantaneously in <a href="http://www.html5rocks.com/en/tutorials/file/dndfiles/#toc-reading-files" rel="noreferrer">this File API tutorial</a>, so the issue is not with <code>FileReader</code> but rather with my particular implementation in Backbone.</p>
  
  <p>I've included the relevant Backbone model and view below. Here's an outline of the logic:</p>
  
  <ol>
  <li>The model creates a new <code>FileReader</code> into which the file path is passed.</li>
  <li>Once the file is loaded, an <code>Image</code> is created and the <code>src</code> of the <code>Image</code> is set to the data URL provided by the <code>FileReader</code>.</li>
  <li>Once the image has loaded, the model is updated with the width and height of the image (used elsewhere in the application) and the <code>initialized</code> attribute of the model is set to <code>true</code> (thus firing a <code>change</code> event on the model).</li>
  <li>When the view detects the <code>change</code> event on the model, the <code>render</code> function is called, which replaces the background image of a <code>div</code> with the data URL.</li>
  </ol>
  
  <p><strong>Backbone Model</strong></p>
  
  <pre><code>var PhotoModel = Backbone.Model.extend({
  
    initialize: function() {
      this.set({"available": true});
      this.initialized = false;
      if (this.get("file")) { 
        this.uploadPhoto();
      }
    },
  
    uploadPhoto: function() {
      var file = this.get("file");
      var reader = new FileReader();
      var model = this;
      reader.onload = function(event) {
        var image = new Image();
        image.onload = function() {
          model.set({width: this.width, height: this.height});
          model.set("initialized", true);
        };
        image.src = event.target.result; 
        model.set("dataURL", event.target.result);
      }
      reader.readAsDataURL(file);
    },
  });
  </code></pre>
  
  <p><strong>Backbone View</strong></p>
  
  <pre><code>var PhotoFormView = Backbone.View.extend({
  
    className: "photo-form",
  
    initialize: function() {
      this.listenTo(this.model, "change", this.render);
      this.render();
    },
  
    render: function() {
      $(this.el).find(".photo").css({"background-image": "url(" + this.model.get("dataURL") + ")"});
    },
  });
  </code></pre>
  
  <p>Below is a screenshot of the Chrome timeline. The largest delay seems to happen between the data URL request and the load event (this accounts for approximately 0.5 seconds). I'm not sure what "Recalculate style" is all about. I'm also not sure why there are so many paint events (it appears to be rendering in the scrollbar area).</p>
  
  <p><img src="https://i.stack.imgur.com/aJAgn.png" alt="Chrome Timeline"></p>
  
  <p><strong>SOLUTION</strong></p>
  
  <p>I had assumed that the browser would be able to natively resize images more efficiently than any JavaScript solution, but <strong>this assumption was wrong</strong>. By resizing the images <em>before</em> they are displayed, I was able to reduce the delay to about <strong>50 milliseconds</strong>. I used an open source plugin called <a href="https://github.com/blueimp/JavaScript-Load-Image" rel="noreferrer">JavaScript Load Image</a> to handle the image loading and resizing (it uses the HTML5 <code>canvas</code> tag). Here's my modified Backbone model:</p>
  
  <pre><code>var PhotoModel = Backbone.Model.extend({
  
    initialize: function() {
      this.set({available: true});
      this.set({initialized: false});
      if (this.get("file")) { 
        this.uploadPhoto();
      }
    },
  
    uploadPhoto: function() {
      var model = this;
      loadImage(this.get("file"), function (img) {
        model.set({img: img});
        model.set({initialized: true});
      }, {maxHeight: 344});
    },
  });
  </code></pre>