11

jsFiddle URL: http://jsfiddle.net/Xotic750/AjtLx

Been working on this all day and I can't see the issue. It's probably due to my narrow understanding of how FileReader objects work but what I'm trying to do is use readAsDataURL() to obtain images a user has selected and preview them on the screen in a table. Everything is working minus...you guessed it...the preview...well sort of. I'm thinking I'm close because the preview will work, BUT it only displays the last image of the set. Say for example if I uploaded 6 images then the first row of 3 images would be broken, the second row the first 2 would be broken, then the final 6th image would display the preview....Any advice greatly appreciated. Also, once this works it might help others trying to do the same thing because I've searched all over for a solution to this issue and I can't seem to dig anything up....

function PreviewImages() {
    var inputID = document.getElementById('input_clone');
    var totalImages = inputID.files.length;
    var imagesPerRow = 3;
    var numRows = totalImages / imagesPerRow;
    var row = "";
    var cell = "";
    var element1 = "";
    var elementID = "";


    for(var i = 0; i < numRows; i++){ //create rows
        row = document.getElementById('image_preview_table').insertRow(i);
        for(var ii = 0; ii < imagesPerRow; ii++){ //create cells
            cell = row.insertCell(ii);
            elementID = "img_" + ii;
            element1 = document.createElement("img");
            element1.name = elementID;
            element1.id = elementID
            cell.appendChild(element1);

            oFReader = new FileReader();

            oFReader.onload = function(oFREvent){
                var dataURI = oFREvent.target.result;
                var image = document.getElementById(elementID);
                image.src = dataURI;
            };

                oFReader.readAsDataURL(document.getElementById("input_clone").files[ii]);

    }
}
}
whitwhoa
  • 2,389
  • 4
  • 30
  • 61

2 Answers2

17

The problem with your code is: readAsDataURL() is asynchronous, you should wait until it finishes reading before you invoke a second reading or make a new instance by calling new FileReader().

The answer by Xotic750 works because he creates one FileReader for each image, while you used only one FileReader.

But, using FileReader to preview images is not a good choice, as FileReader.readAsDataURL() converts the whole image to a large string (in the form of "data:image/jpeg;base64,/9j/4SVaRXhpZgAAS......"), and you show the image by placing the whole string of image data into the img.src attribute, if your image is large, you take the risk of running out of memory.

img.src is meant to contain the url of the image, not the data of the image, although you can assign a url containing the whole image data via img.src = "data:image/jpeg;......".

So, you should use window.URL.createObjectURL() to create a url referring to your local image, and assign this url to img.src:

...
img.src = window.URL.createObjectURL(fileInput.files[i]);
...
Liang Yan
  • 171
  • 1
  • 3
  • 1
    Thanks for pointing dataUrl and gotcha, been struggling with previews freezing UI on loading moderately large images (> 200kb) – Dr.Strangelove Dec 02 '17 at 05:37
12

Here is a solution, quite a bit of adjustment was made to your original to get it to work, as you will probably notice.

CSS

div.rounded {
    width: 100%;
    border-style: solid;
    border-width: 1px;
    border-radius: 5px;
}
label {
    display: block;
}
input {
    display: block;
}
#previewTable {
    width: 100%;
}

HTML

<div id="imagesDiv" class="rounded">
    <label for="chooseFiles">Add Images</label>
    <input type="file" id="chooseFiles" multiple="multiple" />
    <table id="previewTable">
        <thead id="columns"></thead>
        <tbody id="previews"></tbody>
    </table>
</div>

Javascript

(function (global) {
    var imagesPerRow = 3,
        chooseFiles,
        columns,
        previews;

    function PreviewImages() {
        var row;

        Array.prototype.forEach.call(chooseFiles.files, function (file, index) {
            var cindex = index % imagesPerRow,
                oFReader = new FileReader(),
                cell,
                image;

            if (cindex === 0) {
                row = previews.insertRow(Math.ceil(index / imagesPerRow));
            }

            image = document.createElement("img");
            image.id = "img_" + index;
            image.style.width = "100%";
            image.style.height = "auto";
            cell = row.insertCell(cindex);
            cell.appendChild(image);

            oFReader.addEventListener("load", function assignImageSrc(evt) {
                image.src = evt.target.result;
                this.removeEventListener("load", assignImageSrc);
            }, false);

            oFReader.readAsDataURL(file);
        });
    }

    global.addEventListener("load", function windowLoadHandler() {
        global.removeEventListener("load", windowLoadHandler);
        chooseFiles = document.getElementById("chooseFiles");
        columns = document.getElementById("columns");
        previews = document.getElementById("previews");

        var row = columns.insertRow(-1),
            header,
            i;

        for (i = 0; i < imagesPerRow; i += 1) {
            header = row.insertCell(-1);
            header.style.width = (100 / imagesPerRow) + "%";
        }

        chooseFiles.addEventListener("change", PreviewImages, false);
    }, false);
}(window));

On jsfiddle

Xotic750
  • 22,914
  • 8
  • 57
  • 79
  • Nice! Seems to work great in chrome, but having an issue in firefox. – whitwhoa May 08 '13 at 03:13
  • 2
    I took a look myself and found the problem. The second parameter is actually not optional in `removeEventListener`. Chrome apparently doesn't complain, but Firefox does. The modified fiddle can be found at http://jsfiddle.net/6cgTj/23/. – Ray Nicholus May 08 '13 at 03:59
  • 1
    @Ray Nicholas, yes that is the problem on Firefox. I have updated and all should be working. – Xotic750 May 08 '13 at 10:52