Interactive Image Vamp up with jQuery, CSS3 and PHP

Today we will show you how to create an online application for giving some funny touches to an image. We will be using jQuery and jQuery UI for dragging and […]

Today we will show you how to create an online application for giving some funny touches to an image. We will be using jQuery and jQuery UI for dragging and resizing little bling elements like mustaches and glasses. With PHP the image and the bling elements will get merged and the end result can be viewed.

Ok, let’s get started!

Tiny break: 📬 Want to stay up to date with frontend and trends in web design? Subscribe and get our Collective newsletter twice a tweek.

The Markup

The markup is pretty simple: we have a main container with the objects sidebar, the main image and the tools area:

<div id="content">
	<div id="background" class="background">
		<img id="obj_0" width="640" height="480" src="background.jpg"/>
	</div>

	<div id="tools">
	</div>

	<div id="objects">
		<div class="obj_item">
			<img id="obj_1" width="50" class="ui-widget-content" src="elements/bowtie.png" alt="el"/>
		</div>
		<div class="obj_item">
			<img id="obj_2" width="50" class="ui-widget-content" src="elements/mus1.png" alt="el"/>
		</div>
		<div class="obj_item">
			<img id="obj_3" width="50" class="ui-widget-content" src="elements/beard.png" alt="el"/>
		</div>
	</div>

	<a id="submit"><span></span></a>

	<form id="jsonform" action="merge.php" method="POST">
		<input id="jsondata" name="jsondata" type="hidden" value="" autocomplete="off"></input>
	</form>

</div>

The objects sidebar contains all the draggable elements and the tools area will show a list of all the used elements. In each item we will use a slider to allow the rotation of the respective element. We will also provide a delete icon to remove the element.

The CSS

Let’s start with the styling of the content and the image container:

#content{
    position:relative;
    width:1105px;
    height:500px;
    margin:40px auto 0px auto;
    background-color:#F9F9F9;
    -moz-border-radius:6px;
    -webkit-border-radius:6px;
    border-radius:6px;
    -moz-box-shadow:0px 0px 8px #ccc;
    -webkit-box-shadow:0px 0px 8px #ccc;
    box-shadow:0px 0px 8px #ccc;
}
.background{
    position:absolute;
    width:640px;
    height:480px;
    top:10px;
    left:215px;
    -moz-box-shadow:0px 0px 3px #bbb;
    -webkit-box-shadow:0px 0px 3px #bbb;
    box-shadow:0px 0px 3px #bbb;
}

When using CSS3 properties, make sure that you address them in the browser specific way, since not all browsers implement fully CSS3. Once CSS3 becomes a standard, you will also want to make sure to add the “real” CSS3 term.

Let’s take a look at the style for the left sidebar with the draggable bling images:

#objects{
    width:210px;
    height:486px;
    top:10px;
    left:10px;
    position:absolute;
}
.obj_item{
    width:70px;
    height:70px;
    float:left;
}

The obj_item class will wrap around the draggable and resizable image. The next styles are for the right sidebar where we will have all the used bling images listed:

#tools{
    width:230px;
    top:8px;
    right:10px;
    position:absolute;
    height:420px;
    overflow-y:scroll;
    overflow-x:hidden;
}
.item{
    border:3px solid #fff;
    background-color:#ddd;
    height:60px;
    position:relative;
    margin:2px 5px 2px 2px;
    -moz-border-radius:3px;
    -webkit-border-radius:3px;
    border-radius:3px;
    -moz-box-shadow:0px 0px 2px #999;
    -webkit-box-shadow:0px 0px 2px #999;
    box-shadow:0px 0px 2px #999;
}

The item class defines the style for each element that is currently being used in the main image. Inside of that item there will be a thumbnail, a slider for the rotation, and a little button to remove the element:

