├── .gitignore ├── images ├── demo.gif └── logo.png ├── tests ├── fixtures │ ├── 250x350.gif │ └── 350x250.gif ├── index.html └── runner.js ├── .travis.yml ├── bower.json ├── drag-n-crop.jquery.json ├── LICENSE ├── package.json ├── karma.conf.js ├── jquery.drag-n-crop.css ├── README.md ├── jquery.drag-n-crop.js └── jquery.drag-n-crop.amd.js /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | node_modules/ 3 | _site -------------------------------------------------------------------------------- /images/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukaszfiszer/drag-n-crop/HEAD/images/demo.gif -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukaszfiszer/drag-n-crop/HEAD/images/logo.png -------------------------------------------------------------------------------- /tests/fixtures/250x350.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukaszfiszer/drag-n-crop/HEAD/tests/fixtures/250x350.gif -------------------------------------------------------------------------------- /tests/fixtures/350x250.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukaszfiszer/drag-n-crop/HEAD/tests/fixtures/350x250.gif -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | before_install: # make "bower install" availabke 5 | - npm install bower -g 6 | before_script: # start xvfb to use GUI browsers 7 | - export DISPLAY=:99.0 8 | - sh -e /etc/init.d/xvfb start -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery.drag-n-crop", 3 | "version": "0.1.1", 4 | "main": ["jquery.drag-n-crop.js", "jquery.drag-n-crop.css"], 5 | "keywords": [ 6 | "jquery", 7 | "drag", 8 | "crop", 9 | "cropping", 10 | "facebook" 11 | ], 12 | "ignore": [ 13 | "**/.*", 14 | "*.json", 15 | "karma.conf.js", 16 | "node_modules", 17 | "bower_components", 18 | "images", 19 | "tests" 20 | ], 21 | "dependencies": { 22 | "jquery": "~1.9.1", 23 | "jquery-ui": "~1.10.1", 24 | "imagesloaded": "~3.1.4" 25 | }, 26 | "devDependencies": { 27 | "mocha": "~1.8.1", 28 | "chai": "~1.5.0" 29 | } 30 | } 31 | 32 | 33 | -------------------------------------------------------------------------------- /drag-n-crop.jquery.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "drag-n-crop", 3 | "title": "jQuery Drag-n-Crop", 4 | "description": "jQuery plugin for cropping images like Facebook cover photo", 5 | "keywords": [ 6 | "drag", 7 | "crop", 8 | "cropping", 9 | "facebook" 10 | ], 11 | "version": "0.1.1", 12 | "author": { 13 | "name": "Lukasz Fiszer", 14 | "url": "http://github.com/lukaszfiszer" 15 | }, 16 | "licenses": [ 17 | { 18 | "type": "MIT", 19 | "url": "https://raw2.github.com/lukaszfiszer/drag-n-crop/master/LICENSE" 20 | } 21 | ], 22 | "bugs": "https://github.com/lukaszfiszer/drag-n-crop/issues", 23 | "homepage": "http://lukaszfiszer.github.io/drag-n-crop/", 24 | "docs": "http://lukaszfiszer.github.io/drag-n-crop/", 25 | "download": "https://github.com/lukaszfiszer/drag-n-crop/", 26 | "dependencies": { 27 | "jquery": ">=1.9", 28 | "jquery-ui": ">=1.9" 29 | } 30 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Lukasz Fiszer 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery.drag-n-crop", 3 | "version": "0.1.1", 4 | "description": "jQuery drag'n'crop plugin", 5 | "main": "jquery.drag-n-crop.js", 6 | "directories": { 7 | "example": "examples", 8 | "test": "tests" 9 | }, 10 | "keywords": [ 11 | "jquery", 12 | "drag", 13 | "crop", 14 | "cropping", 15 | "facebook" 16 | ], 17 | "dependencies": {}, 18 | "devDependencies": { 19 | "karma-mocha": "~0.1.1", 20 | "karma": "~0.10.9" 21 | }, 22 | "scripts": { 23 | "postinstall": "bower install", 24 | "test": "./node_modules/.bin/karma start --single-run" 25 | }, 26 | "author": { 27 | "name": "Lukasz Fiszer", 28 | "url": "http://github.com/lukaszfiszer" 29 | }, 30 | "contributors": [], 31 | "repository": { 32 | "type": "git", 33 | "url": "git://github.com/lukaszfiszer/drag-n-crop.git" 34 | }, 35 | "homepage": "http://lukaszfiszer.github.io/drag-n-crop/", 36 | "bugs": { 37 | "url": "https://github.com/lukaszfiszer/drag-n-crop/issues" 38 | }, 39 | "licenses": [ 40 | { 41 | "type": "MIT", 42 | "url": "https://raw2.github.com/lukaszfiszer/drag-n-crop/master/LICENSE" 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mocha Tests 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 25 | 32 |
33 |
34 | 35 | 36 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Sat Feb 15 2014 14:29:17 GMT+0100 (CET) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path, that will be used to resolve files and exclude 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | frameworks: ['mocha'], 13 | 14 | 15 | // list of files / patterns to load in the browser 16 | files: [ 17 | 'bower_components/jquery/jquery.js', 18 | 'bower_components/jquery-ui/ui/jquery.ui.core.js', 19 | 'bower_components/jquery-ui/ui/jquery.ui.widget.js', 20 | 'bower_components/jquery-ui/ui/jquery.ui.mouse.js', 21 | 'bower_components/jquery-ui/ui/jquery.ui.draggable.js', 22 | 'bower_components/eventEmitter/EventEmitter.js', 23 | 'bower_components/eventie/eventie.js', 24 | 'bower_components/imagesloaded/imagesloaded.js', 25 | 'bower_components/chai/chai.js', 26 | 'jquery.drag-n-crop.js', 27 | 'jquery.drag-n-crop.css', 28 | 'tests/*.js', 29 | { 30 | pattern: 'tests/fixtures/*', 31 | included: false, 32 | served: true 33 | } 34 | ], 35 | 36 | 37 | // list of files to exclude 38 | exclude: [ 39 | 40 | ], 41 | 42 | 43 | // test results reporter to use 44 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 45 | reporters: ['progress'], 46 | 47 | 48 | // web server port 49 | port: 9876, 50 | 51 | 52 | // enable / disable colors in the output (reporters and logs) 53 | colors: true, 54 | 55 | 56 | // level of logging 57 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 58 | logLevel: config.LOG_INFO, 59 | 60 | 61 | // enable / disable watching file and executing tests whenever any file changes 62 | autoWatch: true, 63 | 64 | 65 | // Start these browsers, currently available: 66 | // - Chrome 67 | // - ChromeCanary 68 | // - Firefox 69 | // - Opera (has to be installed with `npm install karma-opera-launcher`) 70 | // - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`) 71 | // - PhantomJS 72 | // - IE (only Windows; has to be installed with `npm install karma-ie-launcher`) 73 | browsers: ['PhantomJS'], 74 | 75 | 76 | // If browser does not capture in given timeout [ms], kill it 77 | captureTimeout: 60000, 78 | 79 | 80 | // Continuous Integration mode 81 | // if true, it capture browsers, run tests and exit 82 | singleRun: false 83 | }); 84 | }; 85 | -------------------------------------------------------------------------------- /jquery.drag-n-crop.css: -------------------------------------------------------------------------------- 1 | /* 2 | DEFAULT CLASSES 3 | */ 4 | 5 | .dragncrop { 6 | position: relative; 7 | overflow: hidden; 8 | } 9 | 10 | .dragncrop img { 11 | display: block; 12 | } 13 | 14 | /* TODO: cross-browser cursors */ 15 | .ui-draggable, 16 | .dragncrop-overlay { 17 | cursor:grab; 18 | cursor:-moz-grab; 19 | cursor:-webkit-grab; 20 | } 21 | 22 | .ui-draggable-dragging { 23 | cursor:grabbing; 24 | cursor:-moz-grabbing; 25 | cursor:-webkit-grabbing; 26 | } 27 | 28 | .dragncrop-horizontal { 29 | max-width: none; 30 | height: 100%; 31 | } 32 | 33 | .dragncrop-vertical { 34 | width: 100%; 35 | max-height: none; 36 | } 37 | 38 | .dragncrop-containment { 39 | position: absolute; 40 | } 41 | 42 | /* 43 | OPTIONAL CLASSES 44 | */ 45 | 46 | /* Overlow */ 47 | .dragncrop-dragging.dragncrop-overflow { 48 | overflow: visible; 49 | } 50 | 51 | /* Overlay */ 52 | .dragncrop-overlay { 53 | position: absolute; 54 | opacity: 0.5; 55 | top: 0; 56 | left: 0; 57 | bottom: 0; 58 | right: 0; 59 | border-style: solid; 60 | border-color: black; 61 | border-width: 0; 62 | } 63 | 64 | .dragncrop-dragging .dragncrop-overlay { 65 | z-index: 1; 66 | } 67 | 68 | /* Instruction */ 69 | .dragncrop-instruction { 70 | position: absolute; 71 | top: 40%; 72 | opacity: 0.7; 73 | background: black; 74 | color: #DDD; 75 | text-align: center; 76 | border-radius: 6px; 77 | line-height: 1; 78 | box-sizing: border-box; 79 | font-family: sans-serif; 80 | z-index: 1; 81 | font-size: 0.875em; 82 | right: 15%; 83 | left: 15%; 84 | padding: 10px 12px; 85 | } 86 | 87 | .dragncrop-instruction-text { 88 | color: #DDD; 89 | text-align: center; 90 | line-height: 1; 91 | font-family: sans-serif; 92 | font-size: 0.875em; 93 | background-repeat: no-repeat; 94 | background-position: 0 50%; 95 | display: block; 96 | padding-left: 20px 97 | } 98 | 99 | .dragncrop-horizontal + .dragncrop-instruction .dragncrop-instruction-text { 100 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAqklEQVQ4T2NkoDJgpLJ5DKMGUh6i8DD8//+/ANC4XCCezMjI+IEYo7HpARsIldgNZJoAsSPQwANEGugAVLcfiM8BsTPIIYxohoHMWQDED4gxEKhGAYgToGohhgINBLnMhUgDCCnbCzLwFFCVKSGVRMrvg3l5D1CDMVTTQhK9HA/Vdx5IOyFHCsxQciIFYhgoUmBegUZOHpA/icRkg6JnNC8TmTjwKBv8YQgAitFBotB3lzcAAAAASUVORK5CYII='); 101 | } 102 | 103 | .dragncrop-vertical + .dragncrop-instruction .dragncrop-instruction-text { 104 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAv0lEQVQ4T2NkQAL////vAXKDgDiQkZHxIrIcsWxGmEKgYXOA7GQo/z2QdiTHULCBaIbB7CDLUEagYQZAE84CMRMWb+0FutKFWO+C1MFcaAdkGwFxP1TzOSBdDMT3gQY+JNlAqLcdgPR+qGaSXQazFDlSRg0kLXZHwxCc/QZxsgG6DpT1DIF4AjS2QHkblPUekJz1oIUDKO/Ccw1S3iU5C+Irvj4ADXYgtUzEVcCSZRi8+IJ5EVoFgEptkl0GMwMA7BJsFXeMXW4AAAAASUVORK5CYII='); 105 | } 106 | 107 | .dragncrop:hover .dragncrop-instruction-autohide, 108 | .dragncrop-dragging .dragncrop-instruction-autohide { 109 | display: none; 110 | } 111 | -------------------------------------------------------------------------------- /tests/runner.js: -------------------------------------------------------------------------------- 1 | /*global describe:false, it:false, assert:false, 2 | beforeEach:false, afterEach:false, before:false, after:false */ 3 | 4 | describe('jquery.dragncrop.js', function() { 5 | 6 | var sandbox, imgv, imgh, assert = chai.assert; 7 | 8 | before(function() { 9 | sandbox = $('
').appendTo($('body')) 10 | }) 11 | 12 | beforeEach(function() { 13 | sandbox.append($('
')); 14 | sandbox.append($('
')); 15 | }); 16 | 17 | afterEach(function() { 18 | sandbox.html(''); 19 | }); 20 | 21 | it('is a jquery plugin', function() { 22 | assert.isFunction($.fn.dragncrop); 23 | }); 24 | 25 | it('returns the object', function() { 26 | var el = $('#imgv'); 27 | assert.equal(el, el.dragncrop()); 28 | }); 29 | 30 | describe('after initialization', function() { 31 | 32 | it('is an instance of jquery.ui.draggable', function(done) { 33 | var el = $('#imgv').dragncrop(); 34 | setTimeout(function() { 35 | // if is not an instance, will throw error 36 | el.draggable('widget'); 37 | done() 38 | }, 50); 39 | }); 40 | 41 | it('adds class to the parent container if it is not present', function() { 42 | var el = $('#imgv').dragncrop(); 43 | assert.isTrue(el.parent().hasClass('dragncrop')); 44 | }); 45 | 46 | it('applies "dragncrop-horizontal" class on the element if horizontal', function(done) { 47 | $('#imgh').dragncrop(); 48 | setTimeout(function() { 49 | assert.isTrue($('#imgh').hasClass('dragncrop-horizontal')); 50 | done(); 51 | }, 50); 52 | }); 53 | 54 | it('applies "dragncrop-horizontal" class on the element if vertical', function(done) { 55 | $('#imgv').dragncrop(); 56 | setTimeout(function() { 57 | assert.isTrue($('#imgv').hasClass('dragncrop-vertical')); 58 | done(); 59 | }, 50); 60 | }); 61 | 62 | it('applies "dragncrop-vertical" class on the element in function of proportion', function(done) { 63 | $('#imgv').dragncrop(); 64 | $('#sandbox img').imagesLoaded(function() { 65 | assert.isTrue($('#imgv').hasClass('dragncrop-vertical')); 66 | done(); 67 | }); 68 | }); 69 | 70 | it('resizes vertical img to container', function(done) { 71 | $('#imgv').dragncrop(); 72 | $('#sandbox img').imagesLoaded(function() { 73 | assert.equal($('#imgv').width(), 200); 74 | done(); 75 | }); 76 | }); 77 | 78 | it('resizes horizontal img to container', function(done) { 79 | $('#imgh').dragncrop(); 80 | $('#sandbox img').imagesLoaded(function() { 81 | assert.equal($('#imgh').height(), 200); 82 | done(); 83 | }); 84 | }); 85 | 86 | }); 87 | 88 | 89 | describe('getPosition method', function() { 90 | 91 | it('returns a dictionary with two types of coordinates for horizontal images', function(done) { 92 | var el = $('#imgh').dragncrop({centered: true}); 93 | setTimeout(function() { 94 | var pos = el.dragncrop('getPosition'); 95 | assert.equal(pos.offset[0], '0.5'); 96 | assert.equal(pos.dimension[0].toPrecision(2), '0.14'); 97 | done(); 98 | }, 50); 99 | }); 100 | 101 | it('returns a dictionary with two types of coordinates for vertical images', function(done) { 102 | var el = $('#imgv').dragncrop({centered: true}); 103 | el.imagesLoaded(function() { 104 | var pos = el.dragncrop('getPosition'); 105 | assert.equal(pos.offset[1], '0.5'); 106 | assert.equal(pos.dimension[1].toPrecision(2), '0.14'); 107 | done(); 108 | }); 109 | }); 110 | 111 | }); 112 | 113 | describe('destroy method', function() { 114 | 115 | it('removes draggable widget', function(done) { 116 | var err = null; 117 | var el = $('#imgv').dragncrop(); 118 | 119 | el.imagesLoaded(function() { 120 | el.dragncrop('destroy'); 121 | try { 122 | el.draggable('widget'); 123 | } 124 | catch (e){ 125 | err = e; 126 | } 127 | assert.isNotNull(err, 'Catched error'); 128 | done(); 129 | }); 130 | }); 131 | 132 | it('returns element inner html to the initial state', function(done) { 133 | var err = null; 134 | var el = $('#imgv').dragncrop(); 135 | var before = el.parent().children(); 136 | el.imagesLoaded(function() { 137 | var after = el.dragncrop('destroy').parent().children(); 138 | assert.equal(before.length, after.length); 139 | done(); 140 | }); 141 | }); 142 | 143 | it('cleans up classes from container', function(done) { 144 | var err = null; 145 | var el = $('#imgv').dragncrop(); 146 | var before = el.parent().attr('class'); 147 | el.imagesLoaded(function() { 148 | var after = el.dragncrop('destroy').parent().attr('class'); 149 | assert.equal(before, after); 150 | done(); 151 | }); 152 | }); 153 | 154 | it('cleans up classes from element', function(done) { 155 | var err = null; 156 | var el = $('#imgv').dragncrop(); 157 | var before = el.attr('class') || ''; 158 | el.imagesLoaded(function() { 159 | var after = el.dragncrop('destroy').attr('class'); 160 | assert.equal(before, after); 161 | done(); 162 | }); 163 | }); 164 | 165 | }); 166 | 167 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![jquery.drag-n-crop](images/logo.png "jQuery drag'n'crop logo") 2 | 3 | [![Build Status](https://travis-ci.org/lukaszfiszer/drag-n-crop.png?branch=master)](https://travis-ci.org/lukaszfiszer/drag-n-crop) 4 | 5 | A jQuery plugin for croping images by dragging, inspired by Facebook cover photo. 6 | 7 | It aims to be minimalistic and very easy to use for the end-user. It allows to crop the image only in one dimension (no zooming!). A typical usecase would be to crop rectangular images into squares. If you search for a more advanced croping plugin, there are some [other](https://github.com/tapmodo/Jcrop) [plugins](http://odyniec.net/projects/imgareaselect/) available. 8 | 9 | 10 | Quick demo! 11 | ------------- 12 | ![jquery.drag-n-crop](images/demo.gif "jQuery drag'n'crop logo") 13 | 14 | 15 | Dependencies 16 | ------------- 17 | 18 | * jQuery 19 | * jQuery UI draggable 20 | * [imagesloaded](/desandro/imagesloaded) 21 | 22 | 23 | Installation 24 | ----- 25 | 26 | 1.Install the plugin with bower `bower install jquery.drag-n-crop` or download it from this repo. 27 | 28 | 2.Include drag'n'crop JS and CSS files (+ and its dependencies, if you dont have them already) on your site. 29 | ``` 30 | 31 | (...) 32 | 33 | ``` 34 | 3.Wrap the photo you want to crop in a element with target width and height 35 | 36 | ``` 37 |
38 | ``` 39 | 40 | 4.Initialize the plugin 41 | ``` 42 | $('#photo').dragncrop(); 43 | ``` 44 | 45 | 46 | API 47 | --- 48 | 49 | *jQuery.drag'n'crop* is build using [jQuery widget factory](http://api.jqueryui.com/jQuery.widget/), providing a standard way of interacting with the plugin. It inherits all default [options, events and methods](http://api.jqueryui.com/jQuery.widget/#jQuery-Widget2) but also provides some custom ones, described below: 50 | 51 | ### Init options 52 | 53 | You can customize behaviour of the plugin by passing an option object on initialization, example: 54 | 55 | ``` 56 | $('#photo').dragncrop({ 57 | centered: true, 58 | overflow: true 59 | }); 60 | ``` 61 | 62 | Here's the complete list of available options 63 | 64 | | Options | Description | Default | 65 | | -----------------------|---------------------------------------------------|-------| 66 | | position | set initial position of the image (see [position](#position-object)) | undefined | 67 | | centered | center image in the container on init | false | 68 | | overflow | show image oveflow when dragging | false | 69 | | overlay | show oveflow with a semi-transparent overlay when dragging | false | 70 | | instruction | show text instruction on image | false | 71 | | instructionText | customize instruction text | 'Drag to crop' | 72 | | instructionHideOnHover | hide instruction when hovering over image | true | 73 | 74 | ### Position object 75 | 76 | *drag'n'crop* provides a *position* object describing the coordinates of the image inside the container with the following structure 77 | 78 | ``` 79 | { 80 | offset : [x, y], 81 | dimension : [x, y] 82 | } 83 | ``` 84 | 85 | * **offset** represents the relative position of the image inside the container and ranges from 0-1. Example `offset: [1,0]` with an image 400x200px and container 200x200: the image is dragged to its right edge. 86 | * **dimension** represents the offset proportional to the image dimensions. Example: `dimension: [0.5, 0]` with an image of 600x200px and container 200x200: image is dragged by 300px (=600x0.5) pixels to the right. 87 | 88 | Because *jquery.drag'n'crop* crops only in one dimension, only `x` or `y` changes for a given image - the second is always expected to be 0. 89 | 90 | If you set the position (either via `position` option on init, or with `move` method), you must provide the coordinates in one of two formats. Example: 91 | 92 | ``` 93 | $('#horizontal-image').dragncrop({ 94 | position: {offset: [1,0]} // position image on the right 95 | }); 96 | ``` 97 | 98 | ### Events 99 | 100 | Event listener function are passed to the constructor in the same way as options. They are invoke every time an event is triggered. Example: 101 | 102 | ``` 103 | $('#photo').dragncrop({ 104 | start: function() { 105 | console.log('Dragging started!!') 106 | } 107 | }); 108 | ``` 109 | 110 | Here's the complete list of events emitted by the plugin: 111 | 112 | | Event | Description | Arguments | 113 | | -----------------------|---------------------------------------------------------|-----------------| 114 | | start | when dragging of the image started | event, position | 115 | | drag | when dragging occures | event, position | 116 | | stop | when dragging finished (user released the mouse button) | event, position | 117 | 118 | 119 | ### Instance methods 120 | 121 | Instance methods are invoked by calling the plugin function with the method name provided as a first argument followed by other arguments: 122 | 123 | ``` 124 | $("#photo").dragncrop('move', {offset:{[0,0.2]}); 125 | ``` 126 | 127 | Available methods for drag'n'crop plugin: 128 | 129 | | Method Name | Description | Arguments | Returns | 130 | | ----------------|---------------------------------------------------------|-----------|----------- 131 | | move | Move image programatically to the given position | position | undefined 132 | | getPosition | retrieves the current position of the image | none | position 133 | | destroy | Destroy and cleanup the plugin. | none | undefined 134 | 135 | 136 | 137 | Examples 138 | ------------- 139 | See [http://lukaszfiszer.github.io/drag-n-crop/examples.html](http://lukaszfiszer.github.io/drag-n-crop/examples.html) 140 | 141 | 142 | Release History 143 | --------------- 144 | 0.1.1 - fixing manifest files 145 | 0.1.0 - initial release 146 | 147 | -------------------------------------------------------------------------------- /jquery.drag-n-crop.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jquery.drag-n-crop 3 | * https://github.com/lukaszfiszer/drag-n-crop 4 | * 5 | * Copyright (c) 2013 Lukasz Fiszer 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | ;(function ( $, window, document, undefined ) { 10 | 11 | $.widget( "lukaszfiszer.dragncrop" , { 12 | 13 | classes: { 14 | // Basic classes 15 | container: 'dragncrop', 16 | containerActive: 'dragncrop-dragging', 17 | containment: 'dragncrop-containment', 18 | horizontal: 'dragncrop-horizontal', 19 | vertical: 'dragncrop-vertical', 20 | 21 | // Options' classes 22 | overflow: 'dragncrop-overflow', 23 | overlay: 'dragncrop-overlay', 24 | instruction: 'dragncrop-instruction', 25 | instructionHide: 'dragncrop-instruction-autohide', 26 | instructionText: 'dragncrop-instruction-text' 27 | }, 28 | 29 | options: { 30 | // Initial position 31 | position: {}, 32 | centered: false, 33 | 34 | // Simple overflow: 35 | overflow: false, 36 | 37 | // Overflaid overflow 38 | overlay: false, 39 | 40 | // Drag instruction 41 | instruction: false, 42 | instructionText: 'Drag to crop', 43 | instructionHideOnHover: true, 44 | }, 45 | 46 | move: function ( position ) { 47 | 48 | var left, top, x, y; 49 | 50 | if( !position ){ 51 | throw new Error('position object must be provided'); 52 | } 53 | 54 | if (position.offset === undefined && position.dimension === undefined ) { 55 | throw new Error('position object must contain "left" or "top" props'); 56 | } 57 | 58 | if( this.axis === 'x' && position.offset ){ 59 | left = -position.offset[0] * this.offsetX; 60 | this.element.css('left', left); 61 | this.element.css('top', 0); 62 | } else 63 | if( this.axis === 'x' && position.dimension ){ 64 | left = -position.dimension[0] * this.width; 65 | this.element.css('left', left); 66 | this.element.css('top', 0); 67 | } else 68 | 69 | if( this.axis === 'y' && position.offset ){ 70 | top = -position.offset[1] * this.offsetY; 71 | this.element.css('left', 0); 72 | this.element.css('top', top); 73 | } else 74 | if( this.axis === 'y' && position.dimension ){ 75 | top = -position.dimension[1] * this.height; 76 | this.element.css('left', 0); 77 | this.element.css('top', top); 78 | } 79 | 80 | this._setPosition( { left: left, top: top }); 81 | 82 | }, 83 | 84 | _create: function () { 85 | 86 | this.container = $(this.element.parent()); 87 | this.container.addClass(this.classes.container); 88 | 89 | if( this.options.overflow || this.options.overlay){ 90 | $(this.container).addClass(this.classes.overflow); 91 | } 92 | 93 | var dfd = this.element.imagesLoaded(); 94 | var self = this; 95 | 96 | dfd.done(function(){ 97 | if(self._setAxis.call(self)){ 98 | self._getDimensions.call(self); 99 | self._makeDraggable.call(self); 100 | if (self.options.loaded) { 101 | self.options.loaded(); 102 | } 103 | } 104 | } ); 105 | 106 | }, 107 | 108 | _destroy: function() { 109 | this.draggable && this.draggable.draggable('destroy'); 110 | this.container.find('.' + this.classes.containment + ',' + 111 | '.' + this.classes.overlay + ',' + 112 | '.' + this.classes.instruction).remove(); 113 | this.element.removeClass(this.classes.horizontal) 114 | .removeClass(this.classes.vertical); 115 | }, 116 | 117 | getPosition: function() { 118 | return { 119 | offset : [ 120 | ( -this.position.left / this.offsetX) || 0, 121 | ( -this.position.top / this.offsetY) || 0 122 | ], 123 | dimension : [ 124 | ( -this.position.left / this.width) || 0, 125 | ( -this.position.top / this.height) || 0 126 | ] 127 | }; 128 | 129 | }, 130 | 131 | _getDimensions: function() { 132 | 133 | this.width = this.element.width(); 134 | this.height = this.element.height(); 135 | 136 | this.containerWidth = this.container.width(); 137 | this.containerHeight = this.container.height(); 138 | 139 | this.offsetX = this.width - this.containerWidth; 140 | this.offsetY = this.height - this.containerHeight; 141 | 142 | }, 143 | 144 | _setAxis: function() { 145 | 146 | this.photoRatio = this.element.width() / this.element.height(); 147 | this.containerRatio = this.container.width() / this.container.height(); 148 | 149 | if (this.photoRatio > this.containerRatio) { 150 | 151 | this.axis = 'x'; 152 | $(this.element).addClass(this.classes.horizontal); 153 | return true; 154 | 155 | } else if (this.photoRatio < this.containerRatio) { 156 | 157 | this.axis = 'y'; 158 | $(this.element).addClass(this.classes.vertical); 159 | return true; 160 | 161 | }else{ 162 | 163 | return false; 164 | 165 | } 166 | 167 | }, 168 | 169 | _setPosition: function( obj ) { 170 | this.position = obj; 171 | }, 172 | 173 | _makeDraggable : function () { 174 | 175 | var axis = this.axis; 176 | var position = this.options.position; 177 | var containement = this._insertContainment(); 178 | 179 | var draggable = this.draggable = this.element.draggable({ 180 | axis: axis, 181 | containment: containement 182 | }); 183 | 184 | this._on({ 185 | dragstart: function (event, ui) { 186 | this._dragStart( event , ui ); 187 | this.container.addClass( this.classes.containerActive ); 188 | }, 189 | drag: function( event, ui ){ 190 | this._dragging( event , ui ); 191 | if (this.options.overlay) { 192 | this._adaptOverlay( ui ); 193 | } 194 | }, 195 | dragstop: function( event, ui ){ 196 | this._dragStop( event , ui ); 197 | this.container.removeClass( this.classes.containerActive ); 198 | } 199 | }); 200 | 201 | if(this.options.overlay){ 202 | this._insertOverlay(); 203 | } 204 | 205 | if(this.options.instruction){ 206 | this._insertInstruction(); 207 | } 208 | 209 | if(this.options.centered){ 210 | position = { offset : [0.5, 0.5] }; 211 | } 212 | 213 | if( position && ( position.offset || position.coordinates)) { 214 | this.move(position); 215 | }else{ 216 | this._setPosition({ left: 0, top: 0 }); 217 | } 218 | 219 | }, 220 | 221 | _dragStart: function( event, ui ) { 222 | this._setPosition(ui.position); 223 | this._trigger('start', event, this.getPosition() ); 224 | }, 225 | 226 | _dragging: function( event, ui ) { 227 | this._setPosition(ui.position); 228 | this._trigger('drag', event, this.getPosition() ); 229 | }, 230 | 231 | _dragStop: function( event, ui ) { 232 | this._setPosition(ui.position); 233 | this._trigger('stop', event, this.getPosition() ); 234 | }, 235 | 236 | 237 | _insertOverlay: function(){ 238 | 239 | var overlay = $('
').addClass(this.classes.overlay); 240 | this.overlay = overlay.insertBefore(this.element); 241 | return this.overlay; 242 | 243 | }, 244 | 245 | _adaptOverlay: function( ui ) { 246 | 247 | if ( this.axis === 'x' ) { 248 | 249 | this.overlay.css('left', ui.position.left) 250 | .css('border-left-width', -ui.position.left) 251 | .css('right', -ui.position.left - this.offsetX) 252 | .css('border-right-width', ui.position.left + this.offsetX); 253 | 254 | } else if ( this.axis === 'y' ){ 255 | 256 | this.overlay.css('top', ui.position.top) 257 | .css('border-top-width', -ui.position.top) 258 | .css('bottom', -ui.position.top - this.offsetY ) 259 | .css('border-bottom-width', ui.position.top + this.offsetY ); 260 | } 261 | 262 | }, 263 | 264 | _insertContainment: function() { 265 | 266 | var top = - this.offsetY; 267 | var bottom = - this.offsetY; 268 | var left = - this.offsetX; 269 | var right = - this.offsetX; 270 | 271 | var containment = $('
').addClass(this.classes.containment) 272 | .css('top',top).css('bottom', bottom) 273 | .css('left',left).css('right',right) 274 | .css('position','absolute'); 275 | 276 | this.containment = containment.insertBefore(this.element); 277 | return this.containment; 278 | 279 | }, 280 | 281 | _insertInstruction: function() { 282 | this.instruction = $('
').addClass(this.classes.instruction); 283 | if(this.options.instructionHideOnHover){ 284 | this.instruction.addClass(this.classes.instructionHide); 285 | } 286 | this.instruction.append( 287 | $('').text(this.options.instructionText) 288 | .addClass(this.classes.instructionText) 289 | ); 290 | this.instruction.insertAfter(this.element); 291 | return this.instruction; 292 | }, 293 | 294 | }); 295 | 296 | })( jQuery, window, document ); 297 | -------------------------------------------------------------------------------- /jquery.drag-n-crop.amd.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jquery.drag-n-crop 3 | * https://github.com/lukaszfiszer/drag-n-crop 4 | * 5 | * Copyright (c) 2013 Lukasz Fiszer 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | define(['jquery', 10 | 'jqueryui/draggable', 11 | 'jqueryui/core', 12 | 'jqueryui/mouse', 13 | 'jqueryui/widget' 14 | ], function ($) { 15 | 16 | $.widget( "lukaszfiszer.dragncrop" , { 17 | 18 | classes: { 19 | // Basic classes 20 | container: 'dragncrop', 21 | containerActive: 'dragncrop-dragging', 22 | containment: 'dragncrop-containment', 23 | horizontal: 'dragncrop-horizontal', 24 | vertical: 'dragncrop-vertical', 25 | 26 | // Options' classes 27 | overflow: 'dragncrop-overflow', 28 | overlay: 'dragncrop-overlay', 29 | instruction: 'dragncrop-instruction', 30 | instructionHide: 'dragncrop-instruction-autohide', 31 | instructionText: 'dragncrop-instruction-text' 32 | }, 33 | 34 | options: { 35 | // Initial position 36 | position: {}, 37 | centered: false, 38 | 39 | // Simple overflow: 40 | overflow: false, 41 | 42 | // Overflaid overflow 43 | overlay: false, 44 | 45 | // Drag instruction 46 | instruction: false, 47 | instructionText: 'Drag to crop', 48 | instructionHideOnHover: true, 49 | }, 50 | 51 | move: function ( position ) { 52 | 53 | var left, top, x, y; 54 | 55 | if( !position ){ 56 | throw new Error('position object must be provided'); 57 | } 58 | 59 | if (position.offset === undefined && position.dimension === undefined ) { 60 | throw new Error('position object must contain "left" or "top" props'); 61 | } 62 | 63 | if( this.axis === 'x' && position.offset ){ 64 | left = -position.offset[0] * this.offsetX; 65 | this.element.css('left', left); 66 | this.element.css('top', 0); 67 | } else 68 | if( this.axis === 'x' && position.dimension ){ 69 | left = -position.dimension[0] * this.width; 70 | this.element.css('left', left); 71 | this.element.css('top', 0); 72 | } else 73 | 74 | if( this.axis === 'y' && position.offset ){ 75 | top = -position.offset[1] * this.offsetY; 76 | this.element.css('left', 0); 77 | this.element.css('top', top); 78 | } else 79 | if( this.axis === 'y' && position.dimension ){ 80 | top = -position.dimension[1] * this.height; 81 | this.element.css('left', 0); 82 | this.element.css('top', top); 83 | } 84 | 85 | this._setPosition( { left: left, top: top }); 86 | 87 | }, 88 | 89 | _create: function () { 90 | 91 | this.container = $(this.element.parent()); 92 | this.container.addClass(this.classes.container); 93 | 94 | if( this.options.overflow || this.options.overlay){ 95 | $(this.container).addClass(this.classes.overflow); 96 | } 97 | 98 | var dfd = this.element.imagesLoaded(); 99 | var self = this; 100 | 101 | dfd.done(function(){ 102 | if(self._setAxis.call(self)){ 103 | self._getDimensions.call(self); 104 | self._makeDraggable.call(self); 105 | if (self.options.loaded) { 106 | self.options.loaded(); 107 | } 108 | } 109 | }); 110 | 111 | }, 112 | 113 | _destroy: function() { 114 | this.draggable.draggable('destroy'); 115 | this.container.find('.' + this.classes.containment + ',' + 116 | '.' + this.classes.overlay + ',' + 117 | '.' + this.classes.instruction).remove(); 118 | this.element.removeClass(this.classes.horizontal) 119 | .removeClass(this.classes.vertical); 120 | }, 121 | 122 | getPosition: function() { 123 | return { 124 | offset : [ 125 | ( -this.position.left / this.offsetX) || 0, 126 | ( -this.position.top / this.offsetY) || 0 127 | ], 128 | dimension : [ 129 | ( -this.position.left / this.width) || 0, 130 | ( -this.position.top / this.height) || 0 131 | ] 132 | }; 133 | 134 | }, 135 | 136 | _getDimensions: function() { 137 | 138 | this.width = this.element.width(); 139 | this.height = this.element.height(); 140 | 141 | this.containerWidth = this.container.width(); 142 | this.containerHeight = this.container.height(); 143 | 144 | this.offsetX = this.width - this.containerWidth; 145 | this.offsetY = this.height - this.containerHeight; 146 | 147 | }, 148 | 149 | _setAxis: function() { 150 | 151 | this.photoRatio = this.element.width() / this.element.height(); 152 | this.containerRatio = this.container.width() / this.container.height(); 153 | 154 | if (this.photoRatio > this.containerRatio) { 155 | 156 | this.axis = 'x'; 157 | $(this.element).addClass(this.classes.horizontal); 158 | return true; 159 | 160 | } else if (this.photoRatio < this.containerRatio) { 161 | 162 | this.axis = 'y'; 163 | $(this.element).addClass(this.classes.vertical); 164 | return true; 165 | 166 | }else{ 167 | 168 | return false; 169 | 170 | } 171 | 172 | }, 173 | 174 | _setPosition: function( obj ) { 175 | this.position = obj; 176 | }, 177 | 178 | _makeDraggable : function () { 179 | 180 | var axis = this.axis; 181 | var position = this.options.position; 182 | var containement = this._insertContainment(); 183 | 184 | var draggable = this.draggable = this.element.draggable({ 185 | axis: axis, 186 | containment: containement 187 | }); 188 | 189 | this._on({ 190 | dragstart: function (event, ui) { 191 | this._dragStart( event , ui ); 192 | this.container.addClass( this.classes.containerActive ); 193 | }, 194 | drag: function( event, ui ){ 195 | this._dragging( event , ui ); 196 | if (this.options.overlay) { 197 | this._adaptOverlay( ui ); 198 | } 199 | }, 200 | dragstop: function( event, ui ){ 201 | this._dragStop( event , ui ); 202 | this.container.removeClass( this.classes.containerActive ); 203 | } 204 | }); 205 | 206 | if(this.options.overlay){ 207 | this._insertOverlay(); 208 | } 209 | 210 | if(this.options.instruction){ 211 | this._insertInstruction(); 212 | } 213 | 214 | if(this.options.centered){ 215 | position = { offset : [0.5, 0.5] }; 216 | } 217 | 218 | if( position && ( position.offset || position.coordinates)) { 219 | this.move(position); 220 | }else{ 221 | this._setPosition({ left: 0, top: 0 }); 222 | } 223 | 224 | }, 225 | 226 | _dragStart: function( event, ui ) { 227 | this._setPosition(ui.position); 228 | this._trigger('start', event, this.getPosition() ); 229 | }, 230 | 231 | _dragging: function( event, ui ) { 232 | this._setPosition(ui.position); 233 | this._trigger('drag', event, this.getPosition() ); 234 | }, 235 | 236 | _dragStop: function( event, ui ) { 237 | this._setPosition(ui.position); 238 | this._trigger('stop', event, this.getPosition() ); 239 | }, 240 | 241 | 242 | _insertOverlay: function(){ 243 | 244 | var overlay = $('
').addClass(this.classes.overlay); 245 | this.overlay = overlay.insertBefore(this.element); 246 | return this.overlay; 247 | 248 | }, 249 | 250 | _adaptOverlay: function( ui ) { 251 | 252 | if ( this.axis === 'x' ) { 253 | 254 | this.overlay.css('left', ui.position.left) 255 | .css('border-left-width', -ui.position.left) 256 | .css('right', -ui.position.left - this.offsetX) 257 | .css('border-right-width', ui.position.left + this.offsetX); 258 | 259 | } else if ( this.axis === 'y' ){ 260 | 261 | this.overlay.css('top', ui.position.top) 262 | .css('border-top-width', -ui.position.top) 263 | .css('bottom', -ui.position.top - this.offsetY ) 264 | .css('border-bottom-width', ui.position.top + this.offsetY ); 265 | } 266 | 267 | }, 268 | 269 | _insertContainment: function() { 270 | 271 | var top = - this.offsetY; 272 | var bottom = - this.offsetY; 273 | var left = - this.offsetX; 274 | var right = - this.offsetX; 275 | 276 | var containment = $('
').addClass(this.classes.containment) 277 | .css('top',top).css('bottom', bottom) 278 | .css('left',left).css('right',right) 279 | .css('position','absolute'); 280 | 281 | this.containment = containment.insertBefore(this.element); 282 | return this.containment; 283 | 284 | }, 285 | 286 | _insertInstruction: function() { 287 | this.instruction = $('
').addClass(this.classes.instruction); 288 | if(this.options.instructionHideOnHover){ 289 | this.instruction.addClass(this.classes.instructionHide); 290 | } 291 | this.instruction.append( 292 | $('').text(this.options.instructionText) 293 | .addClass(this.classes.instructionText) 294 | ); 295 | this.instruction.insertAfter(this.element); 296 | return this.instruction; 297 | }, 298 | 299 | }); 300 | 301 | }); 302 | --------------------------------------------------------------------------------