#77 Cropping Images with JCrop
4-23-2017

Summary

Extend your image upload functionality with JCrop. Learn to redirect the user to a crop page once they have uploaded their image and save versions of the cropped images.
4
rails form javascript upload 7:00

Summary

Bashrails g migration add_avatar_to_users avatar
rails g uploader avatar
Gemfilegem 'mini_magick'
gem 'carrierwave'
gem 'rails-assets-jcrop', source: 'https://rails-assets.org'
application.js//= require jcrop
application.css*= require jcrop
users_controller.rb  def create
    @user = User.new(user_params)
    if @user.save
      if params[:user][:avatar].present?
        render :crop
      else
        redirect_to @user, notice: "Successfully created user."
      end
    else
      render :new
    end
  end

  def update
    @user = User.find(params[:id])
    if @user.update_attributes(user_params)
      if params[:user][:avatar].present?
        render :crop
      else
        redirect_to @user, notice: "Successfully updated user."
      end
    else
      render :new
    end
  end

...

    def user_params
      params.require(:user).permit(:name, :avatar, :crop_x, :crop_y, :crop_w, :crop_h)
    end
user.rbclass User < ApplicationRecord
  mount_uploader :avatar, AvatarUploader
  
  attr_accessor :crop_x, :crop_y, :crop_w, :crop_h
  after_update :crop_avatar

  def crop_avatar
    avatar.recreate_versions! if crop_x.present?
  end

end
uploaders/avatar_uploader.rbclass AvatarUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick
  storage :file

  version :thumb do
    process :crop
    resize_to_fill(100, 100)
  end

  version :tiny, from_version: :thumb do
    process resize_to_fill: [20, 20]
  end

  version :large do
    resize_to_limit(600, 600)
  end

  def crop
    if model.crop_x.present?
      resize_to_limit(600, 600)
      manipulate! do |img|
        x = model.crop_x.to_i
        y = model.crop_y.to_i
        w = model.crop_w.to_i
        h = model.crop_h.to_i
        # [[w, h].join('x'),[x, y].join('+')].join('+') => "wxh+x+y"
        img.crop([[w, h].join('x'),[x, y].join('+')].join('+'))
      end
    end
  end
end
users.coffee$ ->
  new AvatarCrop()

class AvatarCrop
  constructor: ->
    width = parseInt($('#cropbox').width())
    height = parseInt($('#cropbox').height())
    $('#cropbox').Jcrop
      aspectRatio: 1
      setSelect: [0, 0, width, height]
      onSelect: @update
      onChange: @update

  update: (coords) =>
    $('#user_crop_x').val(coords.x)
    $('#user_crop_y').val(coords.y)
    $('#user_crop_w').val(coords.w)
    $('#user_crop_h').val(coords.h)
    @updatePreview(coords)

  updatePreview: (coords) =>
    rx = 100 / coords.w
    ry = 100 / coords.h

    $('#preview').css
        width: Math.round(rx * $('#cropbox').width()) + 'px'
        height: Math.round(ry * $('#cropbox').height()) + 'px'
        marginLeft: '-' + Math.round(rx * coords.x) + 'px'
        marginTop: '-' + Math.round(ry * coords.y) + 'px'
users.jsvar AvatarCrop,
  bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };

$(function() {
  return new AvatarCrop();
});

AvatarCrop = (function() {
  function AvatarCrop() {
    this.updatePreview = bind(this.updatePreview, this);
    this.update = bind(this.update, this);
    var height, width;
    width = parseInt($('#cropbox').width());
    height = parseInt($('#cropbox').height());
    $('#cropbox').Jcrop({
      aspectRatio: 1,
      setSelect: [0, 0, width, height],
      onSelect: this.update,
      onChange: this.update
    });
  }

  AvatarCrop.prototype.update = function(coords) {
    $('#user_crop_x').val(coords.x);
    $('#user_crop_y').val(coords.y);
    $('#user_crop_w').val(coords.w);
    $('#user_crop_h').val(coords.h);
    return this.updatePreview(coords);
  };

  AvatarCrop.prototype.updatePreview = function(coords) {
    var rx, ry;
    rx = 100 / coords.w;
    ry = 100 / coords.h;
    return $('#preview').css({
      width: Math.round(rx * $('#cropbox').width()) + 'px',
      height: Math.round(ry * $('#cropbox').height()) + 'px',
      marginLeft: '-' + Math.round(rx * coords.x) + 'px',
      marginTop: '-' + Math.round(ry * coords.y) + 'px'
    });
  };

  return AvatarCrop;

})();
crop.html.erb

<div class='row'>   <div class='col-xs-8'>     <div class="panel panel-primary">       <div class="panel-heading">         <h3 class="panel-title">Crop Avatar</h3>       </div>       <div class="panel-body">         <%= image_tag @user.avatar_url(:large), id: "cropbox" %>         <br>         <%= simple_form_for(@user) do |f| %>           <% %w[x y w h].each do |attribute| %>             <%= f.input "crop_#{attribute}", as: :hidden %>           <% end %>           <div class="form-actions">             <%= f.button :submit %>           </div>         <% end %>       </div>     </div>   </div>   <div class='col-xs-4'>     <div class="panel panel-primary">       <div class="panel-heading">         <h3 class="panel-title">Preview</h3>       </div>       <div class="panel-body">         <div style="width:100px; height:100px; overflow:hidden">           <%= image_tag @user.avatar.url(:large), id: "preview" %>         </div>       </div>     </div>   </div> </div>


Yirazk said over 1 year ago:

Thank you very much, I was looking for it.

Vitaly Panchuk said over 1 year ago:

Cool video, could you say which code sceme you use?

kobaltz PRO said over 1 year ago:

gruvbox for sublime text. It is also available for VIM,  RubyMine, etc.

Vitaly Panchuk said over 1 year ago:

Thanks!

Sylor-huang said 5 months ago:

Thanks for your video, @kobaltz, but I have some problems with your codes , I used rails 4.2.8 , ruby 2.5.0 .

In my users_controller.rb , I write the updatelike this :

def update
    @user = User.find(params[:id])
    if @user.update_attributes(user_params)
      if params[:user][:picture].present?
        render :crop
      else
        redirect_to @user, notice: "Success update"
      end
    else
      render :edit
    end
  end

and then I write the user_params like this :
```
private
def set_user
@user = User.find(params[:id])
end

def user_params
params.require(:user).permit(:name, :email, :phone, :password, :phoneMes, :picture, :crop_x, :crop_y, :crop_w, :crop_h, :password_confirmation)
end
```

But when I upload the image , it always jump to edit.html.erb without jump to crop.html.erb, this is for why ? Could you help me ? I had searched a long time , but it seems this no answer. Thanks so much ,@kobaltz.

[email protected] said 5 months ago:

I'm trying to do this, but I'm using Devise for users and my users controller doesn't like that I don't have any info for the edit method. Is there anyone that can help?

kobaltz PRO said 5 months ago:

You could try to change the controller endpoint so it's not going to devise, but rather a user controller. So you wouldn't use the edit_user_registration_path in the view, but rather something like edit_user. With Devise, you may need to add something like bypass_sign_in(current_user) as it will sometimes log a user out on changes. So the edit action of the UsersController may look like this.

  def update
    if current_user.update_attributes(user_params)
      bypass_sign_in(current_user)
      if params[:user][:avatar].present?
        render :crop
      else
        redirect_to edit_user_path(current_user), notice: "Successfully updated user."
      end
    else
      render :edit
    end
  end

Login to Comment