Ad

HTML5 Canvas ToDataURL Is Always The Same In A For Loop

- 1 answer

Consider this JSFiddle. In it I select photos which I then want to base64 encode using canvas so I can store them in sessionStorage for deferred uploading. Because I have it set for multiple files, I loop through each one and create an image and a canvas, but no matter what it just seems to output the exact same base64 encoded image every time. Through testing I know that on each loop iteration the image is different and does indeed point to a different file blob, but the canvas is just outputting the same thing over and over, which I think is also the last file in the files list. Sometimes it will also just output a "data," string and that's it. I'd love it if someone can point me in the right direction.

Code is show below:

HTML

<style type="text/css">
    input[type="file"] {
        display: none;
    }

    a {
        display: inline-block;
        margin: 6px;
    }
</style>
<form action="#" method="post">
    <input type="file" accept="image/*" multiple />
    <button type="button">Select Photos</button>
</form>
<nav></nav>

JavaScript

console.clear();

$(function () {
  $("button[type=button]").on("click", function (e) {
    $("input[type=file]").trigger("click");
  });
  
  $("input[type=file]").on("change", function (e) {
    var nav = $("nav").empty();
    
    for (var i = 0, l = this.files.length; i < l; i++) {
      var file = this.files[i],
          image = new Image();
          
      $(image).on("load", i, function (e) {
        var canvas = document.createElement("canvas"),
            context = canvas.getContext("2d");
        
      	canvas.height = this.height;
      	canvas.width = this.width;
      	
        context.drawImage(this, 0, 0);
        
        nav.append("<a href=\"" + canvas.toDataURL("image/jpeg") + "\" target=\"_blank\">" + e.data + "</a>");
        
        URL.revokeObjectURL(this.src);
      });
      
      image.src = URL.createObjectURL(file);
    }
  });
});

Ad

Answer

but no matter what it just seems to output the exact same base64 encoded image every time.

.load() event is asynchronous. You can use $.when() , $.Deferred(), substitute $.map() for for loop to handle asynchronously loaded img elements. The caveat that the displayed a element text may not be in numerical order; which can be adjusted by sorting the elements at .then(); though not addressed at Question, if required, the listing or loading of images sequentially can also be achieved.

$("input[type=file]").on("change", function(e) {
    var nav = $("nav").empty();
    var file = this.files
    $.when.apply($, $.map(file, function(img, i) {    
      return new $.Deferred(function(dfd) {
        var image = new Image();
        $(image).on("load", i, function(e) {
          var canvas = document.createElement("canvas")
          , context = canvas.getContext("2d");   
          canvas.height = this.height;
          canvas.width = this.width;    
          context.drawImage(this, 0, 0);    
          nav.append("<a href=\"" 
                    + canvas.toDataURL() 
                    + "\" target=\"_blank\">" 
                    + e.data + "</a>");
          dfd.resolve()
          URL.revokeObjectURL(this.src);
        });
        image.src = URL.createObjectURL(img);
        return dfd.promise()
      })
    })).then(function() {
      nav.find("a").each(function() {
        console.log(this.href + "\n");
      })
    })
  });
})

jsfiddle https://jsfiddle.net/bc6x3s02/19/

Ad
source: stackoverflow.com
Ad