HTML5 Image uploader with Jcrop

HTML5 Image uploader with Jcrop

126 8767
HTML5 Image uploader with Jcrop
HTML5 Image uploader with Jcrop

HTML5 Image uploader with Jcrop

We have received several inquiries for the last time from our readers with a question – how to upload photos to website. I think that this is an interesting question, and, I decided to lift the veil of this question. But, I think that the basic file upload is a bit boring thing, so, I decided to add an important feature – Cropping. It should be more attractive. Moreover, we are going to use HTML5 FileReader in order to perform cropping with Jcrop (jquery library) at client size. That will get rid of unnecessary steps. In the result – we should get 3-step process: select file -> crop -> upload. During selecting a file, we will check for the file type and size (in order to avoid huge files). Finally, when everything is ready and we have uploaded the cropped image – we will accept (upload) this file into our website (into certain folder). Please pay attention, that GD library is required to process images. If you are ready – let’s start.

It is the very time to test our demo and download the sources:

Live Demo

Step 1. HTML

Our first step is html markup. first, we have to put styles and scripts in the HEAD section:

<!-- add styles -->
<link href="css/main.css" rel="stylesheet" type="text/css" />
<link href="css/jquery.Jcrop.min.css" rel="stylesheet" type="text/css" />

<!-- add scripts -->
<script src="js/jquery.min.js"></script>
<script src="js/jquery.Jcrop.min.js"></script>
<script src="js/script.js"></script>

And now, in the BODY section we can put our form:

<div class="bbody">

    <!-- upload form -->
    <form id="upload_form" enctype="multipart/form-data" method="post" action="upload.php" onsubmit="return checkForm()">
        <!-- hidden crop params -->
        <input type="hidden" id="x1" name="x1" />
        <input type="hidden" id="y1" name="y1" />
        <input type="hidden" id="x2" name="x2" />
        <input type="hidden" id="y2" name="y2" />

        <h2>Step1: Please select image file</h2>
        <div><input type="file" name="image_file" id="image_file" onchange="fileSelectHandler()" /></div>

        <div class="error"></div>

        <div class="step2">
            <h2>Step2: Please select a crop region</h2>
            <img id="preview" />

            <div class="info">
                <label>File size</label> <input type="text" id="filesize" name="filesize" />
                <label>Type</label> <input type="text" id="filetype" name="filetype" />
                <label>Image dimension</label> <input type="text" id="filedim" name="filedim" />
                <label>W</label> <input type="text" id="w" name="w" />
                <label>H</label> <input type="text" id="h" name="h" />
            </div>

            <input type="submit" value="Upload" />
        </div>
    </form>
</div>

I hope that all is clear at this step – this is usual upload form, with hidden and visible fields, once we have selected an image, we will see second step (crop). Once we have cropped necessary area, we can Upload our result.

Step 2. CSS

Now, I would like to give you CSS styles to stylize our form:

css/main.css

