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);
  }
}
2
  • 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. jsfiddle.net/rudiedirkx/4ycgqyau/2 Commented Jul 15, 2016 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 Commented Jul 15, 2016 at 16:50

3 Answers 3

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

Sign up to request clarification or add additional context in comments.

Comments

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/

Comments

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)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.