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
download in package

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();
            }

            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
download in package

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!

Enjoyed this Post?

If you enjoy our articles, feel free to share our tutorials with your friends.

81 thoughts on “HTML5 Image uploader with Jcrop

  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.

  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);

  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.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

     

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>