.bheader {
    background-color: #DDDDDD;
    border-radius: 10px 10px 0 0;
    padding: 10px 0;
    text-align: center;
}
.bbody {
    color: #000;
    overflow: hidden;
    padding-bottom: 20px;
    text-align: center;

    background: -moz-linear-gradient(#ffffff, #f2f2f2);
    background: -ms-linear-gradient(#ffffff, #f2f2f2);
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #f2f2f2));
    background: -webkit-linear-gradient(#ffffff, #f2f2f2);
    background: -o-linear-gradient(#ffffff, #f2f2f2);
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f2f2f2');
    -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f2f2f2')";
    background: linear-gradient(#ffffff, #f2f2f2);
}
.bbody h2, .info, .error {
    margin: 10px 0;
}
.step2, .error {
    display: none;
}
.error {
    font-size: 18px;
    font-weight: bold;
    color: red;
}
.info {
    font-size: 14px;
}
label {
    margin: 0 5px;
}
input {
    border: 1px solid #CCCCCC;
    border-radius: 10px;
    padding: 4px 8px;
    text-align: center;
    width: 70px;
}
.jcrop-holder {
    display: inline-block;
}
input[type=submit] {
    background: #e3e3e3;
    border: 1px solid #bbb;
    border-radius: 3px;
    -webkit-box-shadow: inset 0 0 1px 1px #f6f6f6;
    box-shadow: inset 0 0 1px 1px #f6f6f6;
    color: #333;
    font: bold 12px/1 "helvetica neue", helvetica, arial, sans-serif;
    padding: 8px 0 9px;
    text-align: center;
    text-shadow: 0 1px 0 #fff;
    width: 150px;
}
input[type=submit]:hover {
    background: #d9d9d9;
    -webkit-box-shadow: inset 0 0 1px 1px #eaeaea;
    box-shadow: inset 0 0 1px 1px #eaeaea;
    color: #222;
    cursor: pointer;
}
input[type=submit]:active {
    background: #d0d0d0;
    -webkit-box-shadow: inset 0 0 1px 1px #e3e3e3;
    box-shadow: inset 0 0 1px 1px #e3e3e3;
    color: #000;
}

Step 3. JS

Our next step – is javascript. Please review the result code (my comments are below the code):

js/script.js

// convert bytes into friendly format
function bytesToSize(bytes) {
    var sizes = ['Bytes', 'KB', 'MB'];
    if (bytes == 0) return 'n/a';
    var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
    return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i];
};

// check for selected crop region
function checkForm() {
    if (parseInt($('#w').val())) return true;
    $('.error').html('Please select a crop region and then press Upload').show();
    return false;
};

// update info by cropping (onChange and onSelect events handler)
function updateInfo(e) {
    $('#x1').val(e.x);
    $('#y1').val(e.y);
    $('#x2').val(e.x2);
    $('#y2').val(e.y2);
    $('#w').val(e.w);
    $('#h').val(e.h);
};

// clear info by cropping (onRelease event handler)
function clearInfo() {
    $('.info #w').val('');
    $('.info #h').val('');
};

// Create variables (in this scope) to hold the Jcrop API and image size
var jcrop_api, boundx, boundy;

function fileSelectHandler() {

    // get selected file
    var oFile = $('#image_file')[0].files[0];

    // hide all errors
    $('.error').hide();

    // check for image type (jpg and png are allowed)
    var rFilter = /^(image\/jpeg|image\/png)$/i;
    if (! rFilter.test(oFile.type)) {
        $('.error').html('Please select a valid image file (jpg and png are allowed)').show();
        return;
    }

    // check for file size
    if (oFile.size > 250 * 1024) {
        $('.error').html('You have selected too big file, please select a one smaller image file').show();
        return;
    }

    // preview element
    var oImage = document.getElementById('preview');

    // prepare HTML5 FileReader
    var oReader = new FileReader();
        oReader.onload = function(e) {

        // e.target.result contains the DataURL which we can use as a source of the image
        oImage.src = e.target.result;
        oImage.onload = function () { // onload event handler

            // display step 2
            $('.step2').fadeIn(500);

            // display some basic image info
            var sResultFileSize = bytesToSize(oFile.size);
            $('#filesize').val(sResultFileSize);
            $('#filetype').val(oFile.type);
            $('#filedim').val(oImage.naturalWidth + ' x ' + oImage.naturalHeight);

            // destroy Jcrop if it is existed
            if (typeof jcrop_api != 'undefined') {
                jcrop_api.destroy();
                jcrop_api = null;
                $('#preview').width(oImage.naturalWidth);
                $('#preview').height(oImage.naturalHeight);
            }

            setTimeout(function(){
                // initialize Jcrop
                $('#preview').Jcrop({
                    minSize: [32, 32], // min crop size
                    aspectRatio : 1, // keep aspect ratio 1:1
                    bgFade: true, // use fade effect
                    bgOpacity: .3, // fade opacity
                    onChange: updateInfo,
                    onSelect: updateInfo,
                    onRelease: clearInfo
                }, function(){

                    // use the Jcrop API to get the real image size
                    var bounds = this.getBounds();
                    boundx = bounds[0];
                    boundy = bounds[1];

                    // Store the Jcrop API in the jcrop_api variable
                    jcrop_api = this;
                });
            },3000);

        };
    };

    // read selected file as DataURL
    oReader.readAsDataURL(oFile);
}

There are several common functions in the beginning: bytesToSize, checkForm, updateInfo and clearInfo. They are pretty easy. The next function (fileSelectHandler) is more complex, basically, this is the main function. When we have selected a file (I suppose – image file), we will check this file for Type and Size. You can see here a filter for image formats: png and jpg. Plus, we don’t need very large images, I think that 250kb is more than enough. Then, if everything is ok, we can read our selected file using FileReader::readAsDataURL (html5 function). And, once it has loaded, we can continue: we should display step2 with Preview and info section, and then – we have to initialize (or – reinitialize) Jcrop for our Preview image. This is how it works. Once we have cropped the image, we can click ‘Upload’ button in order to send result to the server.

Step 4. PHP

In this step – we have to accept (and upload) our result photo. I prepared next useful PHP function for you:

upload.php

function uploadImageFile() { // Note: GD library is required for this function

    if ($_SERVER['REQUEST_METHOD'] == 'POST') {
        $iWidth = $iHeight = 200; // desired image result dimensions
        $iJpgQuality = 90;

        if ($_FILES) {

            // if no errors and size less than 250kb
            if (! $_FILES['image_file']['error'] && $_FILES['image_file']['size'] < 250 * 1024) {
                if (is_uploaded_file($_FILES['image_file']['tmp_name'])) {

                    // new unique filename
                    $sTempFileName = 'cache/' . md5(time().rand());

                    // move uploaded file into cache folder
                    move_uploaded_file($_FILES['image_file']['tmp_name'], $sTempFileName);

                    // change file permission to 644
                    @chmod($sTempFileName, 0644);

                    if (file_exists($sTempFileName) && filesize($sTempFileName) > 0) {
                        $aSize = getimagesize($sTempFileName); // try to obtain image info
                        if (!$aSize) {
                            @unlink($sTempFileName);
                            return;
                        }

                        // check for image type
                        switch($aSize[2]) {
                            case IMAGETYPE_JPEG:
                                $sExt = '.jpg';

                                // create a new image from file 
                                $vImg = @imagecreatefromjpeg($sTempFileName);
                                break;
                            case IMAGETYPE_PNG:
                                $sExt = '.png';

                                // create a new image from file 
                                $vImg = @imagecreatefrompng($sTempFileName);
                                break;
                            default:
                                @unlink($sTempFileName);
                                return;
                        }

                        // create a new true color image
                        $vDstImg = @imagecreatetruecolor( $iWidth, $iHeight );

                        // copy and resize part of an image with resampling
                        imagecopyresampled($vDstImg, $vImg, 0, 0, (int)$_POST['x1'], (int)$_POST['y1'], $iWidth, $iHeight, (int)$_POST['w'], (int)$_POST['h']);

                        // define a result image filename
                        $sResultFileName = $sTempFileName . $sExt;

                        // output image to file
                        imagejpeg($vDstImg, $sResultFileName, $iJpgQuality);
                        @unlink($sTempFileName);

                        return $sResultFileName;
                    }
                }
            }
        }
    }
}

$sImage = uploadImageFile();
echo '<img src="'.$sImage.'" />';

As you see – we have to check for image size and format at the server’s side too. In the result – we will get double protection (at user side and server side) from unwanted files. Once we have uploaded the image (using move_uploaded_file) – we can crop it (using GD’s functions: imagecreatefromjpeg, imagecreatetruecolor and imagecopyresampled), and – turn result into image file using ‘imagejpeg’ function. Please pay attention – that in the result we will get a small image (which is onle 200×200), so, beside cropping, we also resize the image. I selected next desired size for all incoming photos: 200×200 (this is a good format for .. profile’s avatars as example). Finally – we can display this image on the screen. That’s all.


Live Demo

Conclusion

We have just created own HTML5 Image uploader with Jcrop. I hope that you like it. It would be nice of you to share our materials with your friends. Good luck and welcome back!


126 COMMENTS

  1. The PHP confuses me. Does this still upload the original image, or does it upload only the final cropped image? Is there any way to upload only the cropped image?

    • Hi Clinton,
      In the current realization, it uploads the selected file completely. If you need to upload it only partially, you will need to store this cropped partial result somewhere (for example – on Canvas object), and after, you will need to create a custom upload function.

      • Hello Admin,
        How can i upload original image , I don’t know how to create Canvas object ? Please tell me how to do it. ? Crop image is storing correctly.

  2. Hello!! Great Tutorial!! It works perfectly but only with JQUERY 1.8.3 or lower. It doesn’t work with Jquery 1.9.1 or 1.10.1 and I have a date picker too which need to work with a higher version of jquery. Any ideas?

    • Hi Anathema,
      It could be tedious to resize it, because in that case you need to re-calculate all params for cropping (like x1, x2, y1, y2). Try to think about what would be easier: to re-calculate it all, or to avoid using huge images.

    • Hi Vestrum,
      I suppose that the easiest way is to get the remote image content, and save it on your host (in case if everything is ok, for example, an extension is allowed). You may use the following code to do it:
      file_put_contents($sTempFileName, file_get_contents($sUrl));

  3. problem for transparant file?…
    i upload png transparant file, and the preview the background appears was black.
    plz the solution…

    • Hi Kevin,
      Firstly, it is written (in JCrop manual) that the default background is black, refer to this param:
      bgColor (color value) – Set color of background container; default: ‘black’
      Then, in order to allow saving the transparency, you need to add the following code right after the imagecreatetruecolor:

      // integer representation of the color black (rgb: 0,0,0)
      $background = imagecolorallocate($vImg, 0, 0, 0);
      // removing the black from the placeholder
      imagecolortransparent($vImg, $background);

      // turning off alpha blending (to ensure alpha channel information
      // is preserved, rather than removed (blending with the rest of the
      // image in the form of black))
      imagealphablending($vImg, false);

      // turning on alpha channel information saving (to ensure the full range
      // of transparency is preserved)
      imagesavealpha($vImg, true);

      • Hi,

        I just used the code for background transparency but still the uploaded images has black background on it.

  4. to “resize” the preview box use boxWidth: property
    eg:
    $(‘#preview’).Jcrop({
    minSize: [100, 100], // min crop size
    boxWidth: 600,
    aspectRatio : 9/6, // keep aspect ratio 1:1
    bgFade: true, // use fade effect
    bgOpacity: .4, // fade opacity
    onChange: updateInfo,
    onSelect: updateInfo,
    onRelease: clearInfo
    }, function(){

    u don’t have to change anything else.

    Thank u very much for this post.It is the best

  5. I like your Scripts :)

    It it possible that i can define the crop, width and height? maybe only 100px x 100px??

    Thank you for your good work :)

  6. Hello. I have problems. When I try to resize the “preview” img tag with “width:50%;height:50%;” the cropping function is not working welll. Can you help me?

  7. Hi I like your script but am just wondering a few things:
    1) when I click the upload button it for some reason keeps taking me to the upload.php code and I have no idea why. since I created a test page with your exact code just to try it out.
    2) Is there something else that I can use to be able to allow people who use IE8 and 9 to use it too?

    • Hi Siobhan,
      1) Yes, it is supposed to be so, this page accepts the image and crops it (in PHP) and generates the final result (which could be saved onto your host)
      2) I am not sure that this is important now, because IE8 is outdated browser. They recently published InternetExplorer version 11, I believe that most people were updated automatically.

  8. Hi,

    First of all thanks for this superve plugin.Now I am working with this plugin.I have modify the scripts for upload huge image file (suppose 5mb). Evrything is working,but only problem for to scaling the image to fit in the browser.

    So, if anyone have any idea about please advice.

    Thanks.

    • Hi Biplab,
      Can you clarify your question? You may try to apply following styles for the preview image: width=100%, height=100%

    • Hello Olanchuy,
      Please use :
      if (typeof jcrop_api != ‘undefined’) {
         jcrop_api.destroy();
         jcrop_api = null;
         $(‘#preview’).width(oImage.naturalWidth);
         $(‘#preview’).height(oImage.naturalHeight);
      }
      instead of:
      if (typeof jcrop_api != ‘undefined’) {
         jcrop_api.destroy();
      }

  9. Hello, I’m from Brazil.

    I have a problem. When I send a picture by mistake and I will select another, the preview does not change. How can I adjust this error?

    Sorry for the mistakes of English,

    Thank you.

  10. Hey Andrey, first thing, thanks foy your code, it really works!
    But i need somethin, if i don’t want crop the image, i just upload her withou crop…

      • Hey, it’s me again, i get what u talk, but i mean, i want used the crop also, because, if the user don’t want use the crop, just send normally image, which line that i can used for get the default img?

      • Hi Felipe, I see, so, I think, that in this case, we need to make two things: a) deactivate ‘checkForm’ (JS) or to modify it to display actual image sizes for the W and H b) we need to modify our upload.php in order to accept something non-ordinary (by size)

  11. Sorry, but I don’t get it I have exactly the same question as siobhan .
    you said that it suppose to be …. ok it redirects me there but image dosen’t load it looks like he would like to load but he dosen’t have te path.

    Thanks for your help

    • Hi Patryk,
      Yes, I think that you forgot about the permission for the ‘cache’ directory (that should be 777 – writable folder)

  12. After i select a file to crop. Suppose i don’t want that file anymore. When i click browse again it doesn’t replace the old image with the new one. How do i configure the code to refresh the image with the new image i select?

  13. HI Andrey,

    While using this plugin in IE 8.0 afn IE 9.0 I am getting error on console “0.files.0′ is null or not an object”. Can you pleas guide me on this.

  14. Hi Andrey,
    First at all, thanks for the post. But i am having trouble to run this example (your demo) in Firefox 29 on Linux. Consider next event flow:
    1) I upload one image, cropper works,
    2) I change my mind and re-upload another one – cropper still works, but now my browse button is broken – it just won’t open the dialog any more

    Thanks,
    Dejan

  15. Hi

    This is a the best and wonderful tutor I found. I want to use it in my website. But the only additional I need that you may share are :

    1. How to change the crop ratio to example 3:2 rather than 1: 1

    2. When loading preview ( specially to browse the 2nd image and more), there is some delay time, and should be good if a loading animation image is attached, caused its seem nothing happen until the image previewed. Bigger image will need more delay time, sure.

    3. Thanks, and my appreciation is really high for you.

    • Hi Eko, for the first – it was already explained earlier – see aspectRatio param. 2nd: yes, there is the delay. If you want to add this animation – you may add it right after the setTimeout.

  16. While my command is awaiting moderation. I would like to say thank to andrey cause he has give me the idea for using this tutor. Its work perfect with little modification. The last problem was to resize the preview before cropped, when I set the css for preview to specified w and h, the crop was missing the focus range.

    I have combined with php PDO to save it to database and work nice. To set the preview, I let people upload any size of image, but I put it in a box with css overflow: scroll, so any image size can be cropped by scroll it. I love this way.

    I think this is simple and seem good :

    I include the upload.php in index.php, I need this because I want the page just refresh to index.php rather than stop in the upload.php when done.

    I would like to attach the script here but the spam protection seem works and prevent me to write. But I have sent an email containing the script to “contact us”. I hope this helpful for every one. Thanks again.

    • You always can share your result with me using the contact us form below the page, I always reply our readers (by email)

  17. Hey,

    Thanks for this but when I remove the code section of image info where it shows the image type, and other information, it gives me black image, do you know why it is hapenining?

  18. Hey Andrew! Thanks for this plug-in… I was wondering though, is there a way to change the aspect ratio?

    When I try to do this inside of :
    setTimeout(function(){
    // initialize Jcrop
    $(‘#preview’).Jcrop({
    minSize: [32, 32], // min crop size
    aspectRatio : 7:5, // keep aspect ratio 1:1 *********
    bgFade: true, // use fade effect
    bgOpacity: .3, // fade opacity
    onChange: updateInfo,
    onSelect: updateInfo,
    onRelease: clearInfo
    }

    Nothing changes… I need my aspect ratio to be 7 x 5. Any suggestions would be very helpful, thanks!!

    • Hi Reese, aspectRatio is decimal, w/h (e.g. 1 for square), if you want 7:5, you can not put ’7:5′ of course, but you need to use 7/5 (to be decimal)

  19. Hello Andrew and thanks for the code.
    I have to crop sevral pictures on the same page (3 pictures at least) and I have to crop each one. Have you a solution or tip or something wich could help me to do this. I found the Jcrop.multiple.js but there isn’t the part when you select and preview and it’s the part which confuse me the most.
    Tanks for your time and help

    • Hi Florence, is it a modification of the same library or what? In case of multiple images in the same time, I suppose that you should find an appropriate solution. For example, you can display all images with crop function in the same time, or, you can show them one by one, and initialize the cropper for each image separately.

  20. i had already applied the required change :

    if (typeof jcrop_api != ‘undefined’) {
    jcrop_api.destroy();
    jcrop_api = null;
    $(‘#preview’).width(oImage.naturalWidth);
    $(‘#preview’).height(oImage.naturalHeight);
    }
    instead of:
    if (typeof jcrop_api != ‘undefined’) {
    jcrop_api.destroy();
    }

    still it is showing the previous image

  21. Hi! I’m using your api, it’s a great job, but i have a problem, i’m trying tu use it in a modal bootstrap and jcrop get broke saying “too much recursion”. I can’t know which function get into a bucle. Can you give me any advice? Thank you so much again for your job!

    • Hi Lucas, it looks strange, I can have a look to your result if you send me address of your project (it can be by PM)

  22. Hi Andrey,

    thanks for the great guide! this does not seem to work with IE 11, after selecting the image, it will only display the image, no cropper is displayed (even when using your demo)

  23. Hi Andrey,

    Just an update on my previous post, it appears to work on my desktop computer on ie11, however it does take quite a bit of time for jcrop to initialize, however on my laptop jcrop does not initialise at all, if I try to crop the image, it drags the image across the screen.

Leave a Reply