I can still remember the old digg.com user profiles with dynamic avatar cropping via JavaScript. Their user interface was clean, easy to use, displayed a sample before you cropped, and would auto-update as you changed the selection box. I have always admired this design and have been on the lookout for a great jQuery plugin which can offer similar features with less custom code.
After toying with Jcrop I have come to really support this plugin. The codes are very small and do not require a whole lot of setup (although there are plenty of optional settings). My demo example will include dynamic avatar updating in real-time, along with a PHP rendering script. I think the amount of new social networks and websites offering user profiles should definitely take this UX technique into consideration.
Live Demo – Download Source Code
Contents
Setup the Project
First you should download a copy of Jcrop which includes a series of files we will need. The core will require a CSS and JS file, along with a copy of jQuery. The plugin will require more JavaScript for running the plugin and I have moved these codes into a separate file named cropsetup.js.
<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-type" content="text/html;charset=utf-8"> <title>Jcrop Dynamic Avatar JS/PHP Demo</title> <link rel="shortcut icon" href="http://teamtreehouse.com/assets/favicon.ico"> <link rel="icon" href="http://teamtreehouse.com/assets/favicon.ico"> <link rel="stylesheet" type="text/css" href="css/styles.css"> <link rel="stylesheet" type="text/css" href="css/jquery.Jcrop.css"> <link rel="stylesheet" type="text/css" href="http://fonts.googleapis.com/css?family=Wellfleet"> <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script> <script type="text/javascript" src="js/jquery.Jcrop.js"></script> <script type="text/javascript" src="js/cropsetup.js"></script> </head>
This is my document header which contains a decent number of resources we need to include. Notice I have split everything into folders using js and css which typically makes organizing projects a whole lot easier. The crop.php file is only in the root for convenience of this demo.
<div id="wrapper"> <div class="jc-demo-box"> <header> <h1><span>Jcrop Live Avatar Demo</span></h1> </header> <img src="images/cropimg.jpg" id="target" alt="[Jcrop Example]" /> <div id="preview-pane"> <div class="preview-container"> <img src="images/cropimg.jpg" class="jcrop-preview" alt="Preview" /> </div> </div><!-- @end #preview-pane --> <div id="form-container"> <form id="cropimg" name="cropimg" method="post" action="crop.php" target="_blank"> <input type="hidden" id="x" name="x"> <input type="hidden" id="y" name="y"> <input type="hidden" id="w" name="w"> <input type="hidden" id="h" name="h"> <input type="submit" id="submit" value="Crop Image!"> </form> </div><!-- @end #form-container --> </div><!-- @end .jc-demo-box --> </div><!-- @end #wrapper -->
The code block above holds the entirety of my demo page body content. I have setup an outer wrapper and I’ve kept the container class .jc-demo-box as well. This could be changed to anything as long as the image and preview page will fit properly within the content area. Just reuse the same CSS properties and it will be all set. The img tag with the ID #target will display the full-sized image where a Jcrop selection can be drawn.
Underneath the main image there is a div with the ID #preview-pane and this holds another image tag. For this demo I set the aspect ratio at 1:1 so the avatar will always come out square. This aspect ratio requires an avatar container with the same square dimensions. We can see how this is accomplished in the stylesheet.
Common Page CSS
I will not go over much in the jquery.Jcrop.css file because these codes are traditional for the crop interface, so there are no customizations required, but elements within the content area need to be styled in a very specific fashion. )As a side note, the background tiling is called Mocha Grunge downloaded from Subtle Patterns.
)
/* jcrop styles */ .jc-demo-box { position: relative; text-align: left; margin: 1.5em auto; background: #fff; -webkit-box-shadow: 0 3px 9px -1px rgba(0,0,0,0.75); -moz-box-shadow: 0 3px 9px -1px rgba(0,0,0,0.75); box-shadow: 0 3px 9px -1px rgba(0,0,0,0.75); padding: 1em 2em 2em; } /* Apply these styles only when #preview-pane has been placed within the Jcrop widget */ .jcrop-holder #preview-pane { display: block; position: absolute; z-index: 2000; top: 10px; right: -210px; background-color: #fff; padding: 6px; border: 1px solid #bcbcbc; } /* The Javascript code will set the aspect ratio of the crop area based on the size of the thumbnail preview, specified here */ #preview-pane .preview-container { width: 150px; height: 150px; overflow: hidden; }
These blocks are default settings which can be found in the original Jcrop demo page. The #preview-pane div is specifically important because we are using absolute positioning on the element. The internal preview div is fixed at 150×150 pixels so this is ultimately our final avatar size. We can always go back and edit the values to fit another style, just be sure and update the .preview-container properties.
#form-container { display: block; position: absolute; top: 300px; right: 35px; } #submit { color: #fff; font-size: 1.3em; text-shadow: 1px 1px 1px #131313; border: 7px solid #b0e49f; padding: 14px 9px; cursor: pointer; background-color: #62be46; background-image: -webkit-gradient(linear, left top, left bottom, from(#62be46), to(#41a62b)); background-image: -webkit-linear-gradient(top, #62be46, #41a62b); background-image: -moz-linear-gradient(top, #62be46, #41a62b); background-image: -ms-linear-gradient(top, #62be46, #41a62b); background-image: -o-linear-gradient(top, #62be46, #41a62b); background-image: linear-gradient(top, #62be46, #41a62b); -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; } #submit:hover { background-color: #72ce54; background-image: -webkit-gradient(linear, left top, left bottom, from(#72ce54), to(#49b233)); background-image: -webkit-linear-gradient(top, #72ce54, #49b233); background-image: -moz-linear-gradient(top, #72ce54, #49b233); background-image: -ms-linear-gradient(top, #72ce54, #49b233); background-image: -o-linear-gradient(top, #72ce54, #49b233); background-image: linear-gradient(top, #72ce54, #49b233); } #submit:active { -webkit-box-shadow: inset 0 2px 13px -1px rgba(0,0,0,0.35); -moz-box-shadow: inset 0 2px 13px -1px rgba(0,0,0,0.35); box-shadow: inset 0 2px 13px -1px rgba(0,0,0,0.35); }
Since the container div is set to a relative position we are using substantial absolute positioning to squeeze the dynamic elements inside any layout. Particularly the preview image and crop button which are both located to the right-hand side. I have attached the attribute target=”_blank” so the cropped avatar PHP script will open in a new browser tab.
Configuring the jQuery Method
This final chunk of code is what will get Jcrop running and working properly in the layout. I have copied over the basic function template from the original demo, but I’ve enhanced the features so we can see a live update preview along with the final cropped image. This is my default method call but you may implement something much simpler if needed:
$('#target').Jcrop({ onChange: updatePreview, onSelect: updatePreview, bgOpacity: 0.5, aspectRatio: xsize / ysize },function(){ // Use the API to get the real image size var bounds = this.getBounds(); boundx = bounds[0]; boundy = bounds[1]; jcrop_api = this; // Store the API in the jcrop_api variable // Move the preview into the jcrop container for css positioning $preview.appendTo(jcrop_api.ui.holder); });
Inside the method I am passing a number of unique parameters. Specifically these are onChange, onSelect, bgOpacity, and aspectRatio. The first two will call the custom function updatePreview() every time the user changes or adjusts the crop selection. bgOpacity allows you to set the level of transparency for the darker background. And obviously aspectRatio will configure the selection box dimensions. We are passing in some variables for this setting which can be explained by the earlier setup variables.
// Create variables (in this scope) to hold the API and image size var jcrop_api, boundx, boundy, // Grab some information about the preview pane $preview = $('#preview-pane'), $pcnt = $('#preview-pane .preview-container'), $pimg = $('#preview-pane .preview-container img'), xsize = $pcnt.width(), ysize = $pcnt.height();
Inside the Jcrop API we can use a number of these variables for handling custom data. The final function clause from earlier will only call after the user has finished making their selection. It will update the preview image so that we can see it resized properly on the screen in real time.
function updatePreview(c) { if (parseInt(c.w) > 0) { var rx = xsize / c.w; var ry = ysize / c.h; $('#x').val(c.x); $('#y').val(c.y); $('#w').val(c.w); $('#h').val(c.h); $pimg.css({ width: Math.round(rx * boundx) + 'px', height: Math.round(ry * boundy) + 'px', marginLeft: '-' + Math.round(rx * c.x) + 'px', marginTop: '-' + Math.round(ry * c.y) + 'px' }); } }
The updatePreview() function is long-winded but necessary for getting the form inputs to work properly. I am targeting a series of hidden input fields which get passed along into PHP on submission. We can check the x/y coordinates and the width/height values to determine where PHP should crop the new avatar image.
Cropping and Saving in PHP
Although you do not need to use PHP as the backend language I chose this because of its popularity among open source web developers. Also the demo Jcrop files will include a similar crop file which is mixed inside another webpage. My edits have removed this PHP process into a new script that the user would likely never see(if using Ajax).
$targ_w = $targ_h = 150; $jpeg_quality = 90; if(!isset($_POST['x']) || !is_numeric($_POST['x'])) { die('Please select a crop area.'); } $src = 'images/cropimg.jpg'; $img_r = imagecreatefromjpeg($src); $dst_r = ImageCreateTrueColor($targ_w, $targ_h); imagecopyresampled($dst_r,$img_r,0,0,$_POST['x'],$_POST['y'], $targ_w,$targ_h,$_POST['w'],$_POST['h']); header('Content-type: image/jpeg'); imagejpeg($dst_r,null,$jpeg_quality); // NULL will output the image directly exit;
The code is a lot easier than it looks since we do not need to perform much functionality. After creating a variable to hold the image contents via imagecreatefromjpeg() then we can make a copy and further clip out exactly what we need for the avatar. I want to focus on the final two lines which send out a header of image/jpeg followed by the image itself.
The header() function is only needed because the page isn’t outputting text/HTML code, it is outputting JPEG information. Otherwise we would not need to include this at all. If we look at the imagejpeg() function there are 3 parameters being passed – the newly cropped image, the save path, and the quality. Using NULL for the path will output the image directly but you can include a local relative path like /public_html/avatars/newimg1.jpg.
The only problem is that you also need a method of distinguishing various avatars from each other. The folder will appear crowded and confusing if you just append another number to the end of the filename. Even organizing by user ID can get messy. However this is obviously a much deeper topic, so I hope this tutorial can offer a good starting point for developers interested in creating such a dynamic user interface. Be sure to check out my live demo and catch a glimpse of this effect in action.
Live Demo – Download Source Code
Closing
Be aware that this script does require hosting on some type of PHP-supported web server to handle the actual image cropping, and please note the demo PHP script uses imagejpeg() to output the codes directly to the browser, instead of saving to a new image file. This was done to spare my server from handling loads of new files and HDD space. But these minor changes would require just a few code edits and the whole script is versatile enough for porting into your own website layout with ease.