2

When a user uploads an image I want to resize the image with canvas. The code below works when you just upload one image. But if you select multiple images the canvas item doesnt display an image (only on the last canvas element) does anyone know what is going wrong please?

https://jsfiddle.net/4ycgqyau/

 function prepPreview(input) {
  var container = document.getElementById("preview");

  var files = jQuery(input).get(0).files;
  for (var i = 0; i < files.length; i++) {

    // create canvas element & resize image
    var canvasEl = document.createElement("canvas");
    canvasEl.id = 'canvas_' + i;
    canvasEl.width = "250";
    canvasEl.height = "140";
    container.appendChild(canvasEl);

    var canvas = document.getElementById('canvas_' + i);
    var context = canvas.getContext('2d');

    var reader = new FileReader();
    reader.onload = function(event) {
      var imageObj = new Image();
      imageObj.onload = function() {
        var wrh = imageObj.width / imageObj.height;
        var newWidth = canvas.width;
        var newHeight = newWidth / wrh;
        if (newHeight > canvas.height) {
          newHeight = canvas.height;
          newWidth = newHeight * wrh;
        }
        context.drawImage(imageObj, 0, 0, newWidth, newHeight);
      };
      imageObj.src = reader.result;
    }
    reader.readAsDataURL(files[i], 'canvas_' + i);
    console.log(files[i], 'canvas_' + i);

    // create input
    var input = document.createElement("input");
    input.type = "text";
    input.setAttribute('maxLength', 150);
    input.name = "caption[" + i + "]";
    container.appendChild(input);

    // create row around image/input
    var row = document.createElement("div");
    row.className = 'user-uploads__row';
    canvasEl.parentNode.insertBefore(row, canvasEl, input);
    row.appendChild(canvasEl);
    row.appendChild(input);
  }
}
NoDachi
  • 874
  • 4
  • 10
  • 20
  • You're creating variables inside a loop. That's dangerous in JS because JS is very async. Replace the loop with a `forEach(callback)` or divide it into callable functions. https://jsfiddle.net/rudiedirkx/4ycgqyau/2/ – Rudie Jul 15 '16 at 16:49
  • And what is this!? `files = jQuery(input).get(0).files` Creating a jQuery object to extract the DOM object? Sounds like `files = input.files` – Rudie Jul 15 '16 at 16:50

3 Answers3

2

JavaScript has function-level scope, it means variable those are created in a function-block will have a scope within a function. In for-loop, block-level({//code}) is being created in which value of a variable is being over-written in nest iteration hence only last canvas is considered in onload because of asynchronous nature of the event

Easier solution will be to use Array#forEach where handler-function for each file in will have its own scope

Use [].forEach.call to use prototype of array over array-like object having to iterate through

Also not that jQuery(input).get(0).files is too ugly as you are not using anything of jQuery hence just access files property of input

jQuery("#uploads").change(function() {
  prepPreview(this);
});

function prepPreview(input) {
  var container = document.getElementById("preview");
  var files = input.files;
  [].forEach.call(files, function(inp, i) {
    var canvasEl = document.createElement("canvas");
    canvasEl.id = 'canvas_' + i;
    canvasEl.width = "250";
    canvasEl.height = "140";
    container.appendChild(canvasEl);
    var canvas = document.getElementById('canvas_' + i);
    var context = canvas.getContext('2d');
    var reader = new FileReader();
    reader.onload = function(event) {
      var imageObj = new Image();
      imageObj.onload = function() {
        var wrh = imageObj.width / imageObj.height;
        var newWidth = canvas.width;
        var newHeight = newWidth / wrh;
        if (newHeight > canvas.height) {
          newHeight = canvas.height;
          newWidth = newHeight * wrh;
        }
        context.drawImage(imageObj, 0, 0, newWidth, newHeight);
      };
      imageObj.src = reader.result;
    }
    reader.readAsDataURL(files[i], 'canvas_' + i);
    var input = document.createElement("input");
    input.type = "text";
    input.setAttribute('maxLength', 150);
    input.name = "caption[" + i + "]";
    container.appendChild(input);
    var row = document.createElement("div");
    row.className = 'user-uploads__row';
    canvasEl.parentNode.insertBefore(row, canvasEl, input);
    row.appendChild(canvasEl);
    row.appendChild(input);
  });
}
.user-uploads__row {
  background: #fff;
  padding: 20px;
  border: 1px solid #888;
  margin: 20px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<form method="post" enctype="multipart/form-data">
  <input type='file' multiple="multiple" name="uploads[]" id="uploads" />
  <div class="preview user-uploads" id="preview"></div>
</form>

Updated Fiddle

Community
  • 1
  • 1
Rayon
  • 36,219
  • 4
  • 49
  • 76
1

Check out this code jsbin - create canvas images

UPDATE:

The problem you have is problem of javascript closures.

Checkout the following links to understand how to use asynchronous functions in cycle;

JavaScript closure inside loops – simple practical example

Asynchronous Process inside a javascript for loop

http://www.javascriptcookbook.com/article/Preserving-variables-inside-async-calls-called-in-a-loop/

Community
  • 1
  • 1
Zura Sekhniashvili
  • 1,147
  • 7
  • 19
1

Please have look this version: https://jsfiddle.net/4ycgqyau/6/

You need to create a closure environment to store the canvas,context , and passing it in to the onload event. Simple way to understand it, how to create a closure?

imageObj.onload = (function(canvas,context) {
          return function (){
             // write your code
          }})(canvas,context)
Joey Etamity
  • 856
  • 4
  • 9