.thumb{
    width:50px;
    height:50px;
    margin:5px;
    float:left;
}
.slider{
    float: left;
    width: 115px;
    margin: 30px 0px 0px 5px;
    background-color:#fff;
    height:10px;
    position:relative;
}
.slider span{
    font-size:10px;
    font-weight:normal;
    margin-top:-25px;
    float:left;
}
.slider span.degrees{
    position:absolute;
    right:-22px;
    top:20px;
    width:20px;
    height:20px;
}
.slider .ui-slider-handle {
    width:10px;
    height:20px;
    outline:none;
}
a.remove{
    width:16px;
    height:16px;
    position:absolute;
    top:0px;
    right:0px;
    background:transparent url(../images/cancel.png) no-repeat top left;
    opacity:0.5;
    cursor:pointer;
}
a.remove:hover{
    opacity:1.0;
}

The class .ui-slider-handle comes from the jQuery slider and we can adapt it by defining a style in our stylesheet.

Ok, let’s get to the serious part: the JavaScript!

The JavaScript

The functionality of this app is made up of many elements, so will go step by step through the most important parts. The JavaScript code in the index is commented, so that you can as well understand the steps that are not mentioned here.

The element images that are dropped onto the main image will be stored in the following JSON object:

var data = {
	"images": [
		{"id" : "obj_0" ,"src" : "background.jpg", "width" : "640", "height" : "480"}
	]
};

Every time we drop a new image we insert it into the “images” array.

The images are resizable and draggable:

$('#objects img').resizable({
	handles	: 'se',
	stop	: resizestop
}).parent('.ui-wrapper').draggable({
    revert	: 'invalid'
});

The background div, where the main image is inserted is droppable. Each time we drop an element into this container we add it to the JSON data object if it is not there already. The most important parameters added are the width, height, top and left. The last two are calculated based on the difference between the absolute top or left of the container and the draggable object.
If the element was already in the JSON data object, meaning that the user keeps dragging it around, we just update the new top and left in the JSON data object. We also set a new z-index to the dropped element, so that the last one dropped always stays on top. Besides adding to the JSON object we also add a new element to the tools sidebar, where we will be able to rotate the corresponding image and also delete it from the container:

$('#background').droppable({
	accept	: '#objects div', /* accept only draggables from #objects */
	drop	: function(event, ui) {
		var $this 		= $(this);
		++count_dropped_hits;
		var draggable_elem = ui.draggable;
		draggable_elem.css('z-index',count_dropped_hits);
		/* object was dropped : register it */
		var objsrc 		= draggable_elem.find('.ui-widget-content').attr('src');
		var objwidth 	= parseFloat(draggable_elem.css('width'),10);
		var objheight 	= parseFloat(draggable_elem.css('height'),10);

		/* for top and left we decrease the top and left of the droppable element */
		var objtop		= ui.offset.top - $this.offset().top;
		var objleft		= ui.offset.left - $this.offset().left;

		var objid		= draggable_elem.find('.ui-widget-content').attr('id');
		var index 		= exist_object(objid);
		if(index!=-1) { //if exists update top and left
			data.images[index].top 	= objtop;
			data.images[index].left = objleft;
		}
		else{
			/* register new one */
			var newObject = {
				'id' 		: objid,
				'src' 		: objsrc,
				'width' 	: objwidth,
				'height' 	: objheight,
				'top' 		: objtop,
				'left' 		: objleft,
				'rotation'  : '0'
			};
			data.images.push(newObject);
			/* add object to sidebar*/

			$('<div/>',{
				className	:	'item'
			}).append(
				$('<div/>',{
					className	:	'thumb',
					html		:	'<img width="50" class="ui-widget-content" src="'+objsrc+'"></img>'
				})
			).append(
				$('<div/>',{
					className	:	'slider',
					html		:	'<span>Rotate</span><span class="degrees">0</span>'
				})
			).append(
				$('<a/>',{
					className	:	'remove'
				})
			).append(
				$('<input/>',{
					type		:	'hidden',
					value		:	objid		// keeps track of which object is associated
				})
			).appendTo($('#tools'));
			$('.slider').slider({
				orientation	: 'horizontal',
				max			: 180,
				min			: -180,
				value		: 0,
				slide		: function(event, ui) {
					var $this = $(this);
					/* Change the rotation and register that value in data object when it stops */
					draggable_elem.css({
						'-moz-transform':'rotate('+ui.value+'deg)',
						'-webkit-transform':'rotate('+ui.value+'deg)'
					});
					$('.degrees',$this).html(ui.value);
				},
				stop		: function(event, ui) {
					newObject.rotation = ui.value;
				}
			});
		}
	}
});

