HTML5 Image uploader with Jcrop

Tutorials

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

[sociallocker]

download in package

[/sociallocker]


Step 1. HTML

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

1 <!-- add styles -->
2 <link href="css/main.css" rel="stylesheet" type="text/css" />
3 <link href="css/jquery.Jcrop.min.css" rel="stylesheet" type="text/css" />
4 <!-- add scripts -->
5 <script src="js/jquery.min.js"></script>
6 <script src="js/jquery.Jcrop.min.js"></script>
7 <script src="js/script.js"></script>

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

01 <div class="bbody">
02     <!-- upload form -->
03     <form id="upload_form" enctype="multipart/form-data" method="post" action="upload.php" onsubmit="return checkForm()">
04         <!-- hidden crop params -->
05         <input type="hidden" id="x1" name="x1" />
06         <input type="hidden" id="y1" name="y1" />
07         <input type="hidden" id="x2" name="x2" />
08         <input type="hidden" id="y2" name="y2" />
09         <h2>Step1: Please select image file</h2>
10         <div><input type="file" name="image_file" id="image_file" onchange="fileSelectHandler()" /></div>
11         <div class="error"></div>
12         <div class="step2">
13             <h2>Step2: Please select a crop region</h2>
14             <img id="preview" />
15             <div class="info">
16                 <label>File size</label> <input type="text" id="filesize" name="filesize" />
17                 <label>Type</label> <input type="text" id="filetype" name="filetype" />
18                 <label>Image dimension</label> <input type="text" id="filedim" name="filedim" />
19                 <label>W</label> <input type="text" id="w" name="w" />
20                 <label>H</label> <input type="text" id="h" name="h" />
21             </div>
22             <input type="submit" value="Upload" />
23         </div>
24     </form>
25 </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

