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!

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.

Tagged with:

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 up to date with the latest web design and development news and relevant updates from Codrops.