When removing an element from the container, we want to remove it from the sidebar, from the JSON data object and also add it again to the elements list:

$('.remove',$('#tools')).live('click',function(){
	var $this = $(this);

	/* the element next to this is the input that stores the obj id */
	var objid = $this.next().val();
	/* remove the object from the sidebar */
	$this.parent().remove();
	/* ,from the picture */
	var divwrapper = $('#'+objid).parent().parent();
	$('#'+objid).remove();
	/* add again to the objects list */
	var image_elem 		= $this.parent().find('img');
	var thumb_width 	= image_elem.attr('width');
	var thumb_height 	= image_elem.attr('height');
	var thumb_src 		= image_elem.attr('src');
	$('',{
		id 			: 	objid,
		src			: 	thumb_src,
		width		:	thumb_width,
		//height		:	thumb_height,
		className	:	'ui-widget-content'
	}).appendTo(divwrapper).resizable({
		handles	: 'se',
		stop	: resizestop
	}).parent('.ui-wrapper').draggable({
		revert: 'invalid'
	});
	/* and unregister it - delete from object data */
	var index = exist_object(objid);
	data.images.remove(index);
});

The PHP

What we are doing here is getting all the info for each image dropped into the main container, and merging it with the background image. If there was a rotation then we need to make sure we recalculate the top and left, since the PHP imagerotate function is not that friendly and it scales down the image after rotation:

$res = JSON_decode(stripslashes($_POST['JSONdata']), true);
/* get data */
$count_images 	= count($res['images']);
/* the background image is the first one */
$background 	= $res['images'][0]['src'];
$photo1 		= imagecreatefromjpeg($background);
$foto1W 		= imagesx($photo1);
$foto1H 		= imagesy($photo1);
$photoFrameW 	= $res['images'][0]['width'];
$photoFrameH 	= $res['images'][0]['height'];
$photoFrame 	= imagecreatetruecolor($photoFrameW,$photoFrameH);
imagecopyresampled($photoFrame, $photo1, 0, 0, 0, 0, $photoFrameW, $photoFrameH, $foto1W, $foto1H);

/* the other images */
for($i = 1; $i < $count_images; ++$i){
	$insert 		= $res['images'][$i]['src'];
	$photoFrame2Rotation = (180-$res['images'][$i]['rotation']) + 180;

	$photo2 		= imagecreatefrompng($insert);

	$foto2W 		= imagesx($photo2);
	$foto2H 		= imagesy($photo2);
	$photoFrame2W	= $res['images'][$i]['width'];
	$photoFrame2H 	= $res['images'][$i]['height'];

	$photoFrame2TOP = $res['images'][$i]['top'];
	$photoFrame2LEFT= $res['images'][$i]['left'];

	$photoFrame2 	= imagecreatetruecolor($photoFrame2W,$photoFrame2H);
	$trans_colour 	= imagecolorallocatealpha($photoFrame2, 0, 0, 0, 127);
	imagefill($photoFrame2, 0, 0, $trans_colour);

	imagecopyresampled($photoFrame2, $photo2, 0, 0, 0, 0, $photoFrame2W, $photoFrame2H, $foto2W, $foto2H);

	$photoFrame2 	= imagerotate($photoFrame2,$photoFrame2Rotation, -1,0);
	/*after rotating calculate the difference of new height/width with the one before*/
	$extraTop		=(imagesy($photoFrame2)-$photoFrame2H)/2;
	$extraLeft		=(imagesx($photoFrame2)-$photoFrame2W)/2;

	imagecopy($photoFrame, $photoFrame2,$photoFrame2LEFT-$extraLeft, $photoFrame2TOP-$extraTop, 0, 0, imagesx($photoFrame2), imagesy($photoFrame2));
}
// Set the content type header - in this case image/jpeg
header('Content-type: image/jpeg');
imagejpeg($photoFrame, $targetfile);
imagedestroy($photoFrame);