01 .bheader {
02     background-color#DDDDDD;
03     border-radius: 10px 10px 0 0;
04     padding10px 0;
05     text-aligncenter;
06 }
07 .bbody {
08     color#000;
09     overflowhidden;
10     padding-bottom20px;
11     text-aligncenter;
12     background: -moz-linear-gradient(#ffffff#f2f2f2);
13     background: -ms-linear-gradient(#ffffff#f2f2f2);
14     background: -webkit-gradient(linear, left topleft bottom, color-stop(0%#ffffff), color-stop(100%#f2f2f2));
15     background: -webkit-linear-gradient(#ffffff#f2f2f2);
16     background: -o-linear-gradient(#ffffff#f2f2f2);
17     filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f2f2f2');
18     -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f2f2f2')";
19     background: linear-gradient(#ffffff#f2f2f2);
20 }
21 .bbody h2, .info, .error {
22     margin10px 0;
23 }
24 .step2, .error {
25     displaynone;
26 }
27 .error {
28     font-size18px;
29     font-weightbold;
30     colorred;
31 }
32 .info {
33     font-size14px;
34 }
35 label {
36     margin0 5px;
37 }
38 input {
39     border1px solid #CCCCCC;
40     border-radius: 10px;
41     padding4px 8px;
42     text-aligncenter;
43     width70px;
44 }
45 .jcrop-holder {
46     display: inline-block;
47 }
48 input[type=submit] {
49     background#e3e3e3;
50     border1px solid #bbb;
51     border-radius: 3px;
52     -webkit-box-shadow: inset 0 0 1px 1px #f6f6f6;
53     box-shadow: inset 0 0 1px 1px #f6f6f6;
54     color#333;
55     fontbold 12px/1 "helvetica neue"helveticaarialsans-serif;
56     padding8px 0 9px;
57     text-aligncenter;
58     text-shadow0 1px 0 #fff;
59     width150px;
60 }
61 input[type=submit]:hover {
62     background#d9d9d9;
63     -webkit-box-shadow: inset 0 0 1px 1px #eaeaea;
64     box-shadow: inset 0 0 1px 1px #eaeaea;
65     color#222;
66     cursorpointer;
67 }
68 input[type=submit]:active {
69     background#d0d0d0;
70     -webkit-box-shadow: inset 0 0 1px 1px #e3e3e3;
71     box-shadow: inset 0 0 1px 1px #e3e3e3;
72     color#000;
73 }

Step 3. JS

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

js/script.js

01 // convert bytes into friendly format
02 function bytesToSize(bytes) {
03     var sizes = ['Bytes''KB''MB'];
04     if (bytes == 0) return 'n/a';
05     var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
06     return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i];
07 };
08 // check for selected crop region
09 function checkForm() {
10     if (parseInt($('#w').val())) return true;
11     $('.error').html('Please select a crop region and then press Upload').show();
12     return false;
13 };
14 // update info by cropping (onChange and onSelect events handler)
15 function updateInfo(e) {
16     $('#x1').val(e.x);
17     $('#y1').val(e.y);
18     $('#x2').val(e.x2);
19     $('#y2').val(e.y2);
20     $('#w').val(e.w);
21     $('#h').val(e.h);
22 };
23 // clear info by cropping (onRelease event handler)
24 function clearInfo() {
25     $('.info #w').val('');
26     $('.info #h').val('');
27 };
28 // Create variables (in this scope) to hold the Jcrop API and image size
29 var jcrop_api, boundx, boundy;
30 function fileSelectHandler() {
31     // get selected file
32     var oFile = $('#image_file')[0].files[0];
33     // hide all errors
34     $('.error').hide();
35     // check for image type (jpg and png are allowed)
36     var rFilter = /^(image\/jpeg|image\/png)$/i;
37     if (! rFilter.test(oFile.type)) {
38         $('.error').html('Please select a valid image file (jpg and png are allowed)').show();
39         return;
40     }
41     // check for file size
42     if (oFile.size > 250 * 1024) {
43         $('.error').html('You have selected too big file, please select a one smaller image file').show();
44         return;
45     }
46     // preview element
47     var oImage = document.getElementById('preview');
48     // prepare HTML5 FileReader
49     var oReader = new FileReader();
50         oReader.onload = function(e) {
51         // e.target.result contains the DataURL which we can use as a source of the image
52         oImage.src = e.target.result;
53         oImage.onload = function () { // onload event handler
54             // display step 2
55             $('.step2').fadeIn(500);
56             // display some basic image info
57             var sResultFileSize = bytesToSize(oFile.size);
58             $('#filesize').val(sResultFileSize);
59             $('#filetype').val(oFile.type);
60             $('#filedim').val(oImage.naturalWidth + ' x ' + oImage.naturalHeight);
61             // destroy Jcrop if it is existed
62             if (typeof jcrop_api != 'undefined') {
63                 jcrop_api.destroy();
64                 jcrop_api = null;
65                 $('#preview').width(oImage.naturalWidth);
66                 $('#preview').height(oImage.naturalHeight);
67             }
68             setTimeout(function(){
69                 // initialize Jcrop
70                 $('#preview').Jcrop({
71                     minSize: [32, 32], // min crop size
72                     aspectRatio : 1, // keep aspect ratio 1:1
73                     bgFade: true// use fade effect
74                     bgOpacity: .3, // fade opacity
75                     onChange: updateInfo,
76                     onSelect: updateInfo,
77                     onRelease: clearInfo
78                 }, function(){
79                     // use the Jcrop API to get the real image size
80                     var bounds = this.getBounds();
81                     boundx = bounds[0];
82                     boundy = bounds[1];
83                     // Store the Jcrop API in the jcrop_api variable
84                     jcrop_api = this;
85                 });
86             },3000);
87         };
88     };
89     // read selected file as DataURL
90     oReader.readAsDataURL(oFile);
91 }

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

01 function uploadImageFile() { // Note: GD library is required for this function
02     if ($_SERVER['REQUEST_METHOD'] == 'POST') {
03         $iWidth $iHeight = 200; // desired image result dimensions
04         $iJpgQuality = 90;
05         if ($_FILES) {
06             // if no errors and size less than 250kb
07             if (! $_FILES['image_file']['error'] && $_FILES['image_file']['size'] < 250 * 1024) {
08                 if (is_uploaded_file($_FILES['image_file']['tmp_name'])) {
09                     // new unique filename
10                     $sTempFileName 'cache/' . md5(time().rand());
11                     // move uploaded file into cache folder
12                     move_uploaded_file($_FILES['image_file']['tmp_name'], $sTempFileName);
13                     // change file permission to 644
14                     @chmod($sTempFileName, 0644);
15                     if (file_exists($sTempFileName) && filesize($sTempFileName) > 0) {
16                         $aSize getimagesize($sTempFileName); // try to obtain image info
17                         if (!$aSize) {
18                             @unlink($sTempFileName);
19                             return;
20                         }
21                         // check for image type
22                         switch($aSize[2]) {
23                             case IMAGETYPE_JPEG:
24                                 $sExt '.jpg';
25                                 // create a new image from file
26                                 $vImg = @imagecreatefromjpeg($sTempFileName);
27                                 break;
28                             case IMAGETYPE_PNG:
29                                 $sExt '.png';
30                                 // create a new image from file
31                                 $vImg = @imagecreatefrompng($sTempFileName);
32                                 break;
33                             default:
34                                 @unlink($sTempFileName);
35                                 return;
36                         }
37                         // create a new true color image
38                         $vDstImg = @imagecreatetruecolor( $iWidth$iHeight );
39                         // copy and resize part of an image with resampling
40                         imagecopyresampled($vDstImg$vImg, 0, 0, (int)$_POST['x1'], (int)$_POST['y1'], $iWidth$iHeight, (int)$_POST['w'], (int)$_POST['h']);
41                         // define a result image filename
42                         $sResultFileName $sTempFileName $sExt;
43                         // output image to file
44                         imagejpeg($vDstImg$sResultFileName$iJpgQuality);
45                         @unlink($sTempFileName);
46                         return $sResultFileName;
47                     }
48                 }
49             }
50         }
51     }
52 }
53 $sImage = uploadImageFile();
54 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!

Rate article