Skip to content

Instantly share code, notes, and snippets.

@Kryptonit3-zz
Last active July 13, 2018 18:15
Show Gist options
  • Save Kryptonit3-zz/f3e7e29707ed791087c6 to your computer and use it in GitHub Desktop.
Save Kryptonit3-zz/f3e7e29707ed791087c6 to your computer and use it in GitHub Desktop.
Lightweight image cropping with jQuery
# Ignore everything in this directory
*
# Except this file
!.gitignore
<!-- Elsewhere on your document -->
<button class="btn btn-info" id="showAvatar">Change Photo</button>
<!-- the modal code -->
<div id="avatar" class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">Profile Picture</h4>
</div>
<form method="POST" action="your-avatar-url" accept-charset="UTF-8" enctype="multipart/form-data">
<input name="_token" type="hidden" value="your-token-value">
<div class="modal-body">
<!-- crop data -->
<!-- points for cropping -->
<input type="hidden" name="x" />
<input type="hidden" name="y" />
<!-- the size of the crop window -->
<input type="hidden" name="width" />
<input type="hidden" name="height" />
<!-- the size that the full size image needs to
be resized to before cropping due to the use
of width:100% and how it affects the size/x,y
points of the crop -->
<input type="hidden" name="img-width" />
<input type="hidden" name="img-height" />
<div class="row">
<div class="col-md-12">
<h5>Step 1: Select a photo (at least 140px by 140px)</h5>
<input type="file" name="avatar" />
</div>
<div class="col-md-12">
<div class="errors"></div>
</div>
<div class="col-md-12 step2">
<h5>Step2: Select a crop region</h5>
<div class="preview-container">
<img class="preview" />
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Save</button>
</div>
</form>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
$(function() {
$input = $('#avatar input:file');
$form = $input.closest('form');
$errors = $('#avatar .errors');
$step2 = $form.find('.step2');
$previewContainer = $form.find('.preview-container');
$form.submit(function(e){
$errors.hide();
if(typeof $file == 'undefined') {
e.preventDefault();
$errors.text('Select an image and crop it first!').show();
throw 'Select an image and crop it first!';
}
if(!$form.find('[name="width"]').val().length) {
e.preventDefault();
$errors.text('Select a crop region first!').show();
throw 'Select a crop region first!';
}
});
$('#showAvatar').click(function(){
$('#avatar').modal({
backdrop: 'static',
keyboard: false
});
});
$('#avatar').on('hidden.bs.modal', function(){
$form.trigger('reset');
$step2.hide();
$previewContainer.empty();
});
$input.on('change', function () {
$step2.hide();
$file = $input[0].files[0];
$allowed = ['image/jpeg', 'image/png'];
// check if image is actually an accepted image type
if($.inArray($file.type, $allowed) == -1) {
$errors.text('Invalid file type! JPG(JPEG) and PNG only!').show();
throw 'Invalid file type! JPG(JPEG) and PNG only!';
}
// 3MB limit on images
if($file.size > 3 * 1024 * 1024) {
$errors.text('The image you have selected is too big! 3MB limit!').show();
throw 'The image you have selected is too big! 3MB limit!';
}
$errors.hide();
var reader = new FileReader();
reader.onload = function(e) {
$previewContainer.html('<img class="preview" src="'+ e.target.result+'" />');
$preview = $form.find('.preview');
$preview.on('load change', function(){
new Cropper(this, {
ratio: {
width: 1,
height: 1
},
min_width: 140,
min_height: 140,
max_width: ($preview.width() > $preview.height() ? $preview.height() : $preview.width()),
max_height: ($preview.width() > $preview.height() ? $preview.height() : $preview.width()),
update: function (coordinates) {
for (var i in coordinates) {
$form.find('[name="'+i+'"]').val(coordinates[i]);
}
}
});
$form.find('[name="img-width"]').val($preview.width());
$form.find('[name="img-height"]').val($preview.height());
});
$step2.show();
};
reader.readAsDataURL($file);
});
});
// A nice way to manage it on laravel using Intervention Image package
// Also, there needs to be a 'avatar' varchar/string column on users table
// $table->string('avatar')->nullable();
// you should also have an /uploads/avatar directory in your public folder
public function avatar(Request $request)
{
$validator = Validator::make($request->all(), [
'x' => 'required|numeric',
'y' => 'required|numeric',
'width' => 'required|numeric',
'height' => 'required|numeric',
'img-width' => 'required|numeric',
'img-height' => 'required|numeric',
'avatar' => 'required|image'
]);
if ($validator->fails()) {
session()->flash('error_msg', 'There was an issue with your avatar! Try again!');
return redirect()->back();
}
if(auth()->user()->avatar) {
if(File::exists(public_path(auth()->user()->avatar))) {
File::delete(public_path(auth()->user()->avatar));
}
}
$name = hash("SHA256", env('APP_KEY') . str_random(30) . time());
$db_location = 'uploads/avatar/' . $name . '.jpg';
$save_location = public_path('uploads/avatar/') . $name . '.jpg';
Image::make($request->file('avatar')->getRealPath())
->resize($request->input('img-width'), $request->input('img-height'))
->crop($request->input('width'), $request->input('height'), $request->input('x'), $request->input('y'))
->resize(140, 140)
->encode('jpg', 100)
->save($save_location, 100);
$user = User::find(auth()->user()->getAuthIdentifier());
$user->avatar = $db_location;
$user->save();
return redirect()->back();
}

This is a simple implementation of this lightweight image cropping library Pure JavaScript Image Crop Component - wouldn't be hard to implement this free cropping library

Be sure to include the required files cropper.css, cropper.min.js, [functions.js](https://gist.github.com/Kryptonit3/f3e7e29707ed791087c6#file-functions-js)

This assumes you are using bootstrap with modals, and have a button with the ID of showAvatar somewhere on your document to invoke the modal.

When processing on the backend you will have the following inputs to work with:

x & y = the starting crop points
width & height = the size of the crop window
img-width & img-height = the size that the full size image needs to be resized to
                            before cropping due to the use of width:100% and how it 
                            affects the size/x,y points of the crop

You will need to add your action/url to the form and process it how you choose to.

If you are using Laravel you can add this method to your user model

A nice way to manage it on laravel using Intervention Image package Also, there needs to be a 'avatar' varchar/string column on users table add this to a new column migration or your user migration

$table->string('avatar')->nullable();

you should also have an /uploads/avatar directory in your public folder with the following file in it for when pushing to git https://gist.github.com/Kryptonit3/f3e7e29707ed791087c6#file-gitignore

public function avatar()
{
    $avatar = ($this->avatar) ? asset($this->avatar) : asset('img/default-avatar.png');

    return $avatar;
}

Be sure to set the default avatar url ('img/default-avatar.png') for users who have not set theirs.

Then in your loops you can do

@foreach($users as $user)
    <img src="{{ $user->avatar() }}" alt="{{ $user->username }}" />
@endforeach

That way if you ever change the structure of how your avatars are fetched, you only need to update your method on your User model.

Here is a method for uploading the image to the filesystem

If you have any questions, ask away in the comments! :)

.step2,.errors{display:none}
.errors{background:#e74430;color:#fff;padding:6px 10px;margin:10px 0;}
.preview{max-width:100%;max-height:600px;}
@paradisetester
Copy link

step 2 i have getting the error, not showing the cropper

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment