How The jquery Image Crop And Edit with Canvas

In this tutorial we’re going to figure out how the jquery image crop and edit  by using the HTML 5 <canvas> element with some cool looking controls for resizing, normally found in image editing software.

In real life application a site or application may utilize a process like this to resize and outline a profile picture before uploading.Although we could do this on the server, it would require the transfer of a potentially large file, which is slow or we can resize the image on the client side before uploading it, which is quicker.

We are going to do this by making an HTML5 <canvas> element and drawing an image on the canvas at a specific size, then extracting the new image data from the canvas as an data URI.Majority of web browsers have great support for these methods , so you can likely utilize this strategy at this time.Even so just be aware of some limitations related to browser support such as quality and performance.

Editing large images can cause performance issues and could even slow down the browsers or perhaps, crash them.It would be sensible to set reasonable limits on the file size as you find in numerous websites.If you are quality conscious you may find the resized image looks unsuitable due to the browser’s resampling.These techniques would be helpful n improving the quality of images downscaled with canvas.

Here are the final results in this ZIP file.

HTML Structure.

In this demo we’re going to use an existing image:

<img class="resize-image" src="image.jpg" alt="Image" />

Now, that’s all the HTML we are gonna need in this demo.

CSS Styling.

The CSS is very minimal as well. To begin with, define the styles for the resize-container and the image.

.resize-container {
    position: relative;
    display: inline-block;
    cursor: move;
    margin: 0 auto;
}

.resize-container img {
    display: block
}

.resize-container:hover img,
.resize-container:active img {
    outline: 2px dashed rgba(222,60,80,.9);
}

Next, specify the position and style for each of the ‘resize handles’. These are the little squares at every corner of the picture that we drag to resize.

.resize-handle-ne,
.resize-handle-ne,
.resize-handle-se,
.resize-handle-nw,
.resize-handle-sw {
    position: absolute;
    display: block;
    width: 10px;
    height: 10px;
    background: rgba(222,60,80,.9);
    z-index: 999;
}

.resize-handle-nw {
    top: -5px;
    left: -5px;
    cursor: nw-resize;
}

.resize-handle-sw {
    bottom: -5px;
    left: -5px;
    cursor: sw-resize;
}

.resize-handle-ne {
    top: -5px;
    right: -5px;
    cursor: ne-resize;
}

.resize-handle-se {
    bottom: -5px;
    right: -5px;
    cursor: se-resize;
}

 Javascript

First of all, define some of the variables and initializing the Canvas and the target image.

var resizeableImage = function(image_target) {
    var $container,
    orig_src = new Image(),
    image_target = $(image_target).get(0),
    event_state = {},
    constrain = false,
    min_width = 60,
    min_height = 60,
    max_width = 800,
    max_height = 900,
    resize_canvas = document.createElement('canvas');
});

resizeableImage($('.resize-image'));

Next, we are going to create the init function that we will call immediately.This function wraps the image with a container, creates resize handles and makes a copy of the original image used for resizing. We likewise assign the jQuery object for the container element to a variable so we can use it later and add a mousedown event listener to recognize when someone starts dragging one of the handles.

var resizeableImage = function(image_target) {

// ...
    init = function(){

        // Create a new image with a copy of the original src
        // When resizing, we will always use this original copy as the base
        orig_src.src=image_target.src;

        // Add resize handles
        $(image_target).wrap('<div class="resize-container"></div>')
        .before('<span class="resize-handle resize-handle-nw"></span>')
        .before('<span class="resize-handle resize-handle-ne"></span>')
        .after('<span class="resize-handle resize-handle-se"></span>')
        .after('<span class="resize-handle resize-handle-sw"></span>');

        // Get a variable for the container
        $container =  $(image_target).parent('.resize-container');

        // Add events
        $container.on('mousedown', '.resize-handle', startResize);
    };

//...

    init();
}

startResize and endResize functions does almost nothing other than tell the browser to start detecting where the mouse is moving and when to stop .

startResize = function(e){
    e.preventDefault();
    e.stopPropagation();
    saveEventState(e);
    $(document).on('mousemove', resizing);
    $(document).on('mouseup', endResize);
};

endResize = function(e){
    e.preventDefault();
    $(document).off('mouseup touchend', endResize);
    $(document).off('mousemove touchmove', resizing);
};

We would like to take a snapshot of the container’s dimensions and other key data inputs before we start tracking the mouse position.We are going to store these in a variable named event_state and use them later as a point of reference while resizing to figure out the change in height and width.

