image_cropper 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +228 -0
- data/app/assets/javascript/image_cropper.js +688 -0
- data/app/assets/stylesheets/image_cropper.css +199 -0
- data/lib/generators/image_cropper/install_generator.rb +14 -0
- data/lib/generators/image_cropper/migrate_generator.rb +27 -0
- data/lib/generators/image_cropper/templates/images/default.png +0 -0
- data/lib/generators/image_cropper/templates/migration/migrate.rb +11 -0
- data/lib/image_cropper.rb +13 -0
- data/lib/image_cropper/model.rb +28 -0
- data/lib/image_cropper/rails.rb +8 -0
- data/lib/image_cropper/rails/engine.rb +9 -0
- data/lib/image_cropper/rails/form_builder.rb +89 -0
- data/lib/image_cropper/rails/form_helper.rb +16 -0
- data/lib/image_cropper/uploader.rb +89 -0
- data/lib/image_cropper/version.rb +3 -0
- metadata +114 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2e153fbfc633756a6098169bdb4c5e8e7b7f94b8
|
4
|
+
data.tar.gz: c1ea8b304c8ed45b56c204ed4ae40005add49760
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a75b951de33ab1038173d78d0f6a258ffe0acb9442a00ab17787bc9f8153827b43ef46a3a593adfb9fac34c0737a5722b26e9055240a77c1289f71210118b06d
|
7
|
+
data.tar.gz: 7708f4b822e256822940d02495f2b57c41e4356ef4be3762c80b2a44c1bf872196f6b2336d38491bf60ae5cd9d8b0732f09137959fe82385c1a269319a00ea7b
|
data/README.md
ADDED
@@ -0,0 +1,228 @@
|
|
1
|
+
# image_cropper
|
2
|
+
|
3
|
+
Combine upload images with a cropper
|
4
|
+
|
5
|
+
|
6
|
+
## Features
|
7
|
+
|
8
|
+
* Previews image before upload
|
9
|
+
* Considers carrierwave cache (when form return with error)
|
10
|
+
* Responsive
|
11
|
+
* Real crop (generate 2 files, and in case of circle frame, effectively crop an circular image)
|
12
|
+
|
13
|
+
## Dependencies
|
14
|
+
|
15
|
+
* Gem [jquery-rails](https://github.com/rails/jquery-rails)
|
16
|
+
* Gem [CarrierWave](https://github.com/carrierwaveuploader/carrierwave) to upload files
|
17
|
+
* Gem [RMagick](https://github.com/rmagick/rmagick) to crop images
|
18
|
+
* Gem [font-awesome-rails](https://github.com/bokmann/font-awesome-rails) to show pretty icons
|
19
|
+
|
20
|
+
|
21
|
+
___
|
22
|
+
|
23
|
+
|
24
|
+
# Getting Started
|
25
|
+
|
26
|
+
## Installation
|
27
|
+
|
28
|
+
Add this line to your application's Gemfile:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
gem 'image_cropper', git: 'git@gitlab.snappler.com:gems/image_cropper.git'
|
32
|
+
```
|
33
|
+
|
34
|
+
And then execute:
|
35
|
+
|
36
|
+
$ bundle install
|
37
|
+
|
38
|
+
|
39
|
+
Finally run:
|
40
|
+
|
41
|
+
rails g image_cropper:install
|
42
|
+
|
43
|
+
This one generate the default null image **'app/assets/images/image_cropper/default.png'** used to show when the image window don't have one (later you can modify if you need)
|
44
|
+
|
45
|
+
## Create/Update the Uploader
|
46
|
+
|
47
|
+
Start off by [generating an uploader](https://github.com/carrierwaveuploader/carrierwave#user-content-getting-started) or modify if you already get one:
|
48
|
+
|
49
|
+
Using "Image" as an example, you should have a file
|
50
|
+
|
51
|
+
app/uploaders/image_uploader.rb
|
52
|
+
|
53
|
+
Add these lines and leave it like this:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
class ImageUploader < CarrierWave::Uploader::Base
|
57
|
+
|
58
|
+
#If you need to define another/s columns, just define in class
|
59
|
+
#this methods and puts in th array, but it's no necesary and take :file as default
|
60
|
+
#def self.mount_uploader_columns
|
61
|
+
# [:file]
|
62
|
+
#end
|
63
|
+
|
64
|
+
#Include the functionality of the gem
|
65
|
+
include ImageCropper::Uploader
|
66
|
+
|
67
|
+
storage :file
|
68
|
+
|
69
|
+
def store_dir
|
70
|
+
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
## Update Model
|
80
|
+
|
81
|
+
As an example I use a dummy model **User**:
|
82
|
+
|
83
|
+
$ rails g scaffold User name
|
84
|
+
|
85
|
+
Then integrate with [Carrierwave generating the column that it need](https://github.com/carrierwaveuploader/carrierwave#user-content-activerecord)
|
86
|
+
_(In the example of carrierwave use **avatar** as column, in this one I choose **file** as column because is more pretty :P)_
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
Now, add fields to the model you want to mount the uploader by executing:
|
91
|
+
|
92
|
+
$ rails g image_cropper:migrate User
|
93
|
+
|
94
|
+
This generate a file **XXXXXXXXXXXXXX_image_cropper_add_fields_to_users.rb**
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
class ImageCropperAddFieldsToUsers < ActiveRecord::Migration
|
98
|
+
def change
|
99
|
+
#Used to crop file
|
100
|
+
add_column :users, :coord_x, :float, default: 0
|
101
|
+
add_column :users, :coord_y, :float, default: 0
|
102
|
+
add_column :users, :coord_w, :float, default: 0
|
103
|
+
add_column :users, :coord_h, :float, default: 0
|
104
|
+
add_column :users, :coord_z, :float, default: 1
|
105
|
+
add_column :users, :frame, :string, default: 'square'
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
```
|
110
|
+
|
111
|
+
Then execute:
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
rake db:migrate
|
115
|
+
```
|
116
|
+
|
117
|
+
|
118
|
+
Open your model file and add these line:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
class User < ActiveRecord::Base
|
122
|
+
include ImageCropper::Model
|
123
|
+
mount_uploader :file, ImageUploader
|
124
|
+
end
|
125
|
+
```
|
126
|
+
|
127
|
+
|
128
|
+
## Require Assets (Css/Js)
|
129
|
+
|
130
|
+
Include the following in your javascript manifest:
|
131
|
+
|
132
|
+
```javascript
|
133
|
+
//= require jquery
|
134
|
+
//= require image_cropper
|
135
|
+
```
|
136
|
+
|
137
|
+
And the following in your stylesheet manifest:
|
138
|
+
|
139
|
+
```css
|
140
|
+
*= require font-awesome
|
141
|
+
*= require image_cropper
|
142
|
+
```
|
143
|
+
(for the moment font-awesome is dependency and you need to require)
|
144
|
+
|
145
|
+
|
146
|
+
# Using
|
147
|
+
|
148
|
+
Now you're able to use it in forms:
|
149
|
+
|
150
|
+
```erb
|
151
|
+
<div style='max-width: 400px;'>
|
152
|
+
<%= f.image_cropper_field :file %>
|
153
|
+
</div>
|
154
|
+
```
|
155
|
+
|
156
|
+
If you need to save remote image, you need to set value in the hidden field and trigger 'change'
|
157
|
+
```js
|
158
|
+
$('.image-cropper-file-url').val('https://www.gettyimages.es/gi-resources/images/Embed/new/embed2.jpg').trigger('change');
|
159
|
+
```
|
160
|
+
|
161
|
+
|
162
|
+
remember put the property 'multipart' to form when you use image_cropper:
|
163
|
+
|
164
|
+
```erb
|
165
|
+
<%= form_with(model: user, local: true, html: {multipart: true}) do |form| %>
|
166
|
+
```
|
167
|
+
|
168
|
+
And add to strong parameters the fields
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
params.require(:user).permit(:name, :coord_x, :coord_y, :coord_w, :coord_h, :coord_z, :frame, :file, :file_cache, :remote_file_url)
|
172
|
+
```
|
173
|
+
**(Is very important the orden of these params, always leave the :file_cache, and :file to the end)**
|
174
|
+
|
175
|
+
|
176
|
+
|
177
|
+
And in a show view:
|
178
|
+
|
179
|
+
```erb
|
180
|
+
<div style='max-width: 400px;'>
|
181
|
+
<%= image_tag @user.file.thumb.url, class: 'image-cropper' %>
|
182
|
+
</div>
|
183
|
+
```
|
184
|
+
|
185
|
+
You can send some params
|
186
|
+
|
187
|
+
* aspect_ratio
|
188
|
+
* frame
|
189
|
+
* operation_object
|
190
|
+
* destroyable
|
191
|
+
* destroy_class
|
192
|
+
* destroy_confirm
|
193
|
+
|
194
|
+
| Params | Type | Default | Options | Description |
|
195
|
+
| ---------------- |:-------------:|:-------------------------------:|:--------------------------------------:|-----------------------------------------------------------------------------:|
|
196
|
+
| aspect_ratio | _(string)_ | **'4:3'** | any combination of **'number:number'** | 4:4 are perfect square, 4:3 rectangle horizontal, 3:4 rectangle vertical |
|
197
|
+
| frame | _(string)_ | **'square'** | **'square'** or **'circle'** | frame of image, if you want a circle o square frame |
|
198
|
+
| operation_object | _(object)_ | **self** | any **object** | maybe the object where mount the uploader it's not the one you want to erase |
|
199
|
+
| destroyable | _(boolean)_ | **false** | **true** or **false** | If you want a button to remove object with nested_attributes |
|
200
|
+
| destroy_class | _(string)_ | **'.image-cropper-container'** | a class or id of containter | Is the container that disapear when you click remove |
|
201
|
+
| destroy_confirm | _(string)_ | **'Are you sure?'** | any message you want | message to display to confirm to remove the image |
|
202
|
+
|
203
|
+
|
204
|
+
___
|
205
|
+
|
206
|
+
|
207
|
+
## Features to the future
|
208
|
+
* Make test
|
209
|
+
* Problem with gif when crop area, not resize - **SOLVED**
|
210
|
+
* Problem with jpg, jpeg, circular crop - **NOT ALOW, JPG AND JPEG CAN'T CIRCULAR CROP**
|
211
|
+
|
212
|
+
## Thanks!
|
213
|
+
|
214
|
+
RubyGems
|
215
|
+
|
216
|
+
Esta gema está publicada en [RubyGems](https://rubygems.org/gems/image_cropper) bajo el user juan.labattaglia@snappler.com
|
217
|
+
|
218
|
+
|
219
|
+
## Actualización
|
220
|
+
|
221
|
+
Una vez que hay una nueva versión, modificar `lib/image_cropper/version.rb`
|
222
|
+
|
223
|
+
Y luego ejecutar:
|
224
|
+
|
225
|
+
```shell
|
226
|
+
gem build image_cropper.gemspec
|
227
|
+
gem push image_cropper-[VERSION].gem
|
228
|
+
```
|
@@ -0,0 +1,688 @@
|
|
1
|
+
|
2
|
+
(function ( $ ) {
|
3
|
+
|
4
|
+
$.fn.image_cropper = function( options ) {
|
5
|
+
|
6
|
+
var settings = $.extend({
|
7
|
+
}, options );
|
8
|
+
|
9
|
+
//-------------------------- Definitions
|
10
|
+
//Containers
|
11
|
+
var image_cropper_container = $(this);
|
12
|
+
var image_cropper_crop_container = image_cropper_container.find('.image-cropper-crop-container');
|
13
|
+
|
14
|
+
//Components
|
15
|
+
var image_cropper_crop = image_cropper_container.find('.image-cropper-crop');
|
16
|
+
var image_cropper_file = image_cropper_container.find('.image-cropper-file');
|
17
|
+
var image_cropper_file_cache = image_cropper_container.find('.image-cropper-file-cache');
|
18
|
+
var image_cropper_file_url = image_cropper_container.find('.image-cropper-file-url');
|
19
|
+
|
20
|
+
//Coords
|
21
|
+
var coord_x = image_cropper_container.find('.coord-x');
|
22
|
+
var coord_y = image_cropper_container.find('.coord-y');
|
23
|
+
var coord_z = image_cropper_container.find('.coord-z');
|
24
|
+
var coord_w = image_cropper_container.find('.coord-w');
|
25
|
+
var coord_h = image_cropper_container.find('.coord-h');
|
26
|
+
|
27
|
+
//Buttons
|
28
|
+
var image_cropper_file_trigger = image_cropper_container.find('.image-cropper-file-trigger');
|
29
|
+
var coord_zoom_in = image_cropper_container.find('.image-cropper-zoom-in');
|
30
|
+
var coord_zoom_out = image_cropper_container.find('.image-cropper-zoom-out');
|
31
|
+
var image_cropper_remove = image_cropper_container.find('.image-cropper-remove');
|
32
|
+
|
33
|
+
//Aux vars
|
34
|
+
var coord_x_val, coord_y_val, coord_z_val, aspect_ratio, aspect_ratio_arr, w_val, h_val, coord_w_val, coord_h_val;
|
35
|
+
|
36
|
+
|
37
|
+
|
38
|
+
var image_cropper_initialize_vars = function(){
|
39
|
+
|
40
|
+
|
41
|
+
//Set image_cropper active
|
42
|
+
image_cropper_container.addClass('active');
|
43
|
+
|
44
|
+
//Get X,Y,W,H,Z
|
45
|
+
coord_x_val = parseFloat(coord_x.val());
|
46
|
+
if (isNaN(coord_x_val)){
|
47
|
+
coord_x_val = 0;
|
48
|
+
}
|
49
|
+
|
50
|
+
coord_y_val = parseFloat(coord_y.val());
|
51
|
+
if (isNaN(coord_y_val)){
|
52
|
+
coord_y_val = 0;
|
53
|
+
}
|
54
|
+
coord_z_val = parseFloat(coord_z.val());
|
55
|
+
if (isNaN(coord_z_val)){
|
56
|
+
coord_z_val = 1;
|
57
|
+
}
|
58
|
+
|
59
|
+
aspect_ratio = image_cropper_container.data('aspect-ratio');
|
60
|
+
|
61
|
+
if (aspect_ratio === undefined){
|
62
|
+
aspect_ratio = '4:3';
|
63
|
+
}
|
64
|
+
|
65
|
+
aspect_ratio_arr = aspect_ratio.split(':');
|
66
|
+
w_val = parseFloat(aspect_ratio_arr[0]);
|
67
|
+
if (isNaN(w_val)){
|
68
|
+
w_val = 4;
|
69
|
+
}
|
70
|
+
h_val = parseFloat(aspect_ratio_arr[1]);
|
71
|
+
if (isNaN(h_val)){
|
72
|
+
h_val = 3;
|
73
|
+
}
|
74
|
+
coord_w_val = w_val * 160;
|
75
|
+
coord_h_val = h_val * 160;
|
76
|
+
|
77
|
+
|
78
|
+
//Define Crop
|
79
|
+
image_cropper_crop.guillotine({
|
80
|
+
width: coord_w_val,
|
81
|
+
height: coord_h_val,
|
82
|
+
init: { scale: coord_z_val, x: coord_x_val, y: coord_y_val },
|
83
|
+
onChange: function(data, action){
|
84
|
+
coord_x.val(data.x);
|
85
|
+
coord_y.val(data.y);
|
86
|
+
coord_w.val(data.w);
|
87
|
+
coord_h.val(data.h);
|
88
|
+
coord_z.val(data.scale);
|
89
|
+
}
|
90
|
+
});
|
91
|
+
|
92
|
+
|
93
|
+
if ((settings['enabled'] !== undefined) && (settings['enabled'] === false)){
|
94
|
+
image_cropper_crop.guillotine('disable');
|
95
|
+
image_cropper_crop_container.addClass('disable');
|
96
|
+
}
|
97
|
+
|
98
|
+
//Initialize
|
99
|
+
data = image_cropper_crop.guillotine('getData');
|
100
|
+
coord_x.val(data.x);
|
101
|
+
coord_y.val(data.y);
|
102
|
+
coord_w.val(data.w);
|
103
|
+
coord_h.val(data.h);
|
104
|
+
coord_z.val(data.scale);
|
105
|
+
}
|
106
|
+
|
107
|
+
var image_cropper_initialize_events = function(){
|
108
|
+
|
109
|
+
|
110
|
+
//remove image
|
111
|
+
image_cropper_remove.off('click');
|
112
|
+
image_cropper_remove.on('click', function() {
|
113
|
+
if (confirm($(this).data('destroy-confirm'))) {
|
114
|
+
$(this).prev('input[type=hidden]').val('1');
|
115
|
+
|
116
|
+
$(this).closest(settings['destroy_class']).fadeOut(function(){
|
117
|
+
$(this).hide();
|
118
|
+
});
|
119
|
+
}
|
120
|
+
});
|
121
|
+
|
122
|
+
|
123
|
+
//Upload image
|
124
|
+
image_cropper_file_trigger.off('click');
|
125
|
+
image_cropper_file_trigger.on('click', function() {
|
126
|
+
image_cropper_file.trigger('click');
|
127
|
+
});
|
128
|
+
|
129
|
+
//When change input file show img
|
130
|
+
image_cropper_file.off('change');
|
131
|
+
image_cropper_file.on('change', function() {
|
132
|
+
if (this.files && this.files[0]) {
|
133
|
+
//image_cropper_file_binary.val('');
|
134
|
+
var reader = new FileReader;
|
135
|
+
reader.onload = function(e) {
|
136
|
+
set_image(e.target.result);
|
137
|
+
image_cropper_file_url.val('');
|
138
|
+
image_cropper_file_cache.val('');
|
139
|
+
};
|
140
|
+
reader.readAsDataURL(this.files[0]);
|
141
|
+
}
|
142
|
+
});
|
143
|
+
|
144
|
+
|
145
|
+
//When change input url show img
|
146
|
+
image_cropper_file_url.off('change');
|
147
|
+
image_cropper_file_url.on('change', function() {
|
148
|
+
if ($(this).val() != '') {
|
149
|
+
set_image($(this).val());
|
150
|
+
image_cropper_file.val('');
|
151
|
+
image_cropper_file_cache.val('');
|
152
|
+
}
|
153
|
+
});
|
154
|
+
|
155
|
+
|
156
|
+
//Zoom in
|
157
|
+
coord_zoom_in.off('click');
|
158
|
+
coord_zoom_in.on('click', function() {
|
159
|
+
image_cropper_crop.guillotine('zoomIn');
|
160
|
+
});
|
161
|
+
|
162
|
+
//Zoom out
|
163
|
+
coord_zoom_out.off('click');
|
164
|
+
coord_zoom_out.on('click', function() {
|
165
|
+
image_cropper_crop.guillotine('zoomOut');
|
166
|
+
});
|
167
|
+
|
168
|
+
//Scrolling Zoom
|
169
|
+
image_cropper_crop.off('DOMMouseScroll mousewheel');
|
170
|
+
image_cropper_crop.on('DOMMouseScroll mousewheel', function (event) {
|
171
|
+
var guillotine = image_cropper_crop.guillotine('instance');
|
172
|
+
if(guillotine !== undefined){
|
173
|
+
if( event.originalEvent.detail > 0 || event.originalEvent.wheelDelta < 0 ) { //alternative options for wheelData: wheelDeltaX & wheelDeltaY
|
174
|
+
image_cropper_crop.guillotine('zoomOut');
|
175
|
+
} else {
|
176
|
+
image_cropper_crop.guillotine('zoomIn');
|
177
|
+
}
|
178
|
+
}
|
179
|
+
return false;
|
180
|
+
});
|
181
|
+
|
182
|
+
|
183
|
+
}
|
184
|
+
|
185
|
+
|
186
|
+
var set_image = function(data_uri){
|
187
|
+
var i;
|
188
|
+
image_cropper_crop.guillotine('remove');
|
189
|
+
image_cropper_crop.attr('src', data_uri);
|
190
|
+
coord_x.val('');
|
191
|
+
coord_y.val('');
|
192
|
+
coord_w.val('');
|
193
|
+
coord_h.val('');
|
194
|
+
coord_z.val('');
|
195
|
+
settings['enabled'] = true;
|
196
|
+
image_cropper_crop.guillotine('enable');
|
197
|
+
image_cropper_crop_container.removeClass('disable');
|
198
|
+
|
199
|
+
i = new Image();
|
200
|
+
i.src = data_uri;
|
201
|
+
i.onload = function() {
|
202
|
+
//alert(i.naturalWidth + ' x ' + i.naturalHeight);
|
203
|
+
image_cropper_initialize_vars();
|
204
|
+
};
|
205
|
+
}
|
206
|
+
|
207
|
+
|
208
|
+
|
209
|
+
|
210
|
+
//-------------------------- Initialize
|
211
|
+
//alert(image_cropper_container.hasClass('active'));
|
212
|
+
if(!image_cropper_container.hasClass('active')){
|
213
|
+
var i;
|
214
|
+
i = new Image();
|
215
|
+
i.src = image_cropper_crop.attr('src');
|
216
|
+
i.onload = function(e) {
|
217
|
+
image_cropper_initialize_events();
|
218
|
+
image_cropper_initialize_vars();
|
219
|
+
};
|
220
|
+
}
|
221
|
+
|
222
|
+
|
223
|
+
};
|
224
|
+
|
225
|
+
}( jQuery ));
|
226
|
+
|
227
|
+
|
228
|
+
|
229
|
+
|
230
|
+
|
231
|
+
|
232
|
+
|
233
|
+
|
234
|
+
|
235
|
+
|
236
|
+
|
237
|
+
|
238
|
+
|
239
|
+
|
240
|
+
|
241
|
+
|
242
|
+
|
243
|
+
|
244
|
+
|
245
|
+
|
246
|
+
|
247
|
+
|
248
|
+
|
249
|
+
|
250
|
+
|
251
|
+
// Generated by CoffeeScript 1.8.0
|
252
|
+
|
253
|
+
/*
|
254
|
+
* jQuery Guillotine Plugin v1.3.1
|
255
|
+
* http://matiasgagliano.github.com/guillotine/
|
256
|
+
*
|
257
|
+
* Copyright 2014, Matías Gagliano.
|
258
|
+
* Dual licensed under the MIT or GPLv3 licenses.
|
259
|
+
* http://opensource.org/licenses/MIT
|
260
|
+
* http://opensource.org/licenses/GPL-3.0
|
261
|
+
*
|
262
|
+
*/
|
263
|
+
|
264
|
+
(function() {
|
265
|
+
"use strict";
|
266
|
+
var $, Guillotine, canTransform, defaults, events, getPointerPosition, hardwareAccelerate, isTouch, pluginName, scope, touchRegExp, validEvent, whitelist,
|
267
|
+
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
|
268
|
+
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
|
269
|
+
|
270
|
+
$ = jQuery;
|
271
|
+
|
272
|
+
pluginName = 'guillotine';
|
273
|
+
|
274
|
+
scope = 'guillotine';
|
275
|
+
|
276
|
+
events = {
|
277
|
+
start: "touchstart." + scope + " mousedown." + scope,
|
278
|
+
move: "touchmove." + scope + " mousemove." + scope,
|
279
|
+
stop: "touchend." + scope + " mouseup." + scope
|
280
|
+
};
|
281
|
+
|
282
|
+
defaults = {
|
283
|
+
width: 400,
|
284
|
+
height: 300,
|
285
|
+
zoomStep: 0.1,
|
286
|
+
init: null,
|
287
|
+
eventOnChange: null,
|
288
|
+
onChange: null
|
289
|
+
};
|
290
|
+
|
291
|
+
touchRegExp = /touch/i;
|
292
|
+
|
293
|
+
isTouch = function(e) {
|
294
|
+
return touchRegExp.test(e.type);
|
295
|
+
};
|
296
|
+
|
297
|
+
validEvent = function(e) {
|
298
|
+
if (isTouch(e)) {
|
299
|
+
return e.originalEvent.changedTouches.length === 1;
|
300
|
+
} else {
|
301
|
+
return e.which === 1;
|
302
|
+
}
|
303
|
+
};
|
304
|
+
|
305
|
+
getPointerPosition = function(e) {
|
306
|
+
if (isTouch(e)) {
|
307
|
+
e = e.originalEvent.touches[0];
|
308
|
+
}
|
309
|
+
return {
|
310
|
+
x: e.pageX,
|
311
|
+
y: e.pageY
|
312
|
+
};
|
313
|
+
};
|
314
|
+
|
315
|
+
canTransform = function() {
|
316
|
+
var hasTransform, helper, prefix, prefixes, prop, test, tests, value, _i, _len;
|
317
|
+
hasTransform = false;
|
318
|
+
prefixes = ['webkit', 'Moz', 'O', 'ms', 'Khtml'];
|
319
|
+
tests = {
|
320
|
+
transform: 'transform'
|
321
|
+
};
|
322
|
+
for (_i = 0, _len = prefixes.length; _i < _len; _i++) {
|
323
|
+
prefix = prefixes[_i];
|
324
|
+
tests[prefix + 'Transform'] = '-' + prefix.toLowerCase() + '-transform';
|
325
|
+
}
|
326
|
+
helper = document.createElement('img');
|
327
|
+
document.body.insertBefore(helper, null);
|
328
|
+
for (test in tests) {
|
329
|
+
prop = tests[test];
|
330
|
+
if (helper.style[test] === void 0) {
|
331
|
+
continue;
|
332
|
+
}
|
333
|
+
helper.style[test] = 'rotate(90deg)';
|
334
|
+
value = window.getComputedStyle(helper).getPropertyValue(prop);
|
335
|
+
if ((value != null) && value.length && value !== 'none') {
|
336
|
+
hasTransform = true;
|
337
|
+
break;
|
338
|
+
}
|
339
|
+
}
|
340
|
+
document.body.removeChild(helper);
|
341
|
+
canTransform = hasTransform ? (function() {
|
342
|
+
return true;
|
343
|
+
}) : (function() {
|
344
|
+
return false;
|
345
|
+
});
|
346
|
+
return canTransform();
|
347
|
+
};
|
348
|
+
|
349
|
+
hardwareAccelerate = function(el) {
|
350
|
+
return $(el).css({
|
351
|
+
'-webkit-perspective': 1000,
|
352
|
+
'perspective': 1000,
|
353
|
+
'-webkit-backface-visibility': 'hidden',
|
354
|
+
'backface-visibility': 'hidden'
|
355
|
+
});
|
356
|
+
};
|
357
|
+
|
358
|
+
Guillotine = (function() {
|
359
|
+
function Guillotine(element, options) {
|
360
|
+
this._drag = __bind(this._drag, this);
|
361
|
+
this._unbind = __bind(this._unbind, this);
|
362
|
+
this._start = __bind(this._start, this);
|
363
|
+
this.op = $.extend(true, {}, defaults, options, $(element).data(pluginName));
|
364
|
+
this.enabled = true;
|
365
|
+
this.zoomInFactor = 1 + this.op.zoomStep;
|
366
|
+
this.zoomOutFactor = 1 / this.zoomInFactor;
|
367
|
+
this.glltRatio = this.op.height / this.op.width;
|
368
|
+
this.width = this.height = this.left = this.top = this.angle = 0;
|
369
|
+
this.data = {
|
370
|
+
scale: 1,
|
371
|
+
angle: 0,
|
372
|
+
x: 0,
|
373
|
+
y: 0,
|
374
|
+
w: this.op.width,
|
375
|
+
h: this.op.height
|
376
|
+
};
|
377
|
+
this._wrap(element);
|
378
|
+
if (this.op.init != null) {
|
379
|
+
this._init();
|
380
|
+
}
|
381
|
+
if (this.width < 1 || this.height < 1) {
|
382
|
+
this._fit() && this._center();
|
383
|
+
}
|
384
|
+
hardwareAccelerate(this.$el);
|
385
|
+
this.$el.on(events.start, this._start);
|
386
|
+
}
|
387
|
+
|
388
|
+
Guillotine.prototype._wrap = function(element) {
|
389
|
+
var canvas, el, guillotine, height, paddingTop, width;
|
390
|
+
el = $(element);
|
391
|
+
if (element.tagName === 'IMG') {
|
392
|
+
if (element.naturalWidth) {
|
393
|
+
width = element.naturalWidth;
|
394
|
+
height = element.naturalHeight;
|
395
|
+
} else {
|
396
|
+
el.addClass('guillotine-sample');
|
397
|
+
width = el.width();
|
398
|
+
height = el.height();
|
399
|
+
el.removeClass('guillotine-sample');
|
400
|
+
}
|
401
|
+
} else {
|
402
|
+
width = el.width();
|
403
|
+
height = el.height();
|
404
|
+
}
|
405
|
+
this.width = width / this.op.width;
|
406
|
+
this.height = height / this.op.height;
|
407
|
+
canvas = $('<div>').addClass('guillotine-canvas');
|
408
|
+
canvas.css({
|
409
|
+
width: this.width * 100 + '%',
|
410
|
+
height: this.height * 100 + '%',
|
411
|
+
top: 0,
|
412
|
+
left: 0
|
413
|
+
});
|
414
|
+
canvas = el.wrap(canvas).parent();
|
415
|
+
paddingTop = this.op.height / this.op.width * 100 + '%';
|
416
|
+
guillotine = $('<div>').addClass('guillotine-window');
|
417
|
+
guillotine.css({
|
418
|
+
width: '100%',
|
419
|
+
height: 'auto',
|
420
|
+
'padding-top': paddingTop
|
421
|
+
});
|
422
|
+
guillotine = canvas.wrap(guillotine).parent();
|
423
|
+
this.$el = el;
|
424
|
+
this.el = el[0];
|
425
|
+
this.$canvas = canvas;
|
426
|
+
this.canvas = canvas[0];
|
427
|
+
this.$gllt = guillotine;
|
428
|
+
this.gllt = guillotine[0];
|
429
|
+
this.$document = $(element.ownerDocument);
|
430
|
+
return this.$body = $('body', this.$document);
|
431
|
+
};
|
432
|
+
|
433
|
+
Guillotine.prototype._unwrap = function() {
|
434
|
+
this.$el.removeAttr('style');
|
435
|
+
this.$el.insertBefore(this.gllt);
|
436
|
+
return this.$gllt.remove();
|
437
|
+
};
|
438
|
+
|
439
|
+
Guillotine.prototype._init = function() {
|
440
|
+
var angle, o, scale;
|
441
|
+
o = this.op.init;
|
442
|
+
if ((scale = parseFloat(o.scale))) {
|
443
|
+
this._zoom(scale);
|
444
|
+
}
|
445
|
+
if ((angle = parseInt(o.angle))) {
|
446
|
+
this._rotate(angle);
|
447
|
+
}
|
448
|
+
return this._offset(parseInt(o.x) / this.op.width || 0, parseInt(o.y) / this.op.height || 0);
|
449
|
+
};
|
450
|
+
|
451
|
+
Guillotine.prototype._start = function(e) {
|
452
|
+
if (!(this.enabled && validEvent(e))) {
|
453
|
+
return;
|
454
|
+
}
|
455
|
+
e.preventDefault();
|
456
|
+
e.stopImmediatePropagation();
|
457
|
+
this.p = getPointerPosition(e);
|
458
|
+
return this._bind();
|
459
|
+
};
|
460
|
+
|
461
|
+
Guillotine.prototype._bind = function() {
|
462
|
+
this.$body.addClass('guillotine-dragging');
|
463
|
+
this.$document.on(events.move, this._drag);
|
464
|
+
return this.$document.on(events.stop, this._unbind);
|
465
|
+
};
|
466
|
+
|
467
|
+
Guillotine.prototype._unbind = function(e) {
|
468
|
+
this.$body.removeClass('guillotine-dragging');
|
469
|
+
this.$document.off(events.move, this._drag);
|
470
|
+
this.$document.off(events.stop, this._unbind);
|
471
|
+
if (e != null) {
|
472
|
+
return this._trigger('drag');
|
473
|
+
}
|
474
|
+
};
|
475
|
+
|
476
|
+
Guillotine.prototype._trigger = function(action) {
|
477
|
+
if (this.op.eventOnChange != null) {
|
478
|
+
this.$el.trigger(this.op.eventOnChange, [this.data, action]);
|
479
|
+
}
|
480
|
+
if (typeof this.op.onChange === 'function') {
|
481
|
+
return this.op.onChange.call(this.el, this.data, action);
|
482
|
+
}
|
483
|
+
};
|
484
|
+
|
485
|
+
Guillotine.prototype._drag = function(e) {
|
486
|
+
var dx, dy, left, p, top;
|
487
|
+
e.preventDefault();
|
488
|
+
e.stopImmediatePropagation();
|
489
|
+
p = getPointerPosition(e);
|
490
|
+
dx = p.x - this.p.x;
|
491
|
+
dy = p.y - this.p.y;
|
492
|
+
this.p = p;
|
493
|
+
left = dx === 0 ? null : this.left - dx / this.gllt.clientWidth;
|
494
|
+
top = dy === 0 ? null : this.top - dy / this.gllt.clientHeight;
|
495
|
+
return this._offset(left, top);
|
496
|
+
};
|
497
|
+
|
498
|
+
Guillotine.prototype._offset = function(left, top) {
|
499
|
+
if (left || left === 0) {
|
500
|
+
if (left < 0) {
|
501
|
+
left = 0;
|
502
|
+
}
|
503
|
+
if (left > this.width - 1) {
|
504
|
+
left = this.width - 1;
|
505
|
+
}
|
506
|
+
this.canvas.style.left = (-left * 100).toFixed(2) + '%';
|
507
|
+
this.left = left;
|
508
|
+
this.data.x = Math.round(left * this.op.width);
|
509
|
+
}
|
510
|
+
if (top || top === 0) {
|
511
|
+
if (top < 0) {
|
512
|
+
top = 0;
|
513
|
+
}
|
514
|
+
if (top > this.height - 1) {
|
515
|
+
top = this.height - 1;
|
516
|
+
}
|
517
|
+
this.canvas.style.top = (-top * 100).toFixed(2) + '%';
|
518
|
+
this.top = top;
|
519
|
+
return this.data.y = Math.round(top * this.op.height);
|
520
|
+
}
|
521
|
+
};
|
522
|
+
|
523
|
+
Guillotine.prototype._zoom = function(factor) {
|
524
|
+
var h, left, top, w;
|
525
|
+
if (factor <= 0 || factor === 1) {
|
526
|
+
return;
|
527
|
+
}
|
528
|
+
w = this.width;
|
529
|
+
h = this.height;
|
530
|
+
if (w * factor > 1 && h * factor > 1) {
|
531
|
+
this.width *= factor;
|
532
|
+
this.height *= factor;
|
533
|
+
this.canvas.style.width = (this.width * 100).toFixed(2) + '%';
|
534
|
+
this.canvas.style.height = (this.height * 100).toFixed(2) + '%';
|
535
|
+
this.data.scale *= factor;
|
536
|
+
} else {
|
537
|
+
this._fit();
|
538
|
+
factor = this.width / w;
|
539
|
+
}
|
540
|
+
left = (this.left + 0.5) * factor - 0.5;
|
541
|
+
top = (this.top + 0.5) * factor - 0.5;
|
542
|
+
return this._offset(left, top);
|
543
|
+
};
|
544
|
+
|
545
|
+
Guillotine.prototype._fit = function() {
|
546
|
+
var prevWidth, relativeRatio;
|
547
|
+
prevWidth = this.width;
|
548
|
+
relativeRatio = this.height / this.width;
|
549
|
+
if (relativeRatio > 1) {
|
550
|
+
this.width = 1;
|
551
|
+
this.height = relativeRatio;
|
552
|
+
} else {
|
553
|
+
this.width = 1 / relativeRatio;
|
554
|
+
this.height = 1;
|
555
|
+
}
|
556
|
+
this.canvas.style.width = (this.width * 100).toFixed(2) + '%';
|
557
|
+
this.canvas.style.height = (this.height * 100).toFixed(2) + '%';
|
558
|
+
return this.data.scale *= this.width / prevWidth;
|
559
|
+
};
|
560
|
+
|
561
|
+
Guillotine.prototype._center = function() {
|
562
|
+
return this._offset((this.width - 1) / 2, (this.height - 1) / 2);
|
563
|
+
};
|
564
|
+
|
565
|
+
Guillotine.prototype._rotate = function(angle) {
|
566
|
+
var canvasRatio, glltRatio, h, w, _ref, _ref1, _ref2;
|
567
|
+
if (!canTransform()) {
|
568
|
+
return;
|
569
|
+
}
|
570
|
+
if (!(angle !== 0 && angle % 90 === 0)) {
|
571
|
+
return;
|
572
|
+
}
|
573
|
+
this.angle = (this.angle + angle) % 360;
|
574
|
+
if (this.angle < 0) {
|
575
|
+
this.angle = 360 + this.angle;
|
576
|
+
}
|
577
|
+
if (angle % 180 !== 0) {
|
578
|
+
glltRatio = this.op.height / this.op.width;
|
579
|
+
_ref = [this.height * glltRatio, this.width / glltRatio], this.width = _ref[0], this.height = _ref[1];
|
580
|
+
if (this.width >= 1 && this.height >= 1) {
|
581
|
+
this.canvas.style.width = this.width * 100 + '%';
|
582
|
+
this.canvas.style.height = this.height * 100 + '%';
|
583
|
+
} else {
|
584
|
+
this._fit();
|
585
|
+
}
|
586
|
+
}
|
587
|
+
_ref1 = [1, 1], w = _ref1[0], h = _ref1[1];
|
588
|
+
if (this.angle % 180 !== 0) {
|
589
|
+
canvasRatio = this.height / this.width * glltRatio;
|
590
|
+
_ref2 = [canvasRatio, 1 / canvasRatio], w = _ref2[0], h = _ref2[1];
|
591
|
+
}
|
592
|
+
this.el.style.width = w * 100 + '%';
|
593
|
+
this.el.style.height = h * 100 + '%';
|
594
|
+
this.el.style.left = (1 - w) / 2 * 100 + '%';
|
595
|
+
this.el.style.top = (1 - h) / 2 * 100 + '%';
|
596
|
+
this.$el.css({
|
597
|
+
transform: "rotate(" + this.angle + "deg)"
|
598
|
+
});
|
599
|
+
this._center();
|
600
|
+
return this.data.angle = this.angle;
|
601
|
+
};
|
602
|
+
|
603
|
+
Guillotine.prototype.rotateLeft = function() {
|
604
|
+
return this.enabled && (this._rotate(-90), this._trigger('rotateLeft'));
|
605
|
+
};
|
606
|
+
|
607
|
+
Guillotine.prototype.rotateRight = function() {
|
608
|
+
return this.enabled && (this._rotate(90), this._trigger('rotateRight'));
|
609
|
+
};
|
610
|
+
|
611
|
+
Guillotine.prototype.center = function() {
|
612
|
+
return this.enabled && (this._center(), this._trigger('center'));
|
613
|
+
};
|
614
|
+
|
615
|
+
Guillotine.prototype.fit = function() {
|
616
|
+
return this.enabled && (this._fit(), this._center(), this._trigger('fit'));
|
617
|
+
};
|
618
|
+
|
619
|
+
Guillotine.prototype.zoomIn = function() {
|
620
|
+
return this.enabled && (this._zoom(this.zoomInFactor), this._trigger('zoomIn'));
|
621
|
+
};
|
622
|
+
|
623
|
+
Guillotine.prototype.zoomOut = function() {
|
624
|
+
return this.enabled && (this._zoom(this.zoomOutFactor), this._trigger('zoomOut'));
|
625
|
+
};
|
626
|
+
|
627
|
+
Guillotine.prototype.getData = function() {
|
628
|
+
return this.data;
|
629
|
+
};
|
630
|
+
|
631
|
+
Guillotine.prototype.enable = function() {
|
632
|
+
return this.enabled = true;
|
633
|
+
};
|
634
|
+
|
635
|
+
Guillotine.prototype.disable = function() {
|
636
|
+
return this.enabled = false;
|
637
|
+
};
|
638
|
+
|
639
|
+
Guillotine.prototype.remove = function() {
|
640
|
+
this._unbind();
|
641
|
+
this._unwrap();
|
642
|
+
this.disable();
|
643
|
+
this.$el.off(events.start, this._start);
|
644
|
+
return this.$el.removeData(pluginName + 'Instance');
|
645
|
+
};
|
646
|
+
|
647
|
+
return Guillotine;
|
648
|
+
|
649
|
+
})();
|
650
|
+
|
651
|
+
whitelist = ['rotateLeft', 'rotateRight', 'center', 'fit', 'zoomIn', 'zoomOut', 'instance', 'getData', 'enable', 'disable', 'remove'];
|
652
|
+
|
653
|
+
$.fn[pluginName] = function(options) {
|
654
|
+
if (typeof options !== 'string') {
|
655
|
+
return this.each(function() {
|
656
|
+
var guillotine;
|
657
|
+
if (!$.data(this, pluginName + 'Instance')) {
|
658
|
+
guillotine = new Guillotine(this, options);
|
659
|
+
return $.data(this, pluginName + 'Instance', guillotine);
|
660
|
+
}
|
661
|
+
});
|
662
|
+
} else if (__indexOf.call(whitelist, options) >= 0) {
|
663
|
+
if (options === 'instance') {
|
664
|
+
return $.data(this[0], pluginName + 'Instance');
|
665
|
+
}
|
666
|
+
if (options === 'getData') {
|
667
|
+
return $.data(this[0], pluginName + 'Instance')[options]();
|
668
|
+
}
|
669
|
+
return this.each(function() {
|
670
|
+
var guillotine;
|
671
|
+
guillotine = $.data(this, pluginName + 'Instance');
|
672
|
+
if (guillotine) {
|
673
|
+
return guillotine[options]();
|
674
|
+
}
|
675
|
+
});
|
676
|
+
}
|
677
|
+
};
|
678
|
+
|
679
|
+
}).call(this);
|
680
|
+
|
681
|
+
|
682
|
+
|
683
|
+
|
684
|
+
|
685
|
+
|
686
|
+
|
687
|
+
|
688
|
+
|