And that’s it!
Enjoy!

Message from TestkingBecome expert designer/developer with our testking 312-50 course. Download the testking 1Y0-A08 tutorials and testking 642-415 video to learn about jquery css3 and php.

Manoela Ilic

Manoela is the main tinkerer at Codrops. With a background in coding and passion for all things design, she creates web experiments and keeps frontend professionals informed about the latest trends.

Stay in the loop: Get your dose of frontend twice a week

Fresh news, inspo, code demos, and UI animations—zero fluff, all quality. Make your Mondays and Thursdays creative!

Feedback 44

Comments are closed.
  1. lol, nice. I’m the original photographer for the base image, and was wondering about all the traffic to it from this site! Very funny, and nice tutorial! 🙂

    • Thank you all for your great feedback, we are really glad you like it! Cheers, ML

  2. Wonderfull post!

    ( have an idea for your next post –> a simple web based powerpoint/keynote/… )

  3. hello,

    this is a great script but… on my website, merge.php doesn’t display the picture ? somebody knows why ? 🙁

  4. Thanks for this amazing tutorial. But I have a question, if I wanted the users to change the background image how should I do? what is the best technique ? Please feel free to send me a email with some light about the problem I have…Thanks again for this tutorial, you rock 🙂

  5. Two questions, one the resulting image [merge.php] when I download it and attempted to open it, it says its corrupted – it wont open. How can that be fixed? Also any idea how I can make it so the resulting file is emailed.

  6. Hey I thought this was a very interesting post thanks for thinking of it. You seem to be a very experienced blogger. I recently created a page on different ways to treat gynecomastia. Don’t be a stranger check out my blog tell me what you think? Thanks

  7. Love it. Any chance the user could select from 3 or 4 images? Perhaps a way to slide or select the image from a list of thumbnails?

  8. Incredible component!!! But i have a little problem in IE 7/IE8 and Opera 10.60. The image rotation doesnt work properly in these browsers. Is there any way to solve this problem?
    Thanks for your time

  9. thats a cool work. Can we do those draggable items editable too? i mean, is it possible to write on those dragged items? please do help in this case.. 🙂

  10. Excellent tutorial, thanks a lot!

    But can you advice me how can I save the image in a specific folder

  11. Fantastic work Mary. I have tried to get it to work but it’s falling over on the output on merge.php, would you be able to look into this for me? From what I can gather it’s the header type at the bottom of merge.php… Please help! 🙂

  12. Amazing
    but why not to add an :
    1- Email this picture???
    2- also can you add an upload picture??
    3- plus can i make a gllaery where i can choose which picture to edit?

    thanks 😛

  13. Is there any way to make the rotation work in IE, i know it uses CSS3 but maybe there is another way around this.

  14. Does anyone else have a problem in Chrome, where the transparent png’s show a blue box behind the movable images, & they only “drop” once the left mouse is clicked again?

    – O8

  15. is there a way to get this to work with tabs? Everytime I try to use jquery tabs it breaks the script. Do you have an alternative to giving a user more stuff to add to the pic?

  16. The right sidebar doesn’t work. No slider or remove button visible. Any Suggestion?

  17. It’s fantastic! But there is support for Firefox and IE 4 9? In IE the images and do not drag the FF does not appear to rotate function. =/

  18. I confirm, the right column doesn’t work ! (the slide bar exactly)
    It works 1 month ago.

    I think the googleapis jquery code source sucks, try to replace it. 😉

  19. Since one month ago, the right sidebar doesn’t work. Some solution?
    Thank u!