saveEventState = function(e){
  // Save the initial event details and container state
  event_state.container_width = $container.width();
  event_state.container_height = $container.height();
  event_state.container_left = $container.offset().left; 
  event_state.container_top = $container.offset().top;
  event_state.mouse_x = (e.clientX || e.pageX || e.originalEvent.touches[0].clientX) + $(window).scrollLeft(); 
  event_state.mouse_y = (e.clientY || e.pageY || e.originalEvent.touches[0].clientY) + $(window).scrollTop();

  // This is a fix for mobile safari
  // For some reason it does not allow a direct copy of the touches property
  if(typeof e.originalEvent.touches !== 'undefined'){
	event_state.touches = [];
	$.each(e.originalEvent.touches, function(i, ob){
	  event_state.touches[i] = {};
	  event_state.touches[i].clientX = 0+ob.clientX;
	  event_state.touches[i].clientY = 0+ob.clientY;
	});
  }
  event_state.evnt = e;
}

The resizing function is where most of the things happens. This function is ceaselessly called while the user is dragging one of the resize handles.Each time this function is invoked we work compute new width and height by taking the current position of the mouse with respect to the initial position of the corner we are dragging.

resizing = function(e){ 
    var mouse={},width,height,left,top,offset=$container.offset();
    mouse.x = (e.clientX || e.pageX || e.originalEvent.touches[0].clientX) + $(window).scrollLeft(); 
    mouse.y = (e.clientY || e.pageY || e.originalEvent.touches[0].clientY) + $(window).scrollTop();

    width = mouse.x - event_state.container_left;
    height = mouse.y  - event_state.container_top;
    left = event_state.container_left;
    top = event_state.container_top;

    if(constrain || e.shiftKey){
        height = width / orig_src.width * orig_src.height;
    }

    if(width > min_width && height > min_height && width < max_width && height < max_height){
      resizeImage(width, height);  
      // Without this Firefox will not re-calculate the the image dimensions until drag end
      $container.offset({'left': left, 'top': top});        
    }
}

Now, we add the option to restrain the image dimensions when toggled using the shift key or a variable.

Finally, we resize the image, but only if the new width and height are not exceeding the bounds of the min and max variables that we set before.

Note: As image is being resized actually, not just simply changing the width and height properties, you should think about constraining how regularly resizeImage is called to enhance performance. Which is known as debouncing or throttling.

Actual resizing of the image

Drawing an image with the Canvas is as easy as drawImage. We set the proportions of the canvas first and always use original full-sized picture.After that we use toDataURL on the Canvas to get a Base64-encoded version of the newly resized image and place this on the page.

A full explanation is there for all the parameters that can be used with the drawImage method in the cropping section of this article.

resizeImage = function(width, height){
    resize_canvas.width = width;
    resize_canvas.height = height;
    resize_canvas.getContext('2d').drawImage(orig_src, 0, 0, width, height);   
    $(image_target).attr('src', resize_canvas.toDataURL("image/png"));  
};

Easy ?, There is a little condition: the image must be on the same domain as the page or on a server with cross-origin resource sharing (CORS) enabled. If it’s not, you might get some problems with an error saying ‘tainted canvas’.

Resizing from different corners

Now you should have a working demo.However, it’s not finished. Right now, regardless of which corner of the picture we resize, it carries on as if we are resizing it from the bottom right. We need to have the capacity to resize the picture from any corner. To do this we have to see how it should behave.

When resizing, the corner we’re dragging as well as its adjacent edges should move, while the corner directly opposite and its adjacent edges should remain fixed.

resize image with canvas

The time we change the width and height of an image, the right and bottom edges move, while the top and left edges will remain the same. This implies that by default, an image is resized from its bottom right corner.

This default behaviour cannot be changed, yet when resizing from any corner other than the bottom right we can change the general position of the image so that it appears as though the opposite corner and edges remain fixed. How about we update our resizing capacity:

resizing = function(e){
  var mouse={},width,height,left,top,offset=$container.offset();
  mouse.x = (e.clientX || e.pageX || e.originalEvent.touches[0].clientX) + $(window).scrollLeft(); 
  mouse.y = (e.clientY || e.pageY || e.originalEvent.touches[0].clientY) + $(window).scrollTop();
  
  // Position image differently depending on the corner dragged and constraints
  if( $(event_state.evnt.target).hasClass('resize-handle-se') ){
    width = mouse.x - event_state.container_left;
    height = mouse.y  - event_state.container_top;
    left = event_state.container_left;
    top = event_state.container_top;
  } else if($(event_state.evnt.target).hasClass('resize-handle-sw') ){
    width = event_state.container_width - (mouse.x - event_state.container_left);
    height = mouse.y  - event_state.container_top;
    left = mouse.x;
    top = event_state.container_top;
  } else if($(event_state.evnt.target).hasClass('resize-handle-nw') ){
    width = event_state.container_width - (mouse.x - event_state.container_left);
    height = event_state.container_height - (mouse.y - event_state.container_top);
    left = mouse.x;
    top = mouse.y;
    if(constrain || e.shiftKey){
      top = mouse.y - ((width / orig_src.width * orig_src.height) - height);
    }
  } else if($(event_state.evnt.target).hasClass('resize-handle-ne') ){
    width = mouse.x - event_state.container_left;
    height = event_state.container_height - (mouse.y - event_state.container_top);
    left = event_state.container_left;
    top = mouse.y;
    if(constrain || e.shiftKey){
      top = mouse.y - ((width / orig_src.width * orig_src.height) - height);
    }
  }

We are now checking to see which resize-handle has been dragged and we’re moving the picture while resizing it with the goal that it shows up just as the right corner remains fixed.

Movement of the image

Since we can resize the picture from any of its corners you may have noticed we can unintentionally change its position on the page. We have to give clients the ability to move the picture again into the center of frame. In the init function we should add another event listener like the one we did before.

init = function(){

    //...

    $container.on('mousedown', 'img', startMoving);
}

We now add startMoving and endMoving functions similar to startResize and endResize.

startMoving = function(e){
    e.preventDefault();
    e.stopPropagation();
    saveEventState(e);
    $(document).on('mousemove', moving);
    $(document).on('mouseup', endMoving);
};

endMoving = function(e){
    e.preventDefault();
    $(document).off('mouseup', endMoving);
    $(document).off('mousemove', moving);
};

In the function moving we have to work out the new position of the top left edge of the container. This will be equivalent to the current position of the mouse, offset by the distance the mouse was from the top left corner when we began dragging the image.

moving = function(e){
    var  mouse={};
    e.preventDefault();
    e.stopPropagation();
    mouse.x = (e.clientX || e.pageX) + $(window).scrollLeft();
    mouse.y = (e.clientY || e.pageY) + $(window).scrollTop();
    $container.offset({
        'left': mouse.x - ( event_state.mouse_x - event_state.container_left ),
        'top': mouse.y - ( event_state.mouse_y - event_state.container_top ) 
    });
};

Image Cropping

Since we can resize the image we might need to crop it also. As opposed to permitting clients to edit the image to any size and shape, we should make a frame that is the exact measurements we need and request the users place the image inside that frame. This gives them control over the zoom and framing, yet guarantees the output image to be of same size and shape.

And to accomplish this we need to add  the following HTML:

<div class="overlay">
    <div class="overlay-inner">
    </div>
</div>
<button class="btn-crop js-crop">Crop</button>

 

The styles for the overlay box are essential, especially it’s position, width and height as they are utilized to figure out what portion of the image is cropped.It’s also important to keep in mind that the frame should always be visible on any background color.That is the reason I employed a semi transparent white outline around the main box in the example.

.overlay {
    position: absolute;
    left: 50%;
    top: 50%;
    margin-left: -100px;
    margin-top: -100px;
    z-index: 999;
    width: 200px;
    height: 200px;
    border: solid 2px rgba(222,60,80,.9);
    box-sizing: content-box;
    pointer-events: none;
}

.overlay:after,
.overlay:before {
    content: '';
    position: absolute;
    display: block;
    width: 204px;
    height: 40px;
    border-left: dashed 2px rgba(222,60,80,.9);
    border-right: dashed 2px rgba(222,60,80,.9);
}

.overlay:before {
    top: 0;
    margin-left: -2px;
    margin-top: -40px;
}

.overlay:after {
    bottom: 0;
    margin-left: -2px;
    margin-bottom: -40px;
}

.overlay-inner:after,
.overlay-inner:before {
    content: '';
    position: absolute;
    display: block;
    width: 40px;
    height: 204px;
    border-top: dashed 2px rgba(222,60,80,.9);
    border-bottom: dashed 2px rgba(222,60,80,.9);
}

.overlay-inner:before {
    left: 0;
    margin-left: -40px;
    margin-top: -2px;
}

.overlay-inner:after {
    right: 0;
    margin-right: -40px;
    margin-top: -2px;
}

.btn-crop {
    position: absolute;
    vertical-align: bottom;
    right: 5px;
    bottom: 5px;
    padding: 6px 10px;
    z-index: 999;
    background-color: rgb(222,60,80);
    border: none;
    border-radius: 5px;
    color: #FFF;
}

Now, update the JavaScript with the function and event listener below:

init = function(){

    //...

    $('.js-crop').on('click', crop);
  
};

crop = function(){
    var crop_canvas,
        left = $('.overlay').offset().left - $container.offset().left,
        top =  $('.overlay').offset().top - $container.offset().top,
        width = $('.overlay').width(),
        height = $('.overlay').height();
        
    crop_canvas = document.createElement('canvas');
    crop_canvas.width = width;
    crop_canvas.height = height;
    
    crop_canvas.getContext('2d').drawImage(image_target, left, top, width, height, 0, 0, width, height);
    window.open(crop_canvas.toDataURL("image/png"));
}

The crop function is like the resizeImage function however rather than passing it width and height values we get the width and height from the overlay element.

For cropping, the canvas drawImage function needs nine parameters. The main parameter is the source picture. The following four parameters shows what portion of image is utilized (the clipping box). The last four parameters demonstrate where on the canvas to begin drawing the image and at what size.

Touch events and gesture detection

After adding mouse events, now let’s add support for touch enabled devices.

For mousedown and mouseup there are identical touch events, touchstart and touchend and for mousemove there is the similar touchmove.

We shall include touchstart and touchend all over the we have a mousedown and mouseup event listener and touchmove all wherever we have mousemove.

// In init()...
$container.on('mousedown touchstart', '.resize-handle', startResize);
$container.on('mousedown touchstart', 'img', startMoving);

//In startResize() ...
$(document).on('mousemove touchmove', moving);
$(document).on('mouseup touchend', endMoving);

//In endResize()...
$(document).off('mouseup touchend', endMoving);
$(document).off('mousemove touchmove', moving);

//In  startMoving()...
$(document).on('mousemove touchmove', moving);
$(document).on('mouseup touchend', endMoving);

//In endMoving()...
$(document).off('mouseup touchend', endMoving);
$(document).off('mousemove touchmove', moving);

Because ,we’re resizing an image it may be sensible to expect that a few users will attempt regular signals like pinch to zoom. There is a library called Hammer that makes it really eeasy to work with gesture. However, since we just need pinch to zoom, as per our requirement, it may be a more like a sledge hammer. Let me show you how easy it is to detect pinch zoom without any library.

You may have noticed that we have already stored initial touch data; that will be very useful at this point.

Initially we will check if the event contains two “touches” and measure the distance between them. We take note of this as the initial distance and after that continually measure the amount of change in distance while moving. Now, let’s update the moving function:

 

moving = function(e){
  var  mouse={}, touches;
  e.preventDefault();
  e.stopPropagation();
  
  touches = e.originalEvent.touches;
  mouse.x = (e.clientX || e.pageX || touches[0].clientX) + $(window).scrollLeft(); 
  mouse.y = (e.clientY || e.pageY || touches[0].clientY) + $(window).scrollTop();
  $container.offset({
    'left': mouse.x - ( event_state.mouse_x - event_state.container_left ),
    'top': mouse.y - ( event_state.mouse_y - event_state.container_top ) 
  });
  // Watch for pinch zoom gesture while moving
  if(event_state.touches && event_state.touches.length > 1 && touches.length > 1){
    var width = event_state.container_width, height = event_state.container_height;
    var a = event_state.touches[0].clientX - event_state.touches[1].clientX;
    a = a * a; 
    var b = event_state.touches[0].clientY - event_state.touches[1].clientY;
    b = b * b; 
    var dist1 = Math.sqrt( a + b );
    
    a = e.originalEvent.touches[0].clientX - touches[1].clientX;
    a = a * a; 
    b = e.originalEvent.touches[0].clientY - touches[1].clientY;
    b = b * b; 
    var dist2 = Math.sqrt( a + b );

    var ratio = dist2 /dist1;

    width = width * ratio;
    height = height * ratio;
    // To improve performance you might limit how often resizeImage() is called
    resizeImage(width, height);
  }
};

We divided the initial distance by initial distance to get the proportion and by what amount to scale the image. We calculated new width and height and afterward resized the image accordingly.

That’s all. checkout  the ZIP file.

I hope that you liked this tutorial.I suggest you to read my other blogs as well and if you have any query or suggestion then leave it in the comments section.

Please follow and like us:



Leave a Comment

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