Subsequent HTML5 CreateObjectURL blob image preview and cropping loading bug
Asked Answered
D

1

10

I'm trying to figure out if my code is at fault or the current HTML5 File API implementation.

The code below works. The bug appears when repeating the process after already loading the image once.

The second time a file is selected the image blob loads, then there's a flicker and the image disappears. Subsequent selections after that usually work fine (sometimes there's erratic behavior if the file is big). Repeating the process for the same file selection generally works (as a fix).

Any help would be greatly appreciated.

JavaScript libraries used

  • JQuery 1.7.1

  • JQuery Tools 1.2.6

  • JCrop 0.9.9

Steps - Summary

  1. The user selects a file via the traditional <input type="file" /> dialog.

  2. An onchange handler executes for the input and checks if a file was selected, if so, then verifies that the MIME type is image/jpeg or image/png and that the selected file size is smaller than 250KB. If this validation fails it resets the selection.

  3. At this point the file (image) to be uploaded is valid. Next it checks if the browser supports the File API CreateObjectURL via if (typeof window.URL == 'undefined') return; (if it doesn't, skips the next steps)

  4. It loads the image blob into the current picture preview (one used to display the current image) and also into a dynamically generated image element which is added into a jquery tools' overlay with jcrop cropping functionality.

  5. The user then crops the image via jcrop and closes the overlay, seeing the cropped preview of the image to be uploaded (only if the browser supports CreateObjectURL and the user cropped the image)

The Code

The HTML

<div style="height: 100px; overflow: hidden; width: 100px; margin-bottom: 5px;">
    <img id="PhotoPreview" alt="Photo" src="/Content/no-photo.png" />
</div>
<input type="file" id="Photo" name="Photo" onchange="photoChanged(this.files)" />
<input type="hidden" id="PhotoCrop" name="PhotoCrop" value="" />

The JavaScript window.URL = window.URL || window.webkitURL;

function photoChanged(files) {
    if (!files.length) return;
    var file = files[0];
    if (file.type != 'image/jpeg' && file.type != 'image/png') {
        alert('The photo must be in PNG or JPEG format');
        $("#Photo").val('');
        return;
    }
    var fileSizeLimit = 250;
    var fileSize = file.size / 1024;
    if (fileSize > fileSizeLimit) {
        var fileSizeString = fileSize > 1024 ? (fileSize / 1024).toFixed(2) + "MB" : (fileSize).toFixed(2) + "KB";
        alert("The photo file size is too large, maximum size is " + fileSizeLimit
                + "KB. This photos' file size is: " + fileSizeString);
        $("#Photo").val('');
        return;
    }

    if (typeof window.URL == 'undefined') return;

    var preview = document.getElementById('PhotoPreview');
    $(preview).removeClass('profile-photo');
    preview.src = window.URL.createObjectURL(file);
    preview.onload = function () {
        var img = new Image();
        $("#PhotoOverlay div").empty().append(img);
        window.URL.revokeObjectURL(this.src);
        img.src = window.URL.createObjectURL(file);
        img.onload = function () {
            window.URL.revokeObjectURL(this.src);
            $(this).Jcrop({
                onSelect: onImageCropSelect,
                aspectRatio:
                310 / 240,
                minSize: [100, 100],
                setSelect: [0, 0, 100, 100]
        });

        $("#PhotoOverlay")
            .css({ width: this.width + 'px', : 'auto'})
            .overlay()
            .load();
        };
    };
}

function onImageCropSelect(e) {
    $("#PhotoCrop").val(e.x + ',' + e.y + ',' + .x2 + ',' + e.y2);

    var rx = 100 / e.w;
    var ry = 100 / e.h;

    var height = $("#PhotoOverlay div img").height();
    var width = $("#PhotoOverlay div img").width();

    jQuery('#PhotoPreview').css({
        width: Math.round(rx * width) + 'px',
        height: Math.round(ry * height) + 'px',
        marginLeft: '-' + Math.round(rx * e.x) + 'px',
        marginTop: '-' + Math.round(ry * e.y) + 'px'
    });
}

$(function () {
    $("#PhotoOverlay").overlay({
        mask: {
            color: '#ebecff',
            loadSpeed: 200,
            opacity: 0.9
        }
    });
});

Feel free to use the code (my contribution for any help received here).

Update

I have come across another question on stackoverflow regarding a similar issue (image loading then disappearing) in regards to the use of JCrop. I'm currently betting on JCrop being the culprit.

I've also read that when img.onload executes the image isn't always 'ready', hence the strange behavior and additional checks to .actualWidth/.actualHeight with a setTimeout might solve the issue. I'll investigate it.

Update

I have a working proof of concept using a FileReader and readAsDataUrl instead of using the CreateObjectURL and using a CANVAS to draw a preview instead of a margin based overflow:hidden solution. Needs some refinement then I'll post the code here.

Diplomatist answered 18/3, 2012 at 12:7 Comment(1)
I think it would be better to use the jCrop API and use a generic handler or static url to load your images. I made a cropper that can crop unlimited number of pics one after (using generic handler that passed image blobs from sql db) the other like this- without using API something odd was happeining...Arbitress
T
2

often erratic behavior occurs when you set img.src before declare img.onload

function imageLoadHandler() { ... }

var img = new Image();
img.onload = function() { imageLoadHandler() }
img.src = window.URL.createObjectURL(file);
if(img.complete) { imageLoadHandler() } //fix for browsers that don't trigger .load() event with image in cache

hope this helps

Tuberose answered 12/8, 2012 at 23:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.