├── .gitignore
├── .gitattributes
├── src
├── images
│ └── bg.png
├── js
│ ├── template.js
│ ├── constants.js
│ ├── defaults.js
│ ├── events.js
│ ├── preview.js
│ ├── handlers.js
│ ├── cropper.js
│ ├── change.js
│ └── render.js
└── css
│ └── cropper.css
├── docs
├── images
│ ├── data.jpg
│ ├── layers.jpg
│ ├── picture.jpg
│ ├── picture-2.jpg
│ └── picture-3.jpg
├── css
│ ├── main.css
│ └── cropper.css
└── js
│ └── main.js
├── .travis.yml
├── test
├── css
│ └── main.css
├── options
│ ├── ready.js
│ ├── minContainerWidth.js
│ ├── minContainerHeight.js
│ ├── minCanvasWidth.js
│ ├── minCanvasHeight.js
│ ├── minCropBoxWidth.js
│ ├── minCropBoxHeight.js
│ ├── crop.js
│ ├── cropend.js
│ ├── cropmove.js
│ ├── autoCrop.js
│ ├── aspectRatio.js
│ ├── modal.js
│ ├── background.js
│ ├── rotatable.js
│ ├── data.js
│ ├── center.js
│ ├── zoomOnWheel.js
│ ├── highlight.js
│ ├── checkOrientation.js
│ ├── cropBoxMovable.js
│ ├── scalable.js
│ ├── zoom.js
│ ├── guides.js
│ ├── toggleDragModeOnDblclick.js
│ ├── movable.js
│ ├── dragMode.js
│ ├── zoomable.js
│ ├── cropstart.js
│ ├── checkCrossOrigin.js
│ ├── cropBoxResizable.js
│ └── viewMode.js
├── events
│ ├── ready.js
│ ├── crop.js
│ ├── cropend.js
│ ├── cropmove.js
│ ├── zoom.js
│ └── cropstart.js
├── methods
│ ├── scaleX.js
│ ├── scaleY.js
│ ├── moveTo.js
│ ├── scale.js
│ ├── getContainerData.js
│ ├── setAspectRatio.js
│ ├── rotate.js
│ ├── zoom.js
│ ├── move.js
│ ├── destroy.js
│ ├── crop.js
│ ├── replace.js
│ ├── getCropBoxData.js
│ ├── enable.js
│ ├── rotateTo.js
│ ├── zoomTo.js
│ ├── getCanvasData.js
│ ├── clear.js
│ ├── setDragMode.js
│ ├── reset.js
│ ├── getImageData.js
│ ├── getCroppedCanvas.js
│ ├── setData.js
│ ├── setCanvasData.js
│ ├── getData.js
│ ├── setCropBoxData.js
│ └── disable.js
├── js
│ └── main.js
└── index.html
├── .babelrc
├── .editorconfig
├── .eslintrc
├── postcss.config.js
├── rollup.config.js
├── ISSUE_TEMPLATE.md
├── LICENSE
├── examples
├── multiple-croppers.html
├── full-crop-box.html
├── fixed-crop-box.html
├── responsive-container.html
├── crop-on-canvas.html
├── a-range-of-aspect-ratio.html
├── cropper-in-modal.html
├── crop-a-round-image.html
├── customize-preview.html
└── mask-an-image.html
├── package.json
├── CONTRIBUTING.md
├── dist
├── cropper.min.css
└── cropper.css
└── CHANGELOG.md
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.map
3 | _*
4 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/src/images/bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/polydice/cropperjs/master/src/images/bg.png
--------------------------------------------------------------------------------
/docs/images/data.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/polydice/cropperjs/master/docs/images/data.jpg
--------------------------------------------------------------------------------
/docs/images/layers.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/polydice/cropperjs/master/docs/images/layers.jpg
--------------------------------------------------------------------------------
/docs/images/picture.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/polydice/cropperjs/master/docs/images/picture.jpg
--------------------------------------------------------------------------------
/docs/images/picture-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/polydice/cropperjs/master/docs/images/picture-2.jpg
--------------------------------------------------------------------------------
/docs/images/picture-3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/polydice/cropperjs/master/docs/images/picture-3.jpg
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "node"
4 | script:
5 | - npm run lint
6 | - npm run build
7 | - npm test
8 |
--------------------------------------------------------------------------------
/test/css/main.css:
--------------------------------------------------------------------------------
1 | .container {
2 | max-width: 640px;
3 | max-height: 360px;
4 | margin: 20px auto;
5 | }
6 |
7 | .container > img {
8 | max-width: 100%;
9 | }
10 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": ["last 2 versions", "ie >= 9"]
7 | }
8 | }]
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 2
7 | indent_style = space
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb-base",
3 | "env": {
4 | "browser": true
5 | },
6 | "rules": {
7 | "no-param-reassign": "off",
8 | "no-restricted-properties": "off",
9 | "valid-jsdoc": ["error", {
10 | "requireReturn": false
11 | }]
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | const rollupConfig = require('./rollup.config');
2 |
3 | module.exports = {
4 | plugins: {
5 | 'postcss-cssnext': {},
6 | 'postcss-url': {
7 | url: 'inline',
8 | },
9 | 'postcss-header': {
10 | header: rollupConfig.banner,
11 | },
12 | stylefmt: {},
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/test/options/ready.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#ready', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(1);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | assert.ok(true);
11 |
12 | done();
13 | }
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/test/events/ready.js:
--------------------------------------------------------------------------------
1 | QUnit.test('events#ready', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(1);
7 |
8 | image.addEventListener('ready', function () {
9 | assert.ok(true);
10 |
11 | done();
12 | });
13 |
14 | return new Cropper(image);
15 | });
16 |
--------------------------------------------------------------------------------
/test/methods/scaleX.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#scaleX', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(1);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 | var imageData = cropper.scaleX(-1).getImageData();
12 |
13 | assert.strictEqual(imageData.scaleX, -1);
14 |
15 | done();
16 | }
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/test/methods/scaleY.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#scaleY', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(1);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 | var imageData = cropper.scaleY(-1).getImageData();
12 |
13 | assert.strictEqual(imageData.scaleY, -1);
14 |
15 | done();
16 | }
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/test/methods/moveTo.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#moveTo', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(2);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 | var canvasData = cropper.moveTo(0, 0).getCanvasData();
12 |
13 | assert.strictEqual(canvasData.left, 0);
14 | assert.strictEqual(canvasData.top, 0);
15 |
16 | done();
17 | }
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/test/methods/scale.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#scale', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(2);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 | var imageData = cropper.scale(-1, -1).getImageData();
12 |
13 | assert.strictEqual(imageData.scaleX, -1);
14 | assert.strictEqual(imageData.scaleY, -1);
15 |
16 | done();
17 | }
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/test/methods/getContainerData.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#getContainerData', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(2);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 | var containerData = cropper.getContainerData();
12 |
13 | assert.ok(util.isNumber(containerData.width));
14 | assert.ok(util.isNumber(containerData.height));
15 |
16 | done();
17 | }
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/test/methods/setAspectRatio.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#setAspectRatio', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(2);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 | var options = cropper.options;
12 |
13 | assert.ok(isNaN(options.aspectRatio));
14 | cropper.setAspectRatio(1);
15 | assert.strictEqual(options.aspectRatio, 1);
16 |
17 | done();
18 | }
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/test/methods/rotate.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#rotate', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(3);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 |
12 | assert.strictEqual(cropper.rotate(360).getImageData().rotate, 0);
13 | assert.strictEqual(cropper.rotate(90).getImageData().rotate, 90);
14 | assert.strictEqual(cropper.rotate(-180).getImageData().rotate, -90);
15 |
16 | done();
17 | }
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/test/methods/zoom.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#zoom', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(2);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 | var canvasData = cropper.getCanvasData();
12 | var changedCanvasData = cropper.zoom(0.1).getCanvasData();
13 |
14 | assert.ok(changedCanvasData.width > canvasData.width);
15 | assert.ok(changedCanvasData.height > canvasData.height);
16 |
17 | done();
18 | }
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/test/options/minContainerWidth.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#minContainerWidth', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 | var minContainerWidth = 641;
6 |
7 | assert.expect(1);
8 |
9 | return new Cropper(image, {
10 | minContainerWidth: minContainerWidth,
11 |
12 | ready: function () {
13 | var cropper = this.cropper;
14 | var containerData = cropper.getContainerData();
15 |
16 | assert.strictEqual(Math.round(containerData.width), minContainerWidth);
17 |
18 | done();
19 | }
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/test/options/minContainerHeight.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#minContainerHeight', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 | var minContainerHeight = 361;
6 |
7 | assert.expect(1);
8 |
9 | return new Cropper(image, {
10 | minContainerHeight: minContainerHeight,
11 |
12 | ready: function () {
13 | var cropper = this.cropper;
14 | var containerData = cropper.getContainerData();
15 |
16 | assert.strictEqual(Math.round(containerData.height), minContainerHeight);
17 |
18 | done();
19 | }
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/test/methods/move.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#move', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(2);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 | var canvasData = cropper.getCanvasData();
12 | var changedCanvasData = cropper.move(1, 1).getCanvasData();
13 |
14 | assert.strictEqual(changedCanvasData.left, canvasData.left + 1);
15 | assert.strictEqual(changedCanvasData.top, canvasData.top + 1);
16 |
17 | done();
18 | }
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/test/methods/destroy.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#destroy', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(4);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 |
12 | assert.ok(typeof cropper === 'object');
13 | assert.ok(util.hasClass(image, 'cropper-hidden'));
14 |
15 | cropper.destroy();
16 | assert.ok(typeof this.cropper === 'undefined');
17 | assert.notOk(util.hasClass(image, 'cropper-hidden'));
18 |
19 | done();
20 | }
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/test/methods/crop.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#crop', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(4);
7 |
8 | return new Cropper(image, {
9 | autoCrop: false,
10 |
11 | ready: function () {
12 | var cropper = this.cropper;
13 |
14 | assert.notOk(cropper.cropped);
15 | assert.ok(util.hasClass(cropper.cropBox, 'cropper-hidden'));
16 |
17 | cropper.crop();
18 | assert.ok(cropper.cropped);
19 | assert.notOk(util.hasClass(cropper.cropBox, 'cropper-hidden'));
20 |
21 | done();
22 | }
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/test/methods/replace.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#replace', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(2);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 |
12 | cropper.options.ready = function () {
13 | assert.notOk(cropper.cropped);
14 | cropper.crop();
15 | assert.ok(cropper.cropped);
16 | done();
17 | };
18 |
19 | cropper.options.autoCrop = false;
20 | cropper.replace('../docs/images/picture-2.jpg');
21 | }
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/test/options/minCanvasWidth.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#minCanvasWidth', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 | var minCanvasWidth = 480;
6 |
7 | assert.expect(1);
8 |
9 | return new Cropper(image, {
10 | minCanvasWidth: minCanvasWidth,
11 |
12 | ready: function () {
13 | var cropper = this.cropper;
14 | var canvasData = cropper.setCanvasData({
15 | width: 320
16 | }).getCanvasData();
17 |
18 | assert.strictEqual(Math.round(canvasData.width), minCanvasWidth);
19 |
20 | done();
21 | }
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/test/methods/getCropBoxData.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#getCropBoxData', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(4);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 | var cropBoxData = cropper.getCropBoxData();
12 |
13 | assert.ok(util.isNumber(cropBoxData.left));
14 | assert.ok(util.isNumber(cropBoxData.top));
15 | assert.ok(util.isNumber(cropBoxData.width));
16 | assert.ok(util.isNumber(cropBoxData.height));
17 |
18 | done();
19 | }
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/test/methods/enable.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#enable', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(4);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 |
12 | cropper.disable();
13 | assert.ok(cropper.disabled);
14 | assert.ok(util.hasClass(cropper.cropper, 'cropper-disabled'));
15 |
16 | cropper.enable();
17 | assert.notOk(cropper.disabled);
18 | assert.notOk(util.hasClass(cropper.cropper, 'cropper-disabled'));
19 |
20 | done();
21 | }
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/test/options/minCanvasHeight.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#minCanvasHeight', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 | var minCanvasHeight = 270;
6 |
7 | assert.expect(1);
8 |
9 | return new Cropper(image, {
10 | minCanvasHeight: minCanvasHeight,
11 |
12 | ready: function () {
13 | var cropper = this.cropper;
14 | var canvasData = cropper.setCanvasData({
15 | height: 180
16 | }).getCanvasData();
17 |
18 | assert.strictEqual(Math.round(canvasData.height), minCanvasHeight);
19 |
20 | done();
21 | }
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/test/options/minCropBoxWidth.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#minCropBoxWidth', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 | var minCropBoxWidth = 300;
6 |
7 | assert.expect(1);
8 |
9 | return new Cropper(image, {
10 | minCropBoxWidth: minCropBoxWidth,
11 |
12 | ready: function () {
13 | var cropper = this.cropper;
14 | var cropBoxData = cropper.setCropBoxData({
15 | width: 200
16 | }).getCropBoxData();
17 |
18 | assert.strictEqual(Math.round(cropBoxData.width), minCropBoxWidth);
19 |
20 | done();
21 | }
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/test/options/minCropBoxHeight.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#minCropBoxHeight', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 | var minCropBoxHeight = 150;
6 |
7 | assert.expect(1);
8 |
9 | return new Cropper(image, {
10 | minCropBoxHeight: minCropBoxHeight,
11 |
12 | ready: function () {
13 | var cropper = this.cropper;
14 | var cropBoxData = cropper.setCropBoxData({
15 | height: 100
16 | }).getCropBoxData();
17 |
18 | assert.strictEqual(Math.round(cropBoxData.height), minCropBoxHeight);
19 |
20 | done();
21 | }
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/test/events/crop.js:
--------------------------------------------------------------------------------
1 | QUnit.test('events#crop', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(7);
7 |
8 | image.addEventListener('crop', function (e) {
9 | assert.ok(util.isNumber(e.detail.x));
10 | assert.ok(util.isNumber(e.detail.y));
11 | assert.ok(util.isNumber(e.detail.width));
12 | assert.ok(util.isNumber(e.detail.height));
13 | assert.ok(util.isNumber(e.detail.rotate));
14 | assert.ok(util.isNumber(e.detail.scaleX));
15 | assert.ok(util.isNumber(e.detail.scaleY));
16 |
17 | done();
18 | });
19 |
20 | return new Cropper(image);
21 | });
22 |
--------------------------------------------------------------------------------
/test/options/crop.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#crop', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(7);
7 |
8 | return new Cropper(image, {
9 | crop: function (e) {
10 | assert.ok(util.isNumber(e.detail.x));
11 | assert.ok(util.isNumber(e.detail.y));
12 | assert.ok(util.isNumber(e.detail.width));
13 | assert.ok(util.isNumber(e.detail.height));
14 | assert.ok(util.isNumber(e.detail.rotate));
15 | assert.ok(util.isNumber(e.detail.scaleX));
16 | assert.ok(util.isNumber(e.detail.scaleY));
17 |
18 | done();
19 | }
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/test/methods/rotateTo.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#rotateTo', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(4);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 |
12 | assert.strictEqual(cropper.rotateTo(360).getImageData().rotate, 0);
13 | assert.strictEqual(cropper.rotateTo(90).getImageData().rotate, 90);
14 | assert.strictEqual(cropper.rotateTo(0).getImageData().rotate, 0);
15 | assert.strictEqual(cropper.rotateTo(-180).getImageData().rotate, -180);
16 |
17 | done();
18 | }
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/test/methods/zoomTo.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#zoomTo', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(3);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 | var imageData = cropper.zoomTo(1).getImageData();
12 | var canvasData = cropper.getCanvasData();
13 |
14 | assert.strictEqual(imageData.width, imageData.naturalWidth);
15 | assert.strictEqual(canvasData.width, canvasData.naturalWidth);
16 | assert.strictEqual(canvasData.naturalWidth, imageData.naturalWidth);
17 |
18 | done();
19 | }
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/test/methods/getCanvasData.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#getCanvasData', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(6);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 | var canvasData = cropper.getCanvasData();
12 |
13 | assert.ok(util.isNumber(canvasData.left));
14 | assert.ok(util.isNumber(canvasData.top));
15 | assert.ok(util.isNumber(canvasData.width));
16 | assert.ok(util.isNumber(canvasData.height));
17 | assert.ok(util.isNumber(canvasData.naturalWidth));
18 | assert.ok(util.isNumber(canvasData.naturalHeight));
19 |
20 | done();
21 | }
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/test/methods/clear.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#clear', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(4);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 |
12 | cropper.clear();
13 | assert.notOk(cropper.cropped);
14 | assert.deepEqual(cropper.getData(), {
15 | x: 0,
16 | y: 0,
17 | width: 0,
18 | height: 0,
19 | rotate: 0,
20 | scaleX: 1,
21 | scaleY: 1
22 | });
23 | assert.deepEqual(cropper.getCropBoxData(), {});
24 | assert.ok(util.hasClass(cropper.cropBox, 'cropper-hidden'));
25 |
26 | done();
27 | }
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/test/options/cropend.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#cropend', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(1);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var PointerEvent = window.PointerEvent;
11 | var cropper = this.cropper;
12 |
13 | util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerdown' : 'mousedown');
14 | util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointermove' : 'mousemove');
15 | util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerup' : 'mouseup');
16 |
17 | done();
18 | },
19 |
20 | cropend: function (e) {
21 | assert.strictEqual(e.detail.action, 'crop');
22 | }
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/test/options/cropmove.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#cropmove', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(1);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var PointerEvent = window.PointerEvent;
11 | var cropper = this.cropper;
12 |
13 | util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerdown' : 'mousedown');
14 | util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointermove' : 'mousemove');
15 | util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerup' : 'mouseup');
16 |
17 | done();
18 | },
19 |
20 | cropmove: function (e) {
21 | assert.strictEqual(e.detail.action, 'crop');
22 | }
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/test/methods/setDragMode.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#setDragMode', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(4);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 | var dragBox = cropper.dragBox;
12 |
13 | assert.strictEqual(dragBox.dataset.action, 'crop');
14 |
15 | cropper.setDragMode('move');
16 | assert.strictEqual(dragBox.dataset.action, 'move');
17 |
18 | cropper.setDragMode('crop');
19 | assert.strictEqual(dragBox.dataset.action, 'crop');
20 |
21 | cropper.setDragMode('none');
22 | assert.strictEqual(dragBox.dataset.action, 'none');
23 |
24 | done();
25 | }
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/test/events/cropend.js:
--------------------------------------------------------------------------------
1 | QUnit.test('events#cropend', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(1);
7 |
8 | image.addEventListener('ready', function () {
9 | var PointerEvent = window.PointerEvent;
10 | var cropper = this.cropper;
11 |
12 | util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerdown' : 'mousedown');
13 | util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointermove' : 'mousemove');
14 | util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerup' : 'mouseup');
15 |
16 | done();
17 | });
18 |
19 | image.addEventListener('cropend', function (e) {
20 | assert.strictEqual(e.detail.action, 'crop');
21 | });
22 |
23 | return new Cropper(image);
24 | });
25 |
--------------------------------------------------------------------------------
/test/events/cropmove.js:
--------------------------------------------------------------------------------
1 | QUnit.test('events#cropmove', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(1);
7 |
8 | image.addEventListener('ready', function () {
9 | var PointerEvent = window.PointerEvent;
10 | var cropper = this.cropper;
11 |
12 | util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerdown' : 'mousedown');
13 | util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointermove' : 'mousemove');
14 | util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerup' : 'mouseup');
15 |
16 | done();
17 | });
18 |
19 | image.addEventListener('cropmove', function (e) {
20 | assert.strictEqual(e.detail.action, 'crop');
21 | });
22 |
23 | return new Cropper(image);
24 | });
25 |
--------------------------------------------------------------------------------
/test/options/autoCrop.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#autoCrop: true', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(1);
7 |
8 | return new Cropper(image, {
9 | // autoCrop: true,
10 |
11 | ready: function () {
12 | var cropper = this.cropper;
13 |
14 | assert.ok(cropper.cropped);
15 |
16 | done();
17 | }
18 | });
19 | });
20 |
21 | QUnit.test('options#autoCrop: false', function (assert) {
22 | var done = assert.async();
23 | var util = window.Util;
24 | var image = util.createImage();
25 |
26 | assert.expect(1);
27 |
28 | return new Cropper(image, {
29 | autoCrop: false,
30 |
31 | ready: function () {
32 | var cropper = this.cropper;
33 |
34 | assert.notOk(cropper.cropped);
35 |
36 | done();
37 | }
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/test/options/aspectRatio.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#aspectRatio: NaN', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(1);
7 |
8 | return new Cropper(image, {
9 | // aspectRatio: NaN,
10 |
11 | ready: function () {
12 | var cropper = this.cropper;
13 |
14 | assert.ok(isNaN(cropper.options.aspectRatio));
15 |
16 | done();
17 | }
18 | });
19 | });
20 |
21 | QUnit.test('options#aspectRatio: 1', function (assert) {
22 | var done = assert.async();
23 | var util = window.Util;
24 | var image = util.createImage();
25 |
26 | assert.expect(1);
27 |
28 | return new Cropper(image, {
29 | aspectRatio: 1,
30 |
31 | ready: function () {
32 | var cropper = this.cropper;
33 |
34 | assert.strictEqual(cropper.options.aspectRatio, 1);
35 |
36 | done();
37 | }
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/test/options/modal.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#modal: true', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(1);
7 |
8 | return new Cropper(image, {
9 | // modal: true,
10 |
11 | ready: function () {
12 | var cropper = this.cropper;
13 |
14 | assert.ok(util.hasClass(cropper.dragBox, 'cropper-modal'));
15 |
16 | done();
17 | }
18 | });
19 | });
20 |
21 | QUnit.test('options#modal: false', function (assert) {
22 | var done = assert.async();
23 | var util = window.Util;
24 | var image = util.createImage();
25 |
26 | assert.expect(1);
27 |
28 | return new Cropper(image, {
29 | modal: false,
30 |
31 | ready: function () {
32 | var cropper = this.cropper;
33 |
34 | assert.notOk(util.hasClass(cropper.dragBox, 'cropper-modal'));
35 |
36 | done();
37 | }
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/test/options/background.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#background: true', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(1);
7 |
8 | return new Cropper(image, {
9 | // background: true,
10 |
11 | ready: function () {
12 | var cropper = this.cropper;
13 |
14 | assert.ok(util.hasClass(cropper.cropper, 'cropper-bg'));
15 |
16 | done();
17 | }
18 | });
19 | });
20 |
21 | QUnit.test('options#background: false', function (assert) {
22 | var done = assert.async();
23 | var util = window.Util;
24 | var image = util.createImage();
25 |
26 | assert.expect(1);
27 |
28 | return new Cropper(image, {
29 | background: false,
30 |
31 | ready: function () {
32 | var cropper = this.cropper;
33 |
34 | assert.notOk(util.hasClass(cropper.cropper, 'cropper-bg'));
35 |
36 | done();
37 | }
38 | });
39 |
40 | });
41 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | const babel = require('rollup-plugin-babel');
2 | const pkg = require('./package');
3 |
4 | const now = new Date();
5 |
6 | module.exports = {
7 | input: 'src/js/cropper.js',
8 | output: [
9 | {
10 | file: 'dist/cropper.js',
11 | format: 'umd',
12 | },
13 | {
14 | file: 'dist/cropper.common.js',
15 | format: 'cjs',
16 | },
17 | {
18 | file: 'dist/cropper.esm.js',
19 | format: 'es',
20 | },
21 | {
22 | file: 'docs/js/cropper.js',
23 | format: 'umd',
24 | },
25 | ],
26 | name: 'Cropper',
27 | plugins: [
28 | babel({
29 | exclude: 'node_modules/**',
30 | }),
31 | ],
32 | banner: `/*!
33 | * Cropper.js v${pkg.version}
34 | * https://github.com/${pkg.repository}
35 | *
36 | * Copyright (c) 2015-${now.getFullYear()} ${pkg.author.name}
37 | * Released under the ${pkg.license} license
38 | *
39 | * Date: ${now.toISOString()}
40 | */
41 | `,
42 | };
43 |
--------------------------------------------------------------------------------
/test/options/rotatable.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#rotatable: true', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(1);
7 |
8 | return new Cropper(image, {
9 | // rotatable: true,
10 |
11 | ready: function () {
12 | var cropper = this.cropper;
13 |
14 | assert.strictEqual(cropper.rotate(90).getImageData().rotate, 90);
15 |
16 | done();
17 | }
18 | });
19 | });
20 |
21 | QUnit.test('options#rotatable: false', function (assert) {
22 | var done = assert.async();
23 | var util = window.Util;
24 | var image = util.createImage();
25 |
26 | assert.expect(1);
27 |
28 | return new Cropper(image, {
29 | rotatable: false,
30 |
31 | ready: function () {
32 | var cropper = this.cropper;
33 |
34 | assert.strictEqual(cropper.rotate(90).getImageData().rotate, undefined);
35 |
36 | done();
37 | }
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/test/methods/reset.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#reset', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(4);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 | var canvasData = cropper.getCanvasData();
12 | var cropBoxData = cropper.getCropBoxData();
13 |
14 | cropper.setCanvasData({
15 | top: canvasData.top + 10,
16 | width: canvasData.width - 10
17 | });
18 |
19 | assert.notDeepEqual(cropper.getCanvasData(), canvasData);
20 |
21 | cropper.setCropBoxData({
22 | left: cropBoxData.left + 10,
23 | height: cropBoxData.height - 10
24 | });
25 |
26 | assert.notDeepEqual(cropper.getCropBoxData(), cropBoxData);
27 |
28 | cropper.reset();
29 | assert.deepEqual(cropper.getCanvasData(), canvasData);
30 | assert.deepEqual(cropper.getCropBoxData(), cropBoxData);
31 |
32 | done();
33 | }
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/test/options/data.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#data', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 | var initialData = {
6 | x: 360,
7 | y: 450,
8 | width: 640,
9 | height: 360,
10 | rotate: 45,
11 | scaleX: -1,
12 | scaleY: -1
13 | };
14 |
15 | assert.expect(7);
16 |
17 | return new Cropper(image, {
18 | data: initialData,
19 |
20 | ready: function () {
21 | var cropper = this.cropper;
22 | var data = cropper.getData(true);
23 |
24 | assert.strictEqual(data.x, initialData.x);
25 | assert.strictEqual(data.y, initialData.y);
26 | assert.strictEqual(data.width, initialData.width);
27 | assert.strictEqual(data.height, initialData.height);
28 | assert.strictEqual(data.rotate, initialData.rotate);
29 | assert.strictEqual(data.scaleX, initialData.scaleX);
30 | assert.strictEqual(data.scaleY, initialData.scaleY);
31 |
32 | done();
33 | }
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/test/options/center.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#center: true', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(1);
7 |
8 | return new Cropper(image, {
9 | // center: true,
10 |
11 | ready: function () {
12 | var cropper = this.cropper;
13 | var center = util.getByClass(cropper.cropBox, 'cropper-center');
14 |
15 | assert.notOk(util.hasClass(center[0], 'cropper-hidden'));
16 |
17 | done();
18 | }
19 | });
20 | });
21 |
22 | QUnit.test('options#center: false', function (assert) {
23 | var done = assert.async();
24 | var util = window.Util;
25 | var image = util.createImage();
26 |
27 | assert.expect(1);
28 |
29 | return new Cropper(image, {
30 | center: false,
31 |
32 | ready: function () {
33 | var cropper = this.cropper;
34 | var center = util.getByClass(cropper.cropBox, 'cropper-center');
35 |
36 | assert.ok(util.hasClass(center[0], 'cropper-hidden'));
37 |
38 | done();
39 | }
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/test/options/zoomOnWheel.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#zoomOnWheel: true', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(1);
7 |
8 | return new Cropper(image, {
9 | // zoomOnWheel: true,
10 |
11 | ready: function () {
12 | var cropper = this.cropper;
13 |
14 | util.dispatchEvent(cropper.cropper, 'wheel');
15 |
16 | done();
17 | },
18 |
19 | zoom: function () {
20 | assert.ok(true);
21 | }
22 | });
23 | });
24 |
25 | QUnit.test('options#zoomOnWheel: false', function (assert) {
26 | var done = assert.async();
27 | var util = window.Util;
28 | var image = util.createImage();
29 |
30 | assert.expect(0);
31 |
32 | return new Cropper(image, {
33 | zoomOnWheel: false,
34 |
35 | ready: function () {
36 | var cropper = this.cropper;
37 |
38 | util.dispatchEvent(cropper.cropper, 'wheel');
39 |
40 | done();
41 | },
42 |
43 | zoom: function () {
44 | assert.ok(false);
45 | }
46 | });
47 | });
48 |
--------------------------------------------------------------------------------
/test/options/highlight.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#highlight: true', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(1);
7 |
8 | return new Cropper(image, {
9 | // highlight: true,
10 |
11 | ready: function () {
12 | var cropper = this.cropper;
13 | var face = util.getByClass(cropper.cropBox, 'cropper-face');
14 |
15 | assert.notOk(util.hasClass(face[0], 'cropper-invisible'));
16 |
17 | done();
18 | }
19 | });
20 | });
21 |
22 | QUnit.test('options#highlight: false', function (assert) {
23 | var done = assert.async();
24 | var util = window.Util;
25 | var image = util.createImage();
26 |
27 | assert.expect(1);
28 |
29 | return new Cropper(image, {
30 | highlight: false,
31 |
32 | ready: function () {
33 | var cropper = this.cropper;
34 | var face = util.getByClass(cropper.cropBox, 'cropper-face');
35 |
36 | assert.ok(util.hasClass(face[0], 'cropper-invisible'));
37 |
38 | done();
39 | }
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/test/options/checkOrientation.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#checkOrientation: true', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage({
5 | src: '../docs/images/picture-3.jpg'
6 | });
7 |
8 | assert.expect(1);
9 |
10 | return new Cropper(image, {
11 | // checkOrientation: true,
12 |
13 | ready: function () {
14 | var cropper = this.cropper;
15 |
16 | assert.notStrictEqual(cropper.getData().rotate, 0);
17 |
18 | done();
19 | }
20 | });
21 | });
22 |
23 | QUnit.test('options#checkOrientation: false', function (assert) {
24 | var done = assert.async();
25 | var util = window.Util;
26 | var image = util.createImage({
27 | src: '../docs/images/picture-3.jpg'
28 | });
29 |
30 | assert.expect(1);
31 |
32 | return new Cropper(image, {
33 | checkOrientation: false,
34 |
35 | ready: function () {
36 | var cropper = this.cropper;
37 |
38 | assert.strictEqual(cropper.getData().rotate, 0);
39 |
40 | done();
41 | }
42 | });
43 | });
44 |
--------------------------------------------------------------------------------
/test/methods/getImageData.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#getImageData', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(10);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 | var imageData = cropper.getImageData();
12 |
13 | assert.ok(util.isNumber(imageData.naturalWidth));
14 | assert.ok(util.isNumber(imageData.naturalHeight));
15 | assert.ok(util.isNumber(imageData.aspectRatio));
16 | assert.ok(util.isNumber(imageData.left));
17 | assert.ok(util.isNumber(imageData.top));
18 | assert.ok(util.isNumber(imageData.width));
19 | assert.ok(util.isNumber(imageData.height));
20 |
21 | imageData = cropper.rotateTo(45).getImageData();
22 | assert.strictEqual(imageData.rotate, 45);
23 |
24 | imageData = cropper.scale(-1, -1).getImageData();
25 | assert.strictEqual(imageData.scaleX, -1);
26 | assert.strictEqual(imageData.scaleY, -1);
27 |
28 | done();
29 | }
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/test/options/cropBoxMovable.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#cropBoxMovable: true', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(1);
7 |
8 | return new Cropper(image, {
9 | // cropBoxMovable: true,
10 |
11 | ready: function () {
12 | var cropper = this.cropper;
13 | var face = util.getByClass(cropper.cropBox, 'cropper-face');
14 |
15 | assert.strictEqual(face[0].dataset.action, 'all');
16 |
17 | done();
18 | }
19 | });
20 | });
21 |
22 | QUnit.test('options#cropBoxMovable: false', function (assert) {
23 | var done = assert.async();
24 | var util = window.Util;
25 | var image = util.createImage();
26 |
27 | assert.expect(1);
28 |
29 | return new Cropper(image, {
30 | cropBoxMovable: false,
31 |
32 | ready: function () {
33 | var cropper = this.cropper;
34 | var face = util.getByClass(cropper.cropBox, 'cropper-face');
35 |
36 | assert.strictEqual(face[0].dataset.action, cropper.options.dragMode);
37 |
38 | done();
39 | }
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | Before opening an issue:
2 |
3 | - [Search for duplicate or closed issues](https://github.com/fengyuanchen/cropperjs/issues?utf8=%E2%9C%93&q=is%3Aissue)
4 | - Prepare a [reduced test case](https://css-tricks.com/reduced-test-cases/) for any bugs
5 | - Read the [contributing guidelines](https://github.com/fengyuanchen/cropperjs/blob/master/CONTRIBUTING.md)
6 |
7 | When asking general "how to" questions:
8 |
9 | - Please do not open an issue here
10 | - Instead, ask for help on [StackOverflow](http://stackoverflow.com/)
11 |
12 | When reporting a bug, include:
13 |
14 | - Operating system and version (Windows, Mac OS X, Android, iOS, Win10 Mobile)
15 | - Browser and version (Chrome, Firefox, Safari, IE, MS Edge, Opera 15+, Android Browser)
16 | - Reduced test cases and potential fixes using [JS Bin](https://jsbin.com), [JSFiddle](https://jsfiddle.net/) or [CodePen](https://codepen.io/)
17 |
18 | When suggesting a feature, include:
19 |
20 | - As much detail as possible for what we should add and why it's important to Cropper.js
21 | - Relevant links to prior art, screenshots, or live demos whenever possible
22 |
--------------------------------------------------------------------------------
/test/methods/getCroppedCanvas.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#getCroppedCanvas', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(7);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 | var canvas = cropper.getCroppedCanvas({
12 | width: 160,
13 | height: 90
14 | });
15 | var pixelData;
16 |
17 | assert.ok(canvas instanceof HTMLCanvasElement);
18 | assert.strictEqual(canvas.width, 160);
19 | assert.strictEqual(canvas.height, 90);
20 |
21 | canvas = cropper.rotate(90).getCroppedCanvas({
22 | fillColor: '#010101'
23 | });
24 | pixelData = canvas.getContext('2d').getImageData(0, 0, 1, 1).data;
25 | assert.strictEqual(pixelData[0], 1, 'red is 1');
26 | assert.strictEqual(pixelData[1], 1, 'green is 1');
27 | assert.strictEqual(pixelData[2], 1, 'blue is 1');
28 | assert.strictEqual(pixelData[3], 255, 'color is opaque');
29 |
30 | done();
31 | }
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/test/options/scalable.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#scalable: true', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(2);
7 |
8 | return new Cropper(image, {
9 | // scalable: true,
10 |
11 | ready: function () {
12 | var cropper = this.cropper;
13 | var imageData = cropper.scale(-1, -1).getImageData();
14 |
15 | assert.strictEqual(imageData.scaleX, -1);
16 | assert.strictEqual(imageData.scaleY, -1);
17 |
18 | done();
19 | }
20 | });
21 | });
22 |
23 | QUnit.test('options#scalable: false', function (assert) {
24 | var done = assert.async();
25 | var util = window.Util;
26 | var image = util.createImage();
27 |
28 | assert.expect(2);
29 |
30 | return new Cropper(image, {
31 | scalable: false,
32 |
33 | ready: function () {
34 | var cropper = this.cropper;
35 | var imageData = cropper.scale(-1, -1).getImageData();
36 |
37 | assert.strictEqual(imageData.scaleX, undefined);
38 | assert.strictEqual(imageData.scaleY, undefined);
39 |
40 | done();
41 | }
42 | });
43 | });
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015-2017 Chen Fengyuan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/test/methods/setData.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#setData', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(8);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 | var data = cropper.getData();
12 | var changedData = cropper.setData({
13 | x: 16,
14 | y: 9
15 | }).getData();
16 |
17 | assert.notStrictEqual(changedData.x, data.x);
18 | assert.notStrictEqual(changedData.y, data.y);
19 | assert.strictEqual(changedData.width, data.width);
20 | assert.strictEqual(changedData.height, data.height);
21 |
22 | data = cropper.getData();
23 | changedData = cropper.setData({
24 | width: 320,
25 | height: 180
26 | }).getData();
27 |
28 | assert.strictEqual(changedData.x, data.x);
29 | assert.strictEqual(changedData.y, data.y);
30 | assert.notStrictEqual(changedData.width, data.width);
31 | assert.notStrictEqual(changedData.height, data.height);
32 |
33 | done();
34 | }
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/test/options/zoom.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#zoom', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(3);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 |
12 | cropper.zoom(0.1);
13 |
14 | done();
15 | },
16 |
17 | zoom: function (e) {
18 | assert.ok(e.detail.ratio > 0);
19 | assert.ok(e.detail.oldRatio > 0);
20 | assert.ok(e.detail.ratio > e.detail.oldRatio);
21 | }
22 | });
23 | });
24 |
25 | QUnit.test('options#zoom: default prevented', function (assert) {
26 | var done = assert.async();
27 | var util = window.Util;
28 | var image = util.createImage();
29 |
30 | assert.expect(1);
31 |
32 | return new Cropper(image, {
33 | ready: function () {
34 | var cropper = this.cropper;
35 | var canvasData = cropper.getCanvasData();
36 |
37 | assert.deepEqual(cropper.zoom(0.1).getCanvasData(), canvasData);
38 |
39 | done();
40 | },
41 |
42 | zoom: function (e) {
43 | e.preventDefault();
44 | }
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/test/events/zoom.js:
--------------------------------------------------------------------------------
1 | QUnit.test('events#zoom', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(3);
7 |
8 | image.addEventListener('ready', function () {
9 | var cropper = this.cropper;
10 |
11 | cropper.zoom(0.1);
12 |
13 | done();
14 | });
15 |
16 | image.addEventListener('zoom', function (e) {
17 | assert.ok(e.detail.ratio > 0);
18 | assert.ok(e.detail.oldRatio > 0);
19 | assert.ok(e.detail.ratio > e.detail.oldRatio);
20 | });
21 |
22 | return new Cropper(image);
23 | });
24 |
25 | QUnit.test('events#zoom: default prevented', function (assert) {
26 | var done = assert.async();
27 | var util = window.Util;
28 | var image = util.createImage();
29 |
30 | assert.expect(1);
31 |
32 | image.addEventListener('ready', function () {
33 | var cropper = this.cropper;
34 | var canvasData = cropper.getCanvasData();
35 |
36 | assert.deepEqual(cropper.zoom(0.1).getCanvasData(), canvasData);
37 |
38 | done();
39 | });
40 |
41 | image.addEventListener('zoom', function (e) {
42 | e.preventDefault();
43 | });
44 |
45 | return new Cropper(image);
46 | });
47 |
--------------------------------------------------------------------------------
/test/methods/setCanvasData.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#setCanvasData', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(6);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 | var canvasData = cropper.getCanvasData();
12 | var changedCanvasData = cropper.setCanvasData({
13 | left: 16,
14 | top: 9
15 | }).getCanvasData();
16 |
17 | assert.notStrictEqual(changedCanvasData.left, canvasData.left);
18 | assert.notStrictEqual(changedCanvasData.top, canvasData.top);
19 | assert.strictEqual(changedCanvasData.width, canvasData.width);
20 | assert.strictEqual(changedCanvasData.height, canvasData.height);
21 |
22 | canvasData = cropper.getCanvasData();
23 | changedCanvasData = cropper.setCanvasData({
24 | width: 320,
25 | height: 180
26 | }).getCanvasData();
27 |
28 | assert.notStrictEqual(changedCanvasData.width, canvasData.width);
29 | assert.notStrictEqual(changedCanvasData.height, canvasData.height);
30 |
31 | done();
32 | }
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/test/options/guides.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#guides: true', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(2);
7 |
8 | return new Cropper(image, {
9 | // guides: true,
10 |
11 | ready: function () {
12 | var cropper = this.cropper;
13 | var dashed = util.getByClass(cropper.cropBox, 'cropper-dashed');
14 |
15 | assert.notOk(util.hasClass(dashed[0], 'cropper-hidden'));
16 | assert.notOk(util.hasClass(dashed[1], 'cropper-hidden'));
17 |
18 | done();
19 | }
20 | });
21 | });
22 |
23 | QUnit.test('options#guides: false', function (assert) {
24 | var done = assert.async();
25 | var util = window.Util;
26 | var image = util.createImage();
27 |
28 | assert.expect(2);
29 |
30 | return new Cropper(image, {
31 | guides: false,
32 |
33 | ready: function () {
34 | var cropper = this.cropper;
35 | var dashed = util.getByClass(cropper.cropBox, 'cropper-dashed');
36 |
37 | assert.ok(util.hasClass(dashed[0], 'cropper-hidden'));
38 | assert.ok(util.hasClass(dashed[1], 'cropper-hidden'));
39 |
40 | done();
41 | }
42 | });
43 | });
44 |
--------------------------------------------------------------------------------
/examples/multiple-croppers.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Cropper.js
8 |
9 |
19 |
20 |
21 |
22 |
23 |
Multiple Croppers
24 |
Cropper
25 |
26 |

27 |
28 |
Another
29 |
30 |

31 |
32 |
33 |
34 |
35 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/test/methods/getData.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#getData', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(7);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 | var data = cropper.getData();
12 |
13 | assert.ok(util.isNumber(data.x));
14 | assert.ok(util.isNumber(data.y));
15 | assert.ok(util.isNumber(data.width));
16 | assert.ok(util.isNumber(data.height));
17 | assert.ok(util.isNumber(data.rotate));
18 | assert.ok(util.isNumber(data.scaleX));
19 | assert.ok(util.isNumber(data.scaleY));
20 |
21 | done();
22 | }
23 | });
24 | });
25 |
26 | QUnit.test('methods#getData: rounded', function (assert) {
27 | var done = assert.async();
28 | var util = window.Util;
29 | var image = util.createImage();
30 |
31 | assert.expect(4);
32 |
33 | return new Cropper(image, {
34 | ready: function () {
35 | var cropper = this.cropper;
36 | var data = cropper.getData(true);
37 |
38 | assert.ok(data.x % 1 === 0);
39 | assert.ok(data.y % 1 === 0);
40 | assert.ok(data.width % 1 === 0);
41 | assert.ok(data.height % 1 === 0);
42 |
43 | done();
44 | }
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/test/options/toggleDragModeOnDblclick.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#toggleDragModeOnDblclick: true', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(2);
7 |
8 | return new Cropper(image, {
9 | // toggleDragModeOnDblclick: true,
10 |
11 | ready: function () {
12 | var cropper = this.cropper;
13 | var dragBox = cropper.dragBox;
14 |
15 | util.dispatchEvent(dragBox, 'dblclick');
16 | assert.ok(util.hasClass(dragBox, 'cropper-move'));
17 | assert.strictEqual(dragBox.dataset.action, 'move');
18 |
19 | done();
20 | }
21 | });
22 | });
23 |
24 | QUnit.test('options#toggleDragModeOnDblclick: false', function (assert) {
25 | var done = assert.async();
26 | var util = window.Util;
27 | var image = util.createImage();
28 |
29 | assert.expect(2);
30 |
31 | return new Cropper(image, {
32 | toggleDragModeOnDblclick: false,
33 |
34 | ready: function () {
35 | var cropper = this.cropper;
36 | var dragBox = cropper.dragBox;
37 |
38 | util.dispatchEvent(dragBox, 'dblclick');
39 | assert.ok(util.hasClass(dragBox, 'cropper-crop'));
40 | assert.strictEqual(dragBox.dataset.action, 'crop');
41 |
42 | done();
43 | }
44 | });
45 | });
46 |
--------------------------------------------------------------------------------
/test/options/movable.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#movable: true', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(2);
7 |
8 | return new Cropper(image, {
9 | // movable: true,
10 |
11 | ready: function () {
12 | var cropper = this.cropper;
13 | var canvasData = cropper.getCanvasData();
14 | var changedCanvasData = cropper.move(10, 10).getCanvasData();
15 |
16 | assert.strictEqual(changedCanvasData.left, canvasData.left + 10);
17 | assert.strictEqual(changedCanvasData.top, canvasData.top + 10);
18 |
19 | done();
20 | }
21 | });
22 | });
23 |
24 | QUnit.test('options#movable: false', function (assert) {
25 | var done = assert.async();
26 | var util = window.Util;
27 | var image = util.createImage();
28 |
29 | assert.expect(2);
30 |
31 | return new Cropper(image, {
32 | movable: false,
33 |
34 | ready: function () {
35 | var cropper = this.cropper;
36 | var canvasData = cropper.getCanvasData();
37 | var changedCanvasData = cropper.move(10, 10).getCanvasData();
38 |
39 | assert.strictEqual(changedCanvasData.left, canvasData.left);
40 | assert.strictEqual(changedCanvasData.top, canvasData.top);
41 |
42 | done();
43 | }
44 | });
45 | });
46 |
--------------------------------------------------------------------------------
/examples/full-crop-box.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Cropper.js
8 |
9 |
19 |
20 |
21 |
22 |
23 |
Cropper with full crop box
24 |
25 |

26 |
27 |
28 |
29 |
30 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/examples/fixed-crop-box.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Cropper.js
8 |
9 |
19 |
20 |
21 |
22 |
23 |
Cropper with fixed crop box
24 |
25 |

26 |
27 |
28 |
29 |
30 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/test/methods/setCropBoxData.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#setCropBoxData', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(8);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 | var cropBoxData = cropper.getCropBoxData();
12 | var changedCropBoxData = cropper.setCropBoxData({
13 | left: 16,
14 | top: 9
15 | }).getCropBoxData();
16 |
17 | assert.notStrictEqual(changedCropBoxData.left, cropBoxData.left);
18 | assert.notStrictEqual(changedCropBoxData.top, cropBoxData.top);
19 | assert.strictEqual(changedCropBoxData.width, cropBoxData.width);
20 | assert.strictEqual(changedCropBoxData.height, cropBoxData.height);
21 |
22 | cropBoxData = cropper.getCropBoxData();
23 | changedCropBoxData = cropper.setCropBoxData({
24 | width: 320,
25 | height: 180
26 | }).getCropBoxData();
27 |
28 | assert.strictEqual(changedCropBoxData.left, cropBoxData.left);
29 | assert.strictEqual(changedCropBoxData.top, cropBoxData.top);
30 | assert.notStrictEqual(changedCropBoxData.width, cropBoxData.width);
31 | assert.notStrictEqual(changedCropBoxData.height, cropBoxData.height);
32 |
33 | done();
34 | }
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/examples/responsive-container.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Cropper.js
8 |
9 |
19 |
20 |
21 |
22 |
23 |
Cropper with responsive container
24 |
25 |
26 |
27 |
28 |

29 |
30 |
31 |
32 |
33 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/test/options/dragMode.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#dragMode: crop', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(1);
7 |
8 | return new Cropper(image, {
9 | // dragMode: 'crop',
10 |
11 | ready: function () {
12 | var cropper = this.cropper;
13 |
14 | assert.strictEqual(cropper.dragBox.dataset.action, 'crop');
15 |
16 | done();
17 | }
18 | });
19 | });
20 |
21 | QUnit.test('options#dragMode: move', function (assert) {
22 | var done = assert.async();
23 | var util = window.Util;
24 | var image = util.createImage();
25 |
26 | assert.expect(1);
27 |
28 | return new Cropper(image, {
29 | dragMode: 'move',
30 |
31 | ready: function () {
32 | var cropper = this.cropper;
33 |
34 | assert.strictEqual(cropper.dragBox.dataset.action, 'move');
35 |
36 | done();
37 | }
38 | });
39 | });
40 |
41 |
42 | QUnit.test('options#dragMode: none', function (assert) {
43 | var done = assert.async();
44 | var util = window.Util;
45 | var image = util.createImage();
46 |
47 | assert.expect(1);
48 |
49 | return new Cropper(image, {
50 | dragMode: 'none',
51 |
52 | ready: function () {
53 | var cropper = this.cropper;
54 |
55 | assert.strictEqual(cropper.dragBox.dataset.action, 'none');
56 |
57 | done();
58 | }
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/test/options/zoomable.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#zoomable: true', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(3);
7 |
8 | return new Cropper(image, {
9 | // zoomable: true,
10 |
11 | ready: function () {
12 | var cropper = this.cropper;
13 | var canvasData = cropper.getCanvasData();
14 | var changedCanvasData = cropper.zoom(0.1).getCanvasData();
15 |
16 | assert.ok(changedCanvasData.width > canvasData.width);
17 | assert.ok(changedCanvasData.height > canvasData.height);
18 |
19 | done();
20 | },
21 | zoom: function () {
22 | assert.ok(true);
23 | }
24 | });
25 | });
26 |
27 | QUnit.test('options#zoomable: false', function (assert) {
28 | var done = assert.async();
29 | var util = window.Util;
30 | var image = util.createImage();
31 |
32 | assert.expect(2);
33 |
34 | return new Cropper(image, {
35 | zoomable: false,
36 |
37 | ready: function () {
38 | var cropper = this.cropper;
39 | var canvasData = cropper.getCanvasData();
40 | var changedCanvasData = cropper.zoom(0.1).getCanvasData();
41 |
42 | assert.ok(changedCanvasData.width === canvasData.width);
43 | assert.ok(changedCanvasData.height === canvasData.height);
44 |
45 | done();
46 | },
47 | zoom: function () {
48 | assert.ok(false);
49 | }
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/src/js/template.js:
--------------------------------------------------------------------------------
1 | export default (
2 | '' +
3 | '
' +
6 | '
' +
7 | '
' +
8 | '' +
9 | '' +
10 | '' +
11 | '' +
12 | '' +
13 | '' +
14 | '' +
15 | '' +
16 | '' +
17 | '' +
18 | '' +
19 | '' +
20 | '' +
21 | '' +
22 | '' +
23 | '' +
24 | '' +
25 | '
' +
26 | '
'
27 | );
28 |
--------------------------------------------------------------------------------
/test/options/cropstart.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#cropstart', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(1);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var PointerEvent = window.PointerEvent;
11 | var cropper = this.cropper;
12 |
13 | util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerdown' : 'mousedown');
14 | util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerup' : 'mouseup');
15 |
16 | done();
17 | },
18 |
19 | cropstart: function (e) {
20 | assert.strictEqual(e.detail.action, 'crop');
21 | }
22 | });
23 | });
24 |
25 | QUnit.test('options#cropstart: default prevented', function (assert) {
26 | var done = assert.async();
27 | var util = window.Util;
28 | var image = util.createImage();
29 |
30 | assert.expect(0);
31 |
32 | return new Cropper(image, {
33 | ready: function () {
34 | var PointerEvent = window.PointerEvent;
35 | var cropper = this.cropper;
36 |
37 | util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerdown' : 'mousedown');
38 | util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointermove' : 'mousemove');
39 | util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerup' : 'mouseup');
40 |
41 | done();
42 | },
43 |
44 | cropstart: function (e) {
45 | e.preventDefault();
46 | },
47 |
48 | cropmove: function () {
49 | assert.ok(false);
50 | },
51 |
52 | cropend: function () {
53 | assert.ok(false);
54 | }
55 | });
56 | });
57 |
--------------------------------------------------------------------------------
/test/events/cropstart.js:
--------------------------------------------------------------------------------
1 | QUnit.test('events#cropstart', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(1);
7 |
8 | image.addEventListener('ready', function () {
9 | var PointerEvent = window.PointerEvent;
10 | var cropper = this.cropper;
11 |
12 | util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerdown' : 'mousedown');
13 | util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerup' : 'mouseup');
14 |
15 | done();
16 | });
17 |
18 | image.addEventListener('cropstart', function (e) {
19 | assert.strictEqual(e.detail.action, 'crop');
20 | });
21 |
22 | return new Cropper(image);
23 | });
24 |
25 | QUnit.test('events#cropstart: default prevented', function (assert) {
26 | var done = assert.async();
27 | var util = window.Util;
28 | var image = util.createImage();
29 |
30 | assert.expect(0);
31 |
32 | image.addEventListener('ready', function () {
33 | var PointerEvent = window.PointerEvent;
34 | var cropper = this.cropper;
35 |
36 | util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerdown' : 'mousedown');
37 | util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointermove' : 'mousemove');
38 | util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerup' : 'mouseup');
39 |
40 | done();
41 | });
42 |
43 | image.addEventListener('cropstart', function (e) {
44 | e.preventDefault();
45 | });
46 |
47 | image.addEventListener('cropmove', function () {
48 | assert.ok(false);
49 | });
50 |
51 | image.addEventListener('cropend', function () {
52 | assert.ok(false);
53 | });
54 |
55 | return new Cropper(image);
56 | });
57 |
--------------------------------------------------------------------------------
/examples/crop-on-canvas.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Cropper.js
8 |
9 |
19 |
20 |
21 |
22 |
23 |
Crop on canvas with Cropper
24 |
Image
25 |
26 |

27 |
28 |
Canvas
29 |
30 |
31 |
32 |
33 |
34 |
35 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/test/options/checkCrossOrigin.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#checkCrossOrigin: true', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage({
5 | src: 'https://fengyuanchen.github.io/cropperjs/images/picture.jpg'
6 | });
7 |
8 | assert.expect(2);
9 |
10 | return new Cropper(image, {
11 | // checkCrossOrigin: true,
12 |
13 | ready: function () {
14 | var cropper = this.cropper;
15 |
16 | assert.strictEqual(cropper.image.crossOrigin, 'anonymous');
17 | assert.ok(cropper.image.src.indexOf('timestamp') >= 0);
18 |
19 | done();
20 | }
21 | });
22 | });
23 |
24 | QUnit.test('options#checkCrossOrigin: false', function (assert) {
25 | var done = assert.async();
26 | var util = window.Util;
27 | var image = util.createImage({
28 | src: 'https://fengyuanchen.github.io/cropperjs/images/picture.jpg'
29 | });
30 |
31 | assert.expect(2);
32 |
33 | return new Cropper(image, {
34 | checkCrossOrigin: false,
35 |
36 | ready: function () {
37 | var cropper = this.cropper;
38 |
39 | assert.notStrictEqual(cropper.image.crossOrigin, 'anonymous');
40 | assert.ok(cropper.image.src.indexOf('timestamp') < 0);
41 |
42 | done();
43 | }
44 | });
45 | });
46 |
47 | QUnit.test('options#checkCrossOrigin: exists crossOrigin attribute', function (assert) {
48 | var done = assert.async();
49 | var util = window.Util;
50 | var image = util.createImage({
51 | src: 'https://fengyuanchen.github.io/cropperjs/images/picture.jpg',
52 | crossOrigin: 'anonymous'
53 | });
54 |
55 | assert.expect(2);
56 |
57 | return new Cropper(image, {
58 | ready: function () {
59 | var cropper = this.cropper;
60 |
61 | assert.strictEqual(cropper.image.crossOrigin, 'anonymous');
62 | assert.ok(cropper.image.src.indexOf('timestamp') < 0);
63 |
64 | done();
65 | }
66 | });
67 | });
68 |
--------------------------------------------------------------------------------
/test/methods/disable.js:
--------------------------------------------------------------------------------
1 | QUnit.test('methods#disable', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(11);
7 |
8 | return new Cropper(image, {
9 | ready: function () {
10 | var cropper = this.cropper;
11 | var options = cropper.options;
12 | var cropBoxData;
13 | var canvasData;
14 | var imageData;
15 | var action;
16 |
17 | cropper.disable();
18 | assert.ok(cropper.disabled);
19 | assert.ok(util.hasClass(cropper.cropper, 'cropper-disabled'));
20 |
21 | cropBoxData = cropper.getCropBoxData();
22 | cropper.clear();
23 | assert.deepEqual(cropper.getCropBoxData(), cropBoxData);
24 |
25 | imageData = cropper.getImageData();
26 | cropper.move(10, 10);
27 | assert.deepEqual(cropper.getImageData(), imageData);
28 |
29 | cropper.zoom(0.5);
30 | assert.strictEqual(cropper.getImageData().ratio, imageData.ratio);
31 |
32 | cropper.rotate(15);
33 | assert.strictEqual(cropper.getImageData().rotate, imageData.rotate);
34 |
35 | cropper.scale(-1);
36 | assert.strictEqual(cropper.getImageData().scaleX, imageData.scaleX);
37 |
38 | canvasData = cropper.getCanvasData();
39 | cropper.setCanvasData({
40 | width: canvasData.width - 160
41 | });
42 | assert.deepEqual(cropper.getCanvasData(), canvasData);
43 |
44 | cropBoxData = cropper.getCropBoxData();
45 | cropper.setCropBoxData({
46 | height: cropBoxData.height - 90
47 | });
48 | assert.deepEqual(cropper.getCropBoxData(), cropBoxData);
49 |
50 | cropper.setAspectRatio(0.618);
51 | assert.ok(isNaN(options.aspectRatio));
52 |
53 | action = cropper.dragBox.dataset.action;
54 | cropper.setDragMode('none');
55 | assert.strictEqual(cropper.dragBox.dataset.action, action);
56 |
57 | done();
58 | }
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/test/js/main.js:
--------------------------------------------------------------------------------
1 | window.Util = {
2 | isNumber: function (n) {
3 | return typeof n === 'number' && !isNaN(n);
4 | },
5 | isFunction: function (fn) {
6 | return typeof fn === 'function';
7 | },
8 | hasClass: function (element, className) {
9 | return element.classList.contains(className);
10 | },
11 | getByClass: function (element, className) {
12 | return element.getElementsByClassName ?
13 | element.getElementsByClassName(className) :
14 | element.querySelectorAll('.' + className);
15 | },
16 | createImage: function (attrs) {
17 | var container = document.createElement('div');
18 | var image = new Image();
19 | var attr;
20 |
21 | if (typeof attrs !== 'object') {
22 | attrs = {};
23 | }
24 |
25 | if (!attrs.src) {
26 | attrs.src = '../docs/images/picture.jpg';
27 | }
28 |
29 | for (attr in attrs) {
30 | if (attrs.hasOwnProperty(attr)) {
31 | image[attr] = attrs[attr];
32 | }
33 | }
34 |
35 | container.className = 'container';
36 | container.appendChild(image);
37 | document.body.appendChild(container);
38 |
39 | return image;
40 | },
41 | dispatchEvent: function (element, type, data) {
42 | var event;
43 |
44 | if (element.dispatchEvent) {
45 | // Event and CustomEvent on IE9-11 are global objects, not constructors
46 | if (typeof Event === 'function' && typeof CustomEvent === 'function') {
47 | if (!data) {
48 | event = new Event(type, {
49 | bubbles: true,
50 | cancelable: true
51 | });
52 | } else {
53 | event = new CustomEvent(type, {
54 | detail: data,
55 | bubbles: true,
56 | cancelable: true
57 | });
58 | }
59 | } else if (!data) {
60 | event = document.createEvent('Event');
61 | event.initEvent(type, true, true);
62 | } else {
63 | event = document.createEvent('CustomEvent');
64 | event.initCustomEvent(type, true, true, data);
65 | }
66 |
67 | // IE9+
68 | return element.dispatchEvent(event);
69 | } else if (element.fireEvent) {
70 | // IE6-10 (native events only)
71 | return element.fireEvent('on' + type);
72 | }
73 | }
74 | };
75 |
--------------------------------------------------------------------------------
/src/js/constants.js:
--------------------------------------------------------------------------------
1 | export const WINDOW = typeof window !== 'undefined' ? window : {};
2 | export const NAMESPACE = 'cropper';
3 |
4 | // Actions
5 | export const ACTION_ALL = 'all';
6 | export const ACTION_CROP = 'crop';
7 | export const ACTION_MOVE = 'move';
8 | export const ACTION_ZOOM = 'zoom';
9 | export const ACTION_EAST = 'e';
10 | export const ACTION_WEST = 'w';
11 | export const ACTION_SOUTH = 's';
12 | export const ACTION_NORTH = 'n';
13 | export const ACTION_NORTH_EAST = 'ne';
14 | export const ACTION_NORTH_WEST = 'nw';
15 | export const ACTION_SOUTH_EAST = 'se';
16 | export const ACTION_SOUTH_WEST = 'sw';
17 |
18 | // Classes
19 | export const CLASS_CROP = `${NAMESPACE}-crop`;
20 | export const CLASS_DISABLED = `${NAMESPACE}-disabled`;
21 | export const CLASS_HIDDEN = `${NAMESPACE}-hidden`;
22 | export const CLASS_HIDE = `${NAMESPACE}-hide`;
23 | export const CLASS_INVISIBLE = `${NAMESPACE}-invisible`;
24 | export const CLASS_MODAL = `${NAMESPACE}-modal`;
25 | export const CLASS_MOVE = `${NAMESPACE}-move`;
26 |
27 | // Data keys
28 | export const DATA_ACTION = 'action';
29 | export const DATA_PREVIEW = 'preview';
30 |
31 | // Drag modes
32 | export const DRAG_MODE_CROP = 'crop';
33 | export const DRAG_MODE_MOVE = 'move';
34 | export const DRAG_MODE_NONE = 'none';
35 |
36 | // Events
37 | export const EVENT_CROP = 'crop';
38 | export const EVENT_CROP_END = 'cropend';
39 | export const EVENT_CROP_MOVE = 'cropmove';
40 | export const EVENT_CROP_START = 'cropstart';
41 | export const EVENT_DBLCLICK = 'dblclick';
42 | export const EVENT_ERROR = 'error';
43 | export const EVENT_LOAD = 'load';
44 | export const EVENT_POINTER_DOWN = WINDOW.PointerEvent ? 'pointerdown' : 'touchstart mousedown';
45 | export const EVENT_POINTER_MOVE = WINDOW.PointerEvent ? 'pointermove' : 'touchmove mousemove';
46 | export const EVENT_POINTER_UP = WINDOW.PointerEvent ? ' pointerup pointercancel' : 'touchend touchcancel mouseup';
47 | export const EVENT_READY = 'ready';
48 | export const EVENT_RESIZE = 'resize';
49 | export const EVENT_WHEEL = 'wheel mousewheel DOMMouseScroll';
50 | export const EVENT_ZOOM = 'zoom';
51 |
52 | // RegExps
53 | export const REGEXP_ACTIONS = /^(e|w|s|n|se|sw|ne|nw|all|crop|move|zoom)$/;
54 | export const REGEXP_DATA_URL = /^data:/;
55 | export const REGEXP_DATA_URL_JPEG = /^data:image\/jpeg;base64,/;
56 | export const REGEXP_TAG_NAME = /^(img|canvas)$/i;
57 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cropperjs",
3 | "description": "JavaScript image cropper.",
4 | "version": "1.1.3",
5 | "main": "dist/cropper.common.js",
6 | "module": "dist/cropper.esm.js",
7 | "browser": "dist/cropper.js",
8 | "license": "MIT",
9 | "repository": "fengyuanchen/cropperjs",
10 | "homepage": "https://fengyuanchen.github.io/cropperjs",
11 | "author": {
12 | "name": "Chen Fengyuan",
13 | "url": "http://chenfengyuan.com"
14 | },
15 | "files": [
16 | "src",
17 | "dist"
18 | ],
19 | "keywords": [
20 | "image",
21 | "crop",
22 | "cropping",
23 | "move",
24 | "zoom",
25 | "rotate",
26 | "scale",
27 | "cropper",
28 | "cropperjs",
29 | "cropper.js",
30 | "html",
31 | "css",
32 | "javascript",
33 | "front-end",
34 | "web",
35 | "development"
36 | ],
37 | "scripts": {
38 | "build": "npm run build:css && npm run build:js",
39 | "build:css": "postcss src/css/cropper.css -o dist/cropper.css -m",
40 | "build:js": "rollup -c",
41 | "compress": "npm run compress:css && npm run compress:js",
42 | "compress:css": "postcss dist/cropper.css -u cssnano -o dist/cropper.min.css -m",
43 | "compress:js": "uglifyjs dist/cropper.js -o dist/cropper.min.js -c -m --comments /^!/",
44 | "copy": "cpy dist/cropper.css docs/css",
45 | "lint": "eslint src/js --fix",
46 | "release": "npm run lint && npm run build && npm run compress && npm run copy && npm test",
47 | "start": "npm-run-all --parallel watch:*",
48 | "test": "node-qunit-phantomjs test/index.html --timeout 10",
49 | "watch:css": "postcss src/css/cropper.css -o docs/css/cropper.css -m -w",
50 | "watch:js": "rollup -c -m -w"
51 | },
52 | "devDependencies": {
53 | "babel-core": "^6.26.0",
54 | "babel-preset-env": "^1.6.1",
55 | "cpy-cli": "^1.0.1",
56 | "cssnano": "^3.10.0",
57 | "eslint": "^4.9.0",
58 | "eslint-config-airbnb-base": "^12.1.0",
59 | "eslint-plugin-import": "^2.8.0",
60 | "node-qunit-phantomjs": "^1.5.1",
61 | "npm-run-all": "^4.1.1",
62 | "postcss-cli": "^4.1.1",
63 | "postcss-cssnext": "^3.0.2",
64 | "postcss-header": "^1.0.0",
65 | "postcss-url": "^7.1.2",
66 | "rollup": "^0.50.0",
67 | "rollup-plugin-babel": "^3.0.2",
68 | "rollup-watch": "^4.3.1",
69 | "stylefmt": "^6.0.0",
70 | "uglify-js": "^3.1.4"
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Cropper
2 |
3 | ## How to report bugs
4 |
5 | ### Make sure it is a Cropper bug
6 |
7 | Most bugs reported to our bug tracker are actually bugs in user code, not in Cropper code. Keep in mind that just because your code throws an error inside of Cropper, this does *not* mean the bug is a Cropper bug.
8 |
9 | Ask for help first in a discussion forum like [Stack Overflow](http://stackoverflow.com/). You will get much quicker support, and you will help avoid tying up the Cropper team with invalid bug reports.
10 |
11 | ### Disable browser extensions
12 |
13 | Make sure you have reproduced the bug with all browser extensions and add-ons disabled, as these can sometimes cause things to break in interesting and unpredictable ways. Try using incognito, stealth or anonymous browsing modes.
14 |
15 | ### Try the latest version of Cropper
16 |
17 | Bugs in old versions of Cropper may have already been fixed. In order to avoid reporting known issues, make sure you are always testing against the [latest release](https://github.com/fengyuanchen/cropperjs/releases/latest). We cannot fix bugs in older released files, if a bug has been fixed in a subsequent version of Cropper the site should upgrade.
18 |
19 | ### Simplify the test case
20 |
21 | When experiencing a problem, [reduce your code](http://webkit.org/quality/reduction.html) to the bare minimum required to reproduce the issue. This makes it *much* easier to isolate and fix the offending code. Bugs reported without reduced test cases take on average 9001% longer to fix than bugs that are submitted with them, so you really should try to do this if at all possible.
22 |
23 | ### Search for related or duplicate issues
24 |
25 | Go to the [Cropper issue tracker](https://github.com/fengyuanchen/cropperjs/issues) and make sure the problem hasn't already been reported. If not, create a new issue there and include your test case.
26 |
27 | ### Browser support
28 |
29 | Remember that Cropper supports multiple browsers and their versions; any contributed code must work in all of them. You can refer to the [browser support page](https://github.com/fengyuanchen/cropperjs/blob/master/README.md#browser-support) for the current list of supported browsers.
30 |
31 | ## Notes for pull request
32 |
33 | - Run the test suites in the `test` directory first.
34 | - Don't modify any files in the `dist` directory.
35 | - Follow the same code style as the library.
36 |
--------------------------------------------------------------------------------
/examples/a-range-of-aspect-ratio.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Cropper.js
8 |
9 |
19 |
20 |
21 |
22 |
23 |
Cropper with a range of aspect ratio
24 |
25 |

26 |
27 |
28 |
29 |
30 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/test/options/cropBoxResizable.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#cropBoxResizable: true', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(12);
7 |
8 | return new Cropper(image, {
9 | // cropBoxResizable: true,
10 |
11 | ready: function () {
12 | var cropper = this.cropper;
13 | var line = util.getByClass(cropper.cropBox, 'cropper-line');
14 | var point = util.getByClass(cropper.cropBox, 'cropper-point');
15 |
16 | assert.notOk(util.hasClass(line[0], 'cropper-hidden'));
17 | assert.notOk(util.hasClass(line[1], 'cropper-hidden'));
18 | assert.notOk(util.hasClass(line[2], 'cropper-hidden'));
19 | assert.notOk(util.hasClass(line[3], 'cropper-hidden'));
20 | assert.notOk(util.hasClass(point[0], 'cropper-hidden'));
21 | assert.notOk(util.hasClass(point[1], 'cropper-hidden'));
22 | assert.notOk(util.hasClass(point[2], 'cropper-hidden'));
23 | assert.notOk(util.hasClass(point[3], 'cropper-hidden'));
24 | assert.notOk(util.hasClass(point[4], 'cropper-hidden'));
25 | assert.notOk(util.hasClass(point[5], 'cropper-hidden'));
26 | assert.notOk(util.hasClass(point[6], 'cropper-hidden'));
27 | assert.notOk(util.hasClass(point[7], 'cropper-hidden'));
28 |
29 | done();
30 | }
31 | });
32 | });
33 |
34 | QUnit.test('options#cropBoxResizable: false', function (assert) {
35 | var done = assert.async();
36 | var util = window.Util;
37 | var image = util.createImage();
38 |
39 | assert.expect(12);
40 |
41 | return new Cropper(image, {
42 | cropBoxResizable: false,
43 |
44 | ready: function () {
45 | var cropper = this.cropper;
46 | var line = util.getByClass(cropper.cropBox, 'cropper-line');
47 | var point = util.getByClass(cropper.cropBox, 'cropper-point');
48 |
49 | assert.ok(util.hasClass(line[0], 'cropper-hidden'));
50 | assert.ok(util.hasClass(line[1], 'cropper-hidden'));
51 | assert.ok(util.hasClass(line[2], 'cropper-hidden'));
52 | assert.ok(util.hasClass(line[3], 'cropper-hidden'));
53 | assert.ok(util.hasClass(point[0], 'cropper-hidden'));
54 | assert.ok(util.hasClass(point[1], 'cropper-hidden'));
55 | assert.ok(util.hasClass(point[2], 'cropper-hidden'));
56 | assert.ok(util.hasClass(point[3], 'cropper-hidden'));
57 | assert.ok(util.hasClass(point[4], 'cropper-hidden'));
58 | assert.ok(util.hasClass(point[5], 'cropper-hidden'));
59 | assert.ok(util.hasClass(point[6], 'cropper-hidden'));
60 | assert.ok(util.hasClass(point[7], 'cropper-hidden'));
61 |
62 | done();
63 | }
64 | });
65 | });
66 |
--------------------------------------------------------------------------------
/src/js/defaults.js:
--------------------------------------------------------------------------------
1 | import {
2 | DRAG_MODE_CROP,
3 | } from './constants';
4 |
5 | export default {
6 | // Define the view mode of the cropper
7 | viewMode: 0, // 0, 1, 2, 3
8 |
9 | // Define the dragging mode of the cropper
10 | dragMode: DRAG_MODE_CROP, // 'crop', 'move' or 'none'
11 |
12 | // Define the aspect ratio of the crop box
13 | aspectRatio: NaN,
14 |
15 | // An object with the previous cropping result data
16 | data: null,
17 |
18 | // A selector for adding extra containers to preview
19 | preview: '',
20 |
21 | // Re-render the cropper when resize the window
22 | responsive: true,
23 |
24 | // Restore the cropped area after resize the window
25 | restore: true,
26 |
27 | // Check if the current image is a cross-origin image
28 | checkCrossOrigin: true,
29 |
30 | // Check the current image's Exif Orientation information
31 | checkOrientation: true,
32 |
33 | // Show the black modal
34 | modal: true,
35 |
36 | // Show the dashed lines for guiding
37 | guides: true,
38 |
39 | // Show the center indicator for guiding
40 | center: true,
41 |
42 | // Show the white modal to highlight the crop box
43 | highlight: true,
44 |
45 | // Show the grid background
46 | background: true,
47 |
48 | // Enable to crop the image automatically when initialize
49 | autoCrop: true,
50 |
51 | // Define the percentage of automatic cropping area when initializes
52 | autoCropArea: 0.8,
53 |
54 | // Enable to move the image
55 | movable: true,
56 |
57 | // Enable to rotate the image
58 | rotatable: true,
59 |
60 | // Enable to scale the image
61 | scalable: true,
62 |
63 | // Enable to zoom the image
64 | zoomable: true,
65 |
66 | // Enable to zoom the image by dragging touch
67 | zoomOnTouch: true,
68 |
69 | // Enable to zoom the image by wheeling mouse
70 | zoomOnWheel: true,
71 |
72 | // Define zoom ratio when zoom the image by wheeling mouse
73 | wheelZoomRatio: 0.1,
74 |
75 | // Enable to move the crop box
76 | cropBoxMovable: true,
77 |
78 | // Enable to resize the crop box
79 | cropBoxResizable: true,
80 |
81 | // Toggle drag mode between "crop" and "move" when click twice on the cropper
82 | toggleDragModeOnDblclick: true,
83 |
84 | // Size limitation
85 | minCanvasWidth: 0,
86 | minCanvasHeight: 0,
87 | minCropBoxWidth: 0,
88 | minCropBoxHeight: 0,
89 | minContainerWidth: 200,
90 | minContainerHeight: 100,
91 |
92 | // Shortcuts of events
93 | ready: null,
94 | cropstart: null,
95 | cropmove: null,
96 | cropend: null,
97 | crop: null,
98 | zoom: null,
99 | };
100 |
--------------------------------------------------------------------------------
/examples/cropper-in-modal.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Cropper.js
8 |
9 |
10 |
15 |
16 |
17 |
18 |
19 |
Cropper in a Bootstrap modal
20 |
21 |
22 |
25 |
26 |
27 |
28 |
29 |
30 |
36 |
37 |
38 |

39 |
40 |
41 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/test/options/viewMode.js:
--------------------------------------------------------------------------------
1 | QUnit.test('options#viewMode: 0', function (assert) {
2 | var done = assert.async();
3 | var util = window.Util;
4 | var image = util.createImage();
5 |
6 | assert.expect(4);
7 |
8 | return new Cropper(image, {
9 | // viewMode: 0,
10 |
11 | ready: function () {
12 | var cropper = this.cropper;
13 | var canvasData = {
14 | left: 100,
15 | top: 100,
16 | width: 160,
17 | height: 90
18 | };
19 | var changedCanvasData = cropper.setCanvasData(canvasData).getCanvasData();
20 |
21 | assert.strictEqual(changedCanvasData.left, canvasData.left);
22 | assert.strictEqual(changedCanvasData.top, canvasData.top);
23 | assert.strictEqual(changedCanvasData.width, canvasData.width);
24 | assert.strictEqual(changedCanvasData.height, canvasData.height);
25 |
26 | done();
27 | }
28 | });
29 | });
30 |
31 | QUnit.test('options#viewMode: 1', function (assert) {
32 | var done = assert.async();
33 | var util = window.Util;
34 | var image = util.createImage();
35 |
36 | assert.expect(2);
37 |
38 | return new Cropper(image, {
39 | viewMode: 1,
40 |
41 | ready: function () {
42 | var cropper = this.cropper;
43 | var canvasData = cropper.zoom(-0.5).getCanvasData(); // Zoom out
44 | var cropBoxData = cropper.getCropBoxData();
45 |
46 | assert.ok(canvasData.width >= cropBoxData.width);
47 | assert.ok(canvasData.height >= cropBoxData.height);
48 |
49 | done();
50 | }
51 | });
52 | });
53 |
54 | QUnit.test('options#viewMode: 2', function (assert) {
55 | var done = assert.async();
56 | var util = window.Util;
57 | var image = util.createImage();
58 |
59 | assert.expect(1);
60 |
61 | return new Cropper(image, {
62 | viewMode: 2,
63 |
64 | ready: function () {
65 | var cropper = this.cropper;
66 | var canvasData = cropper.zoom(-0.5).getCanvasData(); // Zoom out
67 | var containerData = cropper.getContainerData();
68 |
69 | assert.ok(canvasData.width >= containerData.width ||
70 | canvasData.height >= containerData.height);
71 |
72 | done();
73 | }
74 | });
75 | });
76 |
77 | QUnit.test('options#viewMode: 3', function (assert) {
78 | var done = assert.async();
79 | var util = window.Util;
80 | var image = util.createImage();
81 |
82 | assert.expect(4);
83 |
84 | return new Cropper(image, {
85 | viewMode: 3,
86 |
87 | ready: function () {
88 | var cropper = this.cropper;
89 | var canvasData = cropper.zoom(-0.5).getCanvasData(); // Zoom out
90 | var containerData = cropper.getContainerData();
91 |
92 | assert.ok(canvasData.left <= 0);
93 | assert.ok(canvasData.top <= 0);
94 | assert.ok(canvasData.width >= containerData.width);
95 | assert.ok(canvasData.height >= containerData.height);
96 |
97 | done();
98 | }
99 | });
100 | });
101 |
--------------------------------------------------------------------------------
/examples/crop-a-round-image.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Cropper.js
8 |
9 |
24 |
25 |
26 |
27 |
28 |
Crop a round image
29 |
Image
30 |
31 |

32 |
33 |
Result
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/src/js/events.js:
--------------------------------------------------------------------------------
1 | import {
2 | EVENT_CROP,
3 | EVENT_CROP_END,
4 | EVENT_CROP_MOVE,
5 | EVENT_CROP_START,
6 | EVENT_DBLCLICK,
7 | EVENT_POINTER_DOWN,
8 | EVENT_POINTER_MOVE,
9 | EVENT_POINTER_UP,
10 | EVENT_RESIZE,
11 | EVENT_WHEEL,
12 | EVENT_ZOOM,
13 | } from './constants';
14 | import {
15 | addListener,
16 | isFunction,
17 | proxy,
18 | removeListener,
19 | } from './utilities';
20 |
21 | export default {
22 | bind() {
23 | const { element, options, cropper } = this;
24 |
25 | if (isFunction(options.cropstart)) {
26 | addListener(element, EVENT_CROP_START, options.cropstart);
27 | }
28 |
29 | if (isFunction(options.cropmove)) {
30 | addListener(element, EVENT_CROP_MOVE, options.cropmove);
31 | }
32 |
33 | if (isFunction(options.cropend)) {
34 | addListener(element, EVENT_CROP_END, options.cropend);
35 | }
36 |
37 | if (isFunction(options.crop)) {
38 | addListener(element, EVENT_CROP, options.crop);
39 | }
40 |
41 | if (isFunction(options.zoom)) {
42 | addListener(element, EVENT_ZOOM, options.zoom);
43 | }
44 |
45 | addListener(cropper, EVENT_POINTER_DOWN, (this.onCropStart = proxy(this.cropStart, this)));
46 |
47 | if (options.zoomable && options.zoomOnWheel) {
48 | addListener(cropper, EVENT_WHEEL, (this.onWheel = proxy(this.wheel, this)));
49 | }
50 |
51 | if (options.toggleDragModeOnDblclick) {
52 | addListener(cropper, EVENT_DBLCLICK, (this.onDblclick = proxy(this.dblclick, this)));
53 | }
54 |
55 | addListener(element.ownerDocument, EVENT_POINTER_MOVE, (this.onCropMove = proxy(this.cropMove, this)));
56 | addListener(element.ownerDocument, EVENT_POINTER_UP, (this.onCropEnd = proxy(this.cropEnd, this)));
57 |
58 | if (options.responsive) {
59 | addListener(window, EVENT_RESIZE, (this.onResize = proxy(this.resize, this)));
60 | }
61 | },
62 |
63 | unbind() {
64 | const { element, options, cropper } = this;
65 |
66 | if (isFunction(options.cropstart)) {
67 | removeListener(element, EVENT_CROP_START, options.cropstart);
68 | }
69 |
70 | if (isFunction(options.cropmove)) {
71 | removeListener(element, EVENT_CROP_MOVE, options.cropmove);
72 | }
73 |
74 | if (isFunction(options.cropend)) {
75 | removeListener(element, EVENT_CROP_END, options.cropend);
76 | }
77 |
78 | if (isFunction(options.crop)) {
79 | removeListener(element, EVENT_CROP, options.crop);
80 | }
81 |
82 | if (isFunction(options.zoom)) {
83 | removeListener(element, EVENT_ZOOM, options.zoom);
84 | }
85 |
86 | removeListener(cropper, EVENT_POINTER_DOWN, this.onCropStart);
87 |
88 | if (options.zoomable && options.zoomOnWheel) {
89 | removeListener(cropper, EVENT_WHEEL, this.onWheel);
90 | }
91 |
92 | if (options.toggleDragModeOnDblclick) {
93 | removeListener(cropper, EVENT_DBLCLICK, this.onDblclick);
94 | }
95 |
96 | removeListener(element.ownerDocument, EVENT_POINTER_MOVE, this.onCropMove);
97 | removeListener(element.ownerDocument, EVENT_POINTER_UP, this.onCropEnd);
98 |
99 | if (options.responsive) {
100 | removeListener(window, EVENT_RESIZE, this.onResize);
101 | }
102 | },
103 | };
104 |
--------------------------------------------------------------------------------
/dist/cropper.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Cropper.js v1.1.3
3 | * https://github.com/fengyuanchen/cropperjs
4 | *
5 | * Copyright (c) 2015-2017 Chen Fengyuan
6 | * Released under the MIT license
7 | *
8 | * Date: 2017-10-21T09:27:29.883Z
9 | */.cropper-container{direction:ltr;font-size:0;line-height:0;position:relative;-ms-touch-action:none;touch-action:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.cropper-container img{display:block;height:100%;image-orientation:0deg;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.cropper-canvas,.cropper-crop-box,.cropper-drag-box,.cropper-modal,.cropper-wrap-box{bottom:0;left:0;position:absolute;right:0;top:0}.cropper-canvas,.cropper-wrap-box{overflow:hidden}.cropper-drag-box{background-color:#fff;opacity:0}.cropper-modal{background-color:#000;opacity:.5}.cropper-view-box{display:block;height:100%;outline-color:rgba(51,153,255,.75);outline:1px solid #39f;overflow:hidden;width:100%}.cropper-dashed{border:0 dashed #eee;display:block;opacity:.5;position:absolute}.cropper-dashed.dashed-h{border-bottom-width:1px;border-top-width:1px;height:33.33333%;left:0;top:33.33333%;width:100%}.cropper-dashed.dashed-v{border-left-width:1px;border-right-width:1px;height:100%;left:33.33333%;top:0;width:33.33333%}.cropper-center{display:block;height:0;left:50%;opacity:.75;position:absolute;top:50%;width:0}.cropper-center:after,.cropper-center:before{background-color:#eee;content:" ";display:block;position:absolute}.cropper-center:before{height:1px;left:-3px;top:0;width:7px}.cropper-center:after{height:7px;left:0;top:-3px;width:1px}.cropper-face,.cropper-line,.cropper-point{display:block;height:100%;opacity:.1;position:absolute;width:100%}.cropper-face{background-color:#fff;left:0;top:0}.cropper-line{background-color:#39f}.cropper-line.line-e{cursor:e-resize;right:-3px;top:0;width:5px}.cropper-line.line-n{cursor:n-resize;height:5px;left:0;top:-3px}.cropper-line.line-w{cursor:w-resize;left:-3px;top:0;width:5px}.cropper-line.line-s{bottom:-3px;cursor:s-resize;height:5px;left:0}.cropper-point{background-color:#39f;height:5px;opacity:.75;width:5px}.cropper-point.point-e{cursor:e-resize;margin-top:-3px;right:-3px;top:50%}.cropper-point.point-n{cursor:n-resize;left:50%;margin-left:-3px;top:-3px}.cropper-point.point-w{cursor:w-resize;left:-3px;margin-top:-3px;top:50%}.cropper-point.point-s{bottom:-3px;cursor:s-resize;left:50%;margin-left:-3px}.cropper-point.point-ne{cursor:ne-resize;right:-3px;top:-3px}.cropper-point.point-nw{cursor:nw-resize;left:-3px;top:-3px}.cropper-point.point-sw{bottom:-3px;cursor:sw-resize;left:-3px}.cropper-point.point-se{bottom:-3px;cursor:se-resize;height:20px;opacity:1;right:-3px;width:20px}@media (min-width:768px){.cropper-point.point-se{height:15px;width:15px}}@media (min-width:992px){.cropper-point.point-se{height:10px;width:10px}}@media (min-width:1200px){.cropper-point.point-se{height:5px;opacity:.75;width:5px}}.cropper-point.point-se:before{background-color:#39f;bottom:-50%;content:" ";display:block;height:200%;opacity:0;position:absolute;right:-50%;width:200%}.cropper-invisible{opacity:0}.cropper-bg{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC")}.cropper-hide{display:block;height:0;position:absolute;width:0}.cropper-hidden{display:none!important}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed}
10 | /*# sourceMappingURL=cropper.min.css.map */
--------------------------------------------------------------------------------
/examples/customize-preview.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Cropper.js
8 |
9 |
44 |
45 |
46 |
47 |
48 |
Customize preview for Cropper
49 |
50 |
51 |

52 |
53 |
56 |
59 |
62 |
63 |
64 |
65 |
66 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/examples/mask-an-image.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Cropper.js
9 |
10 |
27 |
28 |
29 |
30 |
31 |
32 |
Mask an image (Redaction)
33 |
Image
34 |
35 |

36 |
37 |
Result
38 |
39 |
40 |
41 |
42 |
43 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Cropper.js
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/src/js/preview.js:
--------------------------------------------------------------------------------
1 | import {
2 | DATA_PREVIEW,
3 | } from './constants';
4 | import {
5 | each,
6 | empty,
7 | extend,
8 | getData,
9 | getTransforms,
10 | removeData,
11 | setData,
12 | setStyle,
13 | } from './utilities';
14 |
15 | export default {
16 | initPreview() {
17 | const { crossOrigin } = this;
18 | const { preview } = this.options;
19 | const url = crossOrigin ? this.crossOriginUrl : this.url;
20 | const image = document.createElement('img');
21 |
22 | if (crossOrigin) {
23 | image.crossOrigin = crossOrigin;
24 | }
25 |
26 | image.src = url;
27 | this.viewBox.appendChild(image);
28 | this.image2 = image;
29 |
30 | if (!preview) {
31 | return;
32 | }
33 |
34 | const previews = preview.querySelector ? [preview] : document.querySelectorAll(preview);
35 |
36 | this.previews = previews;
37 |
38 | each(previews, (element) => {
39 | const img = document.createElement('img');
40 |
41 | // Save the original size for recover
42 | setData(element, DATA_PREVIEW, {
43 | width: element.offsetWidth,
44 | height: element.offsetHeight,
45 | html: element.innerHTML,
46 | });
47 |
48 | if (crossOrigin) {
49 | img.crossOrigin = crossOrigin;
50 | }
51 |
52 | img.src = url;
53 |
54 | /**
55 | * Override img element styles
56 | * Add `display:block` to avoid margin top issue
57 | * Add `height:auto` to override `height` attribute on IE8
58 | * (Occur only when margin-top <= -height)
59 | */
60 | img.style.cssText = (
61 | 'display:block;' +
62 | 'width:100%;' +
63 | 'height:auto;' +
64 | 'min-width:0!important;' +
65 | 'min-height:0!important;' +
66 | 'max-width:none!important;' +
67 | 'max-height:none!important;' +
68 | 'image-orientation:0deg!important;"'
69 | );
70 |
71 | empty(element);
72 | element.appendChild(img);
73 | });
74 | },
75 |
76 | resetPreview() {
77 | each(this.previews, (element) => {
78 | const data = getData(element, DATA_PREVIEW);
79 |
80 | setStyle(element, {
81 | width: data.width,
82 | height: data.height,
83 | });
84 |
85 | element.innerHTML = data.html;
86 | removeData(element, DATA_PREVIEW);
87 | });
88 | },
89 |
90 | preview() {
91 | const { imageData, canvasData, cropBoxData } = this;
92 | const { width: cropBoxWidth, height: cropBoxHeight } = cropBoxData;
93 | const { width, height } = imageData;
94 | const left = cropBoxData.left - canvasData.left - imageData.left;
95 | const top = cropBoxData.top - canvasData.top - imageData.top;
96 |
97 | if (!this.cropped || this.disabled) {
98 | return;
99 | }
100 |
101 | setStyle(this.image2, extend({
102 | width,
103 | height,
104 | }, getTransforms(extend({
105 | translateX: -left,
106 | translateY: -top,
107 | }, imageData))));
108 |
109 | each(this.previews, (element) => {
110 | const data = getData(element, DATA_PREVIEW);
111 | const originalWidth = data.width;
112 | const originalHeight = data.height;
113 | let newWidth = originalWidth;
114 | let newHeight = originalHeight;
115 | let ratio = 1;
116 |
117 | if (cropBoxWidth) {
118 | ratio = originalWidth / cropBoxWidth;
119 | newHeight = cropBoxHeight * ratio;
120 | }
121 |
122 | if (cropBoxHeight && newHeight > originalHeight) {
123 | ratio = originalHeight / cropBoxHeight;
124 | newWidth = cropBoxWidth * ratio;
125 | newHeight = originalHeight;
126 | }
127 |
128 | setStyle(element, {
129 | width: newWidth,
130 | height: newHeight,
131 | });
132 |
133 | setStyle(element.getElementsByTagName('img')[0], extend({
134 | width: width * ratio,
135 | height: height * ratio,
136 | }, getTransforms(extend({
137 | translateX: -left * ratio,
138 | translateY: -top * ratio,
139 | }, imageData))));
140 | });
141 | },
142 | };
143 |
--------------------------------------------------------------------------------
/docs/css/main.css:
--------------------------------------------------------------------------------
1 | .btn {
2 | padding-left: .75rem;
3 | padding-right: .75rem;
4 | }
5 |
6 | label.btn {
7 | margin-bottom: 0;
8 | }
9 |
10 | .d-flex > .btn {
11 | flex: 1;
12 | }
13 |
14 | .carbonads {
15 | border-radius: .25rem;
16 | border: 1px solid #ccc;
17 | font-size: .875rem;
18 | overflow: hidden;
19 | padding: 1rem;
20 | }
21 |
22 | .carbon-wrap {
23 | overflow: hidden;
24 | }
25 |
26 | .carbon-img {
27 | clear: left;
28 | display: block;
29 | float: left;
30 | }
31 |
32 | .carbon-text,
33 | .carbon-poweredby {
34 | display: block;
35 | margin-left: 140px;
36 | }
37 |
38 | .carbon-text,
39 | .carbon-text:hover,
40 | .carbon-text:focus {
41 | color: #fff;
42 | text-decoration: none;
43 | }
44 |
45 | .carbon-poweredby,
46 | .carbon-poweredby:hover,
47 | .carbon-poweredby:focus {
48 | color: #ddd;
49 | text-decoration: none;
50 | }
51 |
52 | @media (min-width: 768px) {
53 | .carbonads {
54 | float: right;
55 | margin-bottom: -1rem;
56 | margin-top: -1rem;
57 | max-width: 360px;
58 | }
59 | }
60 |
61 | .footer {
62 | font-size: .875rem;
63 | }
64 |
65 | .heart {
66 | color: #ddd;
67 | display: block;
68 | height: 2rem;
69 | line-height: 2rem;
70 | margin-bottom: 0;
71 | margin-top: 1rem;
72 | position: relative;
73 | text-align: center;
74 | width: 100%;
75 | }
76 |
77 | .heart:hover {
78 | color: #ff4136;
79 | }
80 |
81 | .heart::before {
82 | border-top: 1px solid #eee;
83 | content: " ";
84 | display: block;
85 | height: 0;
86 | left: 0;
87 | position: absolute;
88 | right: 0;
89 | top: 50%;
90 | }
91 |
92 | .heart::after {
93 | background-color: #fff;
94 | content: "♥";
95 | padding-left: .5rem;
96 | padding-right: .5rem;
97 | position: relative;
98 | z-index: 1;
99 | }
100 |
101 | .img-container,
102 | .img-preview {
103 | background-color: #f7f7f7;
104 | text-align: center;
105 | width: 100%;
106 | }
107 |
108 | .img-container {
109 | margin-bottom: 1rem;
110 | max-height: 497px;
111 | min-height: 200px;
112 | }
113 |
114 | @media (min-width: 768px) {
115 | .img-container {
116 | min-height: 497px;
117 | }
118 | }
119 |
120 | .img-container > img {
121 | max-width: 100%;
122 | }
123 |
124 | .docs-preview {
125 | margin-right: -1rem;
126 | }
127 |
128 | .img-preview {
129 | float: left;
130 | margin-bottom: .5rem;
131 | margin-right: .5rem;
132 | overflow: hidden;
133 | }
134 |
135 | .img-preview > img {
136 | max-width: 100%;
137 | }
138 |
139 | .preview-lg {
140 | height: 9rem;
141 | width: 16rem;
142 | }
143 |
144 | .preview-md {
145 | height: 4.5rem;
146 | width: 8rem;
147 | }
148 |
149 | .preview-sm {
150 | height: 2.25rem;
151 | width: 4rem;
152 | }
153 |
154 | .preview-xs {
155 | height: 1.125rem;
156 | margin-right: 0;
157 | width: 2rem;
158 | }
159 |
160 | .docs-data > .input-group {
161 | margin-bottom: .5rem;
162 | }
163 |
164 | .docs-data > .input-group > label {
165 | justify-content: center;
166 | min-width: 5rem;
167 | }
168 |
169 | .docs-data > .input-group > span {
170 | justify-content: center;
171 | min-width: 3rem;
172 | }
173 |
174 | .docs-buttons > .btn,
175 | .docs-buttons > .btn-group,
176 | .docs-buttons > .form-control {
177 | margin-bottom: .5rem;
178 | margin-right: .25rem;
179 | }
180 |
181 | .docs-toggles > .btn,
182 | .docs-toggles > .btn-group,
183 | .docs-toggles > .dropdown {
184 | margin-bottom: .5rem;
185 | }
186 |
187 | .docs-tooltip {
188 | display: block;
189 | margin: -.5rem -.75rem;
190 | padding: .5rem .75rem;
191 | }
192 |
193 | .docs-tooltip > .icon {
194 | margin: 0 -.25rem;
195 | vertical-align: top;
196 | }
197 |
198 | .tooltip-inner {
199 | white-space: normal;
200 | }
201 |
202 | .btn-upload .tooltip-inner,
203 | .btn-toggle .tooltip-inner {
204 | white-space: nowrap;
205 | }
206 |
207 | .btn-toggle {
208 | padding: .5rem;
209 | }
210 |
211 | .btn-toggle > .docs-tooltip {
212 | margin: -.5rem;
213 | padding: .5rem;
214 | }
215 |
216 | @media (max-width: 400px) {
217 | .btn-group-crop {
218 | margin-right: -1rem!important;
219 | }
220 |
221 | .btn-group-crop > .btn {
222 | padding-left: .5rem;
223 | padding-right: .5rem;
224 | }
225 |
226 | .btn-group-crop .docs-tooltip {
227 | margin-left: -.5rem;
228 | margin-right: -.5rem;
229 | padding-left: .5rem;
230 | padding-right: .5rem;
231 | }
232 | }
233 |
234 | .docs-options .dropdown-menu {
235 | width: 100%;
236 | }
237 |
238 | .docs-options .dropdown-menu > li {
239 | font-size: .875rem;
240 | padding-left: 1rem;
241 | padding-right: 1rem;
242 | }
243 |
244 | .docs-options .dropdown-menu > li:hover {
245 | background-color: #f7f7f7;
246 | }
247 |
248 | .docs-options .dropdown-menu > li > label {
249 | display: block;
250 | }
251 |
252 | .docs-cropped .modal-body {
253 | text-align: center;
254 | }
255 |
256 | .docs-cropped .modal-body > img,
257 | .docs-cropped .modal-body > canvas {
258 | max-width: 100%;
259 | }
260 |
--------------------------------------------------------------------------------
/src/js/handlers.js:
--------------------------------------------------------------------------------
1 | import {
2 | ACTION_CROP,
3 | ACTION_ZOOM,
4 | CLASS_CROP,
5 | CLASS_MODAL,
6 | DATA_ACTION,
7 | DRAG_MODE_CROP,
8 | DRAG_MODE_MOVE,
9 | DRAG_MODE_NONE,
10 | EVENT_CROP_END,
11 | EVENT_CROP_MOVE,
12 | EVENT_CROP_START,
13 | REGEXP_ACTIONS,
14 | } from './constants';
15 | import {
16 | addClass,
17 | dispatchEvent,
18 | each,
19 | extend,
20 | getData,
21 | getPointer,
22 | hasClass,
23 | toggleClass,
24 | } from './utilities';
25 |
26 | export default {
27 | resize() {
28 | const { options, container, containerData } = this;
29 | const minContainerWidth = Number(options.minContainerWidth) || 200;
30 | const minContainerHeight = Number(options.minContainerHeight) || 100;
31 |
32 | if (this.disabled || containerData.width <= minContainerWidth ||
33 | containerData.height <= minContainerHeight) {
34 | return;
35 | }
36 |
37 | const ratio = container.offsetWidth / containerData.width;
38 |
39 | // Resize when width changed or height changed
40 | if (ratio !== 1 || container.offsetHeight !== containerData.height) {
41 | let canvasData;
42 | let cropBoxData;
43 |
44 | if (options.restore) {
45 | canvasData = this.getCanvasData();
46 | cropBoxData = this.getCropBoxData();
47 | }
48 |
49 | this.render();
50 |
51 | if (options.restore) {
52 | this.setCanvasData(each(canvasData, (n, i) => {
53 | canvasData[i] = n * ratio;
54 | }));
55 | this.setCropBoxData(each(cropBoxData, (n, i) => {
56 | cropBoxData[i] = n * ratio;
57 | }));
58 | }
59 | }
60 | },
61 |
62 | dblclick() {
63 | if (this.disabled || this.options.dragMode === DRAG_MODE_NONE) {
64 | return;
65 | }
66 |
67 | this.setDragMode(hasClass(this.dragBox, CLASS_CROP) ? DRAG_MODE_MOVE : DRAG_MODE_CROP);
68 | },
69 |
70 | wheel(e) {
71 | const ratio = Number(this.options.wheelZoomRatio) || 0.1;
72 | let delta = 1;
73 |
74 | if (this.disabled) {
75 | return;
76 | }
77 |
78 | e.preventDefault();
79 |
80 | // Limit wheel speed to prevent zoom too fast (#21)
81 | if (this.wheeling) {
82 | return;
83 | }
84 |
85 | this.wheeling = true;
86 |
87 | setTimeout(() => {
88 | this.wheeling = false;
89 | }, 50);
90 |
91 | if (e.deltaY) {
92 | delta = e.deltaY > 0 ? 1 : -1;
93 | } else if (e.wheelDelta) {
94 | delta = -e.wheelDelta / 120;
95 | } else if (e.detail) {
96 | delta = e.detail > 0 ? 1 : -1;
97 | }
98 |
99 | this.zoom(-delta * ratio, e);
100 | },
101 |
102 | cropStart(e) {
103 | if (this.disabled) {
104 | return;
105 | }
106 |
107 | const { options, pointers } = this;
108 | let action;
109 |
110 | if (e.changedTouches) {
111 | // Handle touch event
112 | each(e.changedTouches, (touch) => {
113 | pointers[touch.identifier] = getPointer(touch);
114 | });
115 | } else {
116 | // Handle mouse event and pointer event
117 | pointers[e.pointerId || 0] = getPointer(e);
118 | }
119 |
120 | if (Object.keys(pointers).length > 1 && options.zoomable && options.zoomOnTouch) {
121 | action = ACTION_ZOOM;
122 | } else {
123 | action = getData(e.target, DATA_ACTION);
124 | }
125 |
126 | if (!REGEXP_ACTIONS.test(action)) {
127 | return;
128 | }
129 |
130 | if (dispatchEvent(this.element, EVENT_CROP_START, {
131 | originalEvent: e,
132 | action,
133 | }) === false) {
134 | return;
135 | }
136 |
137 | e.preventDefault();
138 |
139 | this.action = action;
140 | this.cropping = false;
141 |
142 | if (action === ACTION_CROP) {
143 | this.cropping = true;
144 | addClass(this.dragBox, CLASS_MODAL);
145 | }
146 | },
147 |
148 | cropMove(e) {
149 | const { action } = this;
150 |
151 | if (this.disabled || !action) {
152 | return;
153 | }
154 |
155 | const { pointers } = this;
156 |
157 | e.preventDefault();
158 |
159 | if (dispatchEvent(this.element, EVENT_CROP_MOVE, {
160 | originalEvent: e,
161 | action,
162 | }) === false) {
163 | return;
164 | }
165 |
166 | if (e.changedTouches) {
167 | each(e.changedTouches, (touch) => {
168 | extend(pointers[touch.identifier], getPointer(touch, true));
169 | });
170 | } else {
171 | extend(pointers[e.pointerId || 0], getPointer(e, true));
172 | }
173 |
174 | this.change(e);
175 | },
176 |
177 | cropEnd(e) {
178 | if (this.disabled) {
179 | return;
180 | }
181 |
182 | const { action, pointers } = this;
183 |
184 | if (e.changedTouches) {
185 | each(e.changedTouches, (touch) => {
186 | delete pointers[touch.identifier];
187 | });
188 | } else {
189 | delete pointers[e.pointerId || 0];
190 | }
191 |
192 | if (!action) {
193 | return;
194 | }
195 |
196 | e.preventDefault();
197 |
198 | if (!Object.keys(pointers).length) {
199 | this.action = '';
200 | }
201 |
202 | if (this.cropping) {
203 | this.cropping = false;
204 | toggleClass(this.dragBox, CLASS_MODAL, this.cropped && this.options.modal);
205 | }
206 |
207 | dispatchEvent(this.element, EVENT_CROP_END, {
208 | originalEvent: e,
209 | action,
210 | });
211 | },
212 | };
213 |
--------------------------------------------------------------------------------
/src/css/cropper.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --blue: #39f;
3 | }
4 |
5 | .cropper-container {
6 | direction: ltr;
7 | font-size: 0;
8 | line-height: 0;
9 | position: relative;
10 | touch-action: none;
11 | user-select: none;
12 |
13 | & img {
14 | /* Avoid margin top issue (Occur only when margin-top <= -height) */
15 | display: block;
16 | height: 100%;
17 | image-orientation: 0deg;
18 | max-height: none !important;
19 | max-width: none !important;
20 | min-height: 0 !important;
21 | min-width: 0 !important;
22 | width: 100%;
23 | }
24 | }
25 |
26 | .cropper-wrap-box,
27 | .cropper-canvas,
28 | .cropper-drag-box,
29 | .cropper-crop-box,
30 | .cropper-modal {
31 | bottom: 0;
32 | left: 0;
33 | position: absolute;
34 | right: 0;
35 | top: 0;
36 | }
37 |
38 | .cropper-wrap-box,
39 | .cropper-canvas {
40 | overflow: hidden;
41 | }
42 |
43 | .cropper-drag-box {
44 | background-color: #fff;
45 | opacity: 0;
46 | }
47 |
48 | .cropper-modal {
49 | background-color: #000;
50 | opacity: .5;
51 | }
52 |
53 | .cropper-view-box {
54 | display: block;
55 | height: 100%;
56 | outline-color: color(var(--blue) alpha(75%));
57 | outline: 1px solid var(--blue);
58 | overflow: hidden;
59 | width: 100%;
60 | }
61 |
62 | .cropper-dashed {
63 | border: 0 dashed #eee;
64 | display: block;
65 | opacity: .5;
66 | position: absolute;
67 |
68 | &.dashed-h {
69 | border-bottom-width: 1px;
70 | border-top-width: 1px;
71 | height: calc(100 / 3)%;
72 | left: 0;
73 | top: calc(100 / 3)%;
74 | width: 100%;
75 | }
76 |
77 | &.dashed-v {
78 | border-left-width: 1px;
79 | border-right-width: 1px;
80 | height: 100%;
81 | left: calc(100 / 3)%;
82 | top: 0;
83 | width: calc(100 / 3)%;
84 | }
85 | }
86 |
87 | .cropper-center {
88 | display: block;
89 | height: 0;
90 | left: 50%;
91 | opacity: .75;
92 | position: absolute;
93 | top: 50%;
94 | width: 0;
95 |
96 | &:before,
97 | &:after {
98 | background-color: #eee;
99 | content: ' ';
100 | display: block;
101 | position: absolute;
102 | }
103 |
104 | &:before {
105 | height: 1px;
106 | left: -3px;
107 | top: 0;
108 | width: 7px;
109 | }
110 |
111 | &:after {
112 | height: 7px;
113 | left: 0;
114 | top: -3px;
115 | width: 1px;
116 | }
117 | }
118 |
119 | .cropper-face,
120 | .cropper-line,
121 | .cropper-point {
122 | display: block;
123 | height: 100%;
124 | opacity: .1;
125 | position: absolute;
126 | width: 100%;
127 | }
128 |
129 | .cropper-face {
130 | background-color: #fff;
131 | left: 0;
132 | top: 0;
133 | }
134 |
135 | .cropper-line {
136 | background-color: var(--blue);
137 |
138 | &.line-e {
139 | cursor: e-resize;
140 | right: -3px;
141 | top: 0;
142 | width: 5px;
143 | }
144 |
145 | &.line-n {
146 | cursor: n-resize;
147 | height: 5px;
148 | left: 0;
149 | top: -3px;
150 | }
151 |
152 | &.line-w {
153 | cursor: w-resize;
154 | left: -3px;
155 | top: 0;
156 | width: 5px;
157 | }
158 |
159 | &.line-s {
160 | bottom: -3px;
161 | cursor: s-resize;
162 | height: 5px;
163 | left: 0;
164 | }
165 | }
166 |
167 | .cropper-point {
168 | background-color: var(--blue);
169 | height: 5px;
170 | opacity: .75;
171 | width: 5px;
172 |
173 | &.point-e {
174 | cursor: e-resize;
175 | margin-top: -3px;
176 | right: -3px;
177 | top: 50%;
178 | }
179 |
180 | &.point-n {
181 | cursor: n-resize;
182 | left: 50%;
183 | margin-left: -3px;
184 | top: -3px;
185 | }
186 |
187 | &.point-w {
188 | cursor: w-resize;
189 | left: -3px;
190 | margin-top: -3px;
191 | top: 50%;
192 | }
193 |
194 | &.point-s {
195 | bottom: -3px;
196 | cursor: s-resize;
197 | left: 50%;
198 | margin-left: -3px;
199 | }
200 |
201 | &.point-ne {
202 | cursor: ne-resize;
203 | right: -3px;
204 | top: -3px;
205 | }
206 |
207 | &.point-nw {
208 | cursor: nw-resize;
209 | left: -3px;
210 | top: -3px;
211 | }
212 |
213 | &.point-sw {
214 | bottom: -3px;
215 | cursor: sw-resize;
216 | left: -3px;
217 | }
218 |
219 | &.point-se {
220 | bottom: -3px;
221 | cursor: se-resize;
222 | height: 20px;
223 | opacity: 1;
224 | right: -3px;
225 | width: 20px;
226 |
227 | @media (min-width: 768px) {
228 | height: 15px;
229 | width: 15px;
230 | }
231 |
232 | @media (min-width: 992px) {
233 | height: 10px;
234 | width: 10px;
235 | }
236 |
237 | @media (min-width: 1200px) {
238 | height: 5px;
239 | opacity: .75;
240 | width: 5px;
241 | }
242 | }
243 |
244 | &.point-se:before {
245 | background-color: var(--blue);
246 | bottom: -50%;
247 | content: ' ';
248 | display: block;
249 | height: 200%;
250 | opacity: 0;
251 | position: absolute;
252 | right: -50%;
253 | width: 200%;
254 | }
255 | }
256 |
257 | .cropper-invisible {
258 | opacity: 0;
259 | }
260 |
261 | .cropper-bg {
262 | background-image: url('../images/bg.png');
263 | }
264 |
265 | .cropper-hide {
266 | display: block;
267 | height: 0;
268 | position: absolute;
269 | width: 0;
270 | }
271 |
272 | .cropper-hidden {
273 | display: none !important;
274 | }
275 |
276 | .cropper-move {
277 | cursor: move;
278 | }
279 |
280 | .cropper-crop {
281 | cursor: crosshair;
282 | }
283 |
284 | .cropper-disabled .cropper-drag-box,
285 | .cropper-disabled .cropper-face,
286 | .cropper-disabled .cropper-line,
287 | .cropper-disabled .cropper-point {
288 | cursor: not-allowed;
289 | }
290 |
291 |
--------------------------------------------------------------------------------
/dist/cropper.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Cropper.js v1.1.3
3 | * https://github.com/fengyuanchen/cropperjs
4 | *
5 | * Copyright (c) 2015-2017 Chen Fengyuan
6 | * Released under the MIT license
7 | *
8 | * Date: 2017-10-21T09:27:29.883Z
9 | */
10 |
11 | .cropper-container {
12 | direction: ltr;
13 | font-size: 0;
14 | line-height: 0;
15 | position: relative;
16 | -ms-touch-action: none;
17 | touch-action: none;
18 | -webkit-user-select: none;
19 | -moz-user-select: none;
20 | -ms-user-select: none;
21 | user-select: none;
22 | }
23 |
24 | .cropper-container img {/*Avoid margin top issue (Occur only when margin-top <= -height)
25 | */
26 | display: block;
27 | height: 100%;
28 | image-orientation: 0deg;
29 | max-height: none !important;
30 | max-width: none !important;
31 | min-height: 0 !important;
32 | min-width: 0 !important;
33 | width: 100%;
34 | }
35 |
36 | .cropper-wrap-box,
37 | .cropper-canvas,
38 | .cropper-drag-box,
39 | .cropper-crop-box,
40 | .cropper-modal {
41 | bottom: 0;
42 | left: 0;
43 | position: absolute;
44 | right: 0;
45 | top: 0;
46 | }
47 |
48 | .cropper-wrap-box,
49 | .cropper-canvas {
50 | overflow: hidden;
51 | }
52 |
53 | .cropper-drag-box {
54 | background-color: #fff;
55 | opacity: 0;
56 | }
57 |
58 | .cropper-modal {
59 | background-color: #000;
60 | opacity: .5;
61 | }
62 |
63 | .cropper-view-box {
64 | display: block;
65 | height: 100%;
66 | outline-color: rgba(51, 153, 255, 0.75);
67 | outline: 1px solid #39f;
68 | overflow: hidden;
69 | width: 100%;
70 | }
71 |
72 | .cropper-dashed {
73 | border: 0 dashed #eee;
74 | display: block;
75 | opacity: .5;
76 | position: absolute;
77 | }
78 |
79 | .cropper-dashed.dashed-h {
80 | border-bottom-width: 1px;
81 | border-top-width: 1px;
82 | height: 33.33333%;
83 | left: 0;
84 | top: 33.33333%;
85 | width: 100%;
86 | }
87 |
88 | .cropper-dashed.dashed-v {
89 | border-left-width: 1px;
90 | border-right-width: 1px;
91 | height: 100%;
92 | left: 33.33333%;
93 | top: 0;
94 | width: 33.33333%;
95 | }
96 |
97 | .cropper-center {
98 | display: block;
99 | height: 0;
100 | left: 50%;
101 | opacity: .75;
102 | position: absolute;
103 | top: 50%;
104 | width: 0;
105 | }
106 |
107 | .cropper-center:before,
108 | .cropper-center:after {
109 | background-color: #eee;
110 | content: ' ';
111 | display: block;
112 | position: absolute;
113 | }
114 |
115 | .cropper-center:before {
116 | height: 1px;
117 | left: -3px;
118 | top: 0;
119 | width: 7px;
120 | }
121 |
122 | .cropper-center:after {
123 | height: 7px;
124 | left: 0;
125 | top: -3px;
126 | width: 1px;
127 | }
128 |
129 | .cropper-face,
130 | .cropper-line,
131 | .cropper-point {
132 | display: block;
133 | height: 100%;
134 | opacity: .1;
135 | position: absolute;
136 | width: 100%;
137 | }
138 |
139 | .cropper-face {
140 | background-color: #fff;
141 | left: 0;
142 | top: 0;
143 | }
144 |
145 | .cropper-line {
146 | background-color: #39f;
147 | }
148 |
149 | .cropper-line.line-e {
150 | cursor: e-resize;
151 | right: -3px;
152 | top: 0;
153 | width: 5px;
154 | }
155 |
156 | .cropper-line.line-n {
157 | cursor: n-resize;
158 | height: 5px;
159 | left: 0;
160 | top: -3px;
161 | }
162 |
163 | .cropper-line.line-w {
164 | cursor: w-resize;
165 | left: -3px;
166 | top: 0;
167 | width: 5px;
168 | }
169 |
170 | .cropper-line.line-s {
171 | bottom: -3px;
172 | cursor: s-resize;
173 | height: 5px;
174 | left: 0;
175 | }
176 |
177 | .cropper-point {
178 | background-color: #39f;
179 | height: 5px;
180 | opacity: .75;
181 | width: 5px;
182 | }
183 |
184 | .cropper-point.point-e {
185 | cursor: e-resize;
186 | margin-top: -3px;
187 | right: -3px;
188 | top: 50%;
189 | }
190 |
191 | .cropper-point.point-n {
192 | cursor: n-resize;
193 | left: 50%;
194 | margin-left: -3px;
195 | top: -3px;
196 | }
197 |
198 | .cropper-point.point-w {
199 | cursor: w-resize;
200 | left: -3px;
201 | margin-top: -3px;
202 | top: 50%;
203 | }
204 |
205 | .cropper-point.point-s {
206 | bottom: -3px;
207 | cursor: s-resize;
208 | left: 50%;
209 | margin-left: -3px;
210 | }
211 |
212 | .cropper-point.point-ne {
213 | cursor: ne-resize;
214 | right: -3px;
215 | top: -3px;
216 | }
217 |
218 | .cropper-point.point-nw {
219 | cursor: nw-resize;
220 | left: -3px;
221 | top: -3px;
222 | }
223 |
224 | .cropper-point.point-sw {
225 | bottom: -3px;
226 | cursor: sw-resize;
227 | left: -3px;
228 | }
229 |
230 | .cropper-point.point-se {
231 | bottom: -3px;
232 | cursor: se-resize;
233 | height: 20px;
234 | opacity: 1;
235 | right: -3px;
236 | width: 20px;
237 | }
238 |
239 | @media (min-width: 768px) {
240 | .cropper-point.point-se {
241 | height: 15px;
242 | width: 15px;
243 | }
244 | }
245 |
246 | @media (min-width: 992px) {
247 | .cropper-point.point-se {
248 | height: 10px;
249 | width: 10px;
250 | }
251 | }
252 |
253 | @media (min-width: 1200px) {
254 | .cropper-point.point-se {
255 | height: 5px;
256 | opacity: .75;
257 | width: 5px;
258 | }
259 | }
260 |
261 | .cropper-point.point-se:before {
262 | background-color: #39f;
263 | bottom: -50%;
264 | content: ' ';
265 | display: block;
266 | height: 200%;
267 | opacity: 0;
268 | position: absolute;
269 | right: -50%;
270 | width: 200%;
271 | }
272 |
273 | .cropper-invisible {
274 | opacity: 0;
275 | }
276 |
277 | .cropper-bg {
278 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC');
279 | }
280 |
281 | .cropper-hide {
282 | display: block;
283 | height: 0;
284 | position: absolute;
285 | width: 0;
286 | }
287 |
288 | .cropper-hidden {
289 | display: none !important;
290 | }
291 |
292 | .cropper-move {
293 | cursor: move;
294 | }
295 |
296 | .cropper-crop {
297 | cursor: crosshair;
298 | }
299 |
300 | .cropper-disabled .cropper-drag-box,
301 | .cropper-disabled .cropper-face,
302 | .cropper-disabled .cropper-line,
303 | .cropper-disabled .cropper-point {
304 | cursor: not-allowed;
305 | }
306 |
307 |
--------------------------------------------------------------------------------
/docs/css/cropper.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Cropper.js v1.1.3
3 | * https://github.com/fengyuanchen/cropperjs
4 | *
5 | * Copyright (c) 2015-2017 Chen Fengyuan
6 | * Released under the MIT license
7 | *
8 | * Date: 2017-10-21T09:27:29.883Z
9 | */
10 |
11 | .cropper-container {
12 | direction: ltr;
13 | font-size: 0;
14 | line-height: 0;
15 | position: relative;
16 | -ms-touch-action: none;
17 | touch-action: none;
18 | -webkit-user-select: none;
19 | -moz-user-select: none;
20 | -ms-user-select: none;
21 | user-select: none;
22 | }
23 |
24 | .cropper-container img {/*Avoid margin top issue (Occur only when margin-top <= -height)
25 | */
26 | display: block;
27 | height: 100%;
28 | image-orientation: 0deg;
29 | max-height: none !important;
30 | max-width: none !important;
31 | min-height: 0 !important;
32 | min-width: 0 !important;
33 | width: 100%;
34 | }
35 |
36 | .cropper-wrap-box,
37 | .cropper-canvas,
38 | .cropper-drag-box,
39 | .cropper-crop-box,
40 | .cropper-modal {
41 | bottom: 0;
42 | left: 0;
43 | position: absolute;
44 | right: 0;
45 | top: 0;
46 | }
47 |
48 | .cropper-wrap-box,
49 | .cropper-canvas {
50 | overflow: hidden;
51 | }
52 |
53 | .cropper-drag-box {
54 | background-color: #fff;
55 | opacity: 0;
56 | }
57 |
58 | .cropper-modal {
59 | background-color: #000;
60 | opacity: .5;
61 | }
62 |
63 | .cropper-view-box {
64 | display: block;
65 | height: 100%;
66 | outline-color: rgba(51, 153, 255, 0.75);
67 | outline: 1px solid #39f;
68 | overflow: hidden;
69 | width: 100%;
70 | }
71 |
72 | .cropper-dashed {
73 | border: 0 dashed #eee;
74 | display: block;
75 | opacity: .5;
76 | position: absolute;
77 | }
78 |
79 | .cropper-dashed.dashed-h {
80 | border-bottom-width: 1px;
81 | border-top-width: 1px;
82 | height: 33.33333%;
83 | left: 0;
84 | top: 33.33333%;
85 | width: 100%;
86 | }
87 |
88 | .cropper-dashed.dashed-v {
89 | border-left-width: 1px;
90 | border-right-width: 1px;
91 | height: 100%;
92 | left: 33.33333%;
93 | top: 0;
94 | width: 33.33333%;
95 | }
96 |
97 | .cropper-center {
98 | display: block;
99 | height: 0;
100 | left: 50%;
101 | opacity: .75;
102 | position: absolute;
103 | top: 50%;
104 | width: 0;
105 | }
106 |
107 | .cropper-center:before,
108 | .cropper-center:after {
109 | background-color: #eee;
110 | content: ' ';
111 | display: block;
112 | position: absolute;
113 | }
114 |
115 | .cropper-center:before {
116 | height: 1px;
117 | left: -3px;
118 | top: 0;
119 | width: 7px;
120 | }
121 |
122 | .cropper-center:after {
123 | height: 7px;
124 | left: 0;
125 | top: -3px;
126 | width: 1px;
127 | }
128 |
129 | .cropper-face,
130 | .cropper-line,
131 | .cropper-point {
132 | display: block;
133 | height: 100%;
134 | opacity: .1;
135 | position: absolute;
136 | width: 100%;
137 | }
138 |
139 | .cropper-face {
140 | background-color: #fff;
141 | left: 0;
142 | top: 0;
143 | }
144 |
145 | .cropper-line {
146 | background-color: #39f;
147 | }
148 |
149 | .cropper-line.line-e {
150 | cursor: e-resize;
151 | right: -3px;
152 | top: 0;
153 | width: 5px;
154 | }
155 |
156 | .cropper-line.line-n {
157 | cursor: n-resize;
158 | height: 5px;
159 | left: 0;
160 | top: -3px;
161 | }
162 |
163 | .cropper-line.line-w {
164 | cursor: w-resize;
165 | left: -3px;
166 | top: 0;
167 | width: 5px;
168 | }
169 |
170 | .cropper-line.line-s {
171 | bottom: -3px;
172 | cursor: s-resize;
173 | height: 5px;
174 | left: 0;
175 | }
176 |
177 | .cropper-point {
178 | background-color: #39f;
179 | height: 5px;
180 | opacity: .75;
181 | width: 5px;
182 | }
183 |
184 | .cropper-point.point-e {
185 | cursor: e-resize;
186 | margin-top: -3px;
187 | right: -3px;
188 | top: 50%;
189 | }
190 |
191 | .cropper-point.point-n {
192 | cursor: n-resize;
193 | left: 50%;
194 | margin-left: -3px;
195 | top: -3px;
196 | }
197 |
198 | .cropper-point.point-w {
199 | cursor: w-resize;
200 | left: -3px;
201 | margin-top: -3px;
202 | top: 50%;
203 | }
204 |
205 | .cropper-point.point-s {
206 | bottom: -3px;
207 | cursor: s-resize;
208 | left: 50%;
209 | margin-left: -3px;
210 | }
211 |
212 | .cropper-point.point-ne {
213 | cursor: ne-resize;
214 | right: -3px;
215 | top: -3px;
216 | }
217 |
218 | .cropper-point.point-nw {
219 | cursor: nw-resize;
220 | left: -3px;
221 | top: -3px;
222 | }
223 |
224 | .cropper-point.point-sw {
225 | bottom: -3px;
226 | cursor: sw-resize;
227 | left: -3px;
228 | }
229 |
230 | .cropper-point.point-se {
231 | bottom: -3px;
232 | cursor: se-resize;
233 | height: 20px;
234 | opacity: 1;
235 | right: -3px;
236 | width: 20px;
237 | }
238 |
239 | @media (min-width: 768px) {
240 | .cropper-point.point-se {
241 | height: 15px;
242 | width: 15px;
243 | }
244 | }
245 |
246 | @media (min-width: 992px) {
247 | .cropper-point.point-se {
248 | height: 10px;
249 | width: 10px;
250 | }
251 | }
252 |
253 | @media (min-width: 1200px) {
254 | .cropper-point.point-se {
255 | height: 5px;
256 | opacity: .75;
257 | width: 5px;
258 | }
259 | }
260 |
261 | .cropper-point.point-se:before {
262 | background-color: #39f;
263 | bottom: -50%;
264 | content: ' ';
265 | display: block;
266 | height: 200%;
267 | opacity: 0;
268 | position: absolute;
269 | right: -50%;
270 | width: 200%;
271 | }
272 |
273 | .cropper-invisible {
274 | opacity: 0;
275 | }
276 |
277 | .cropper-bg {
278 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC');
279 | }
280 |
281 | .cropper-hide {
282 | display: block;
283 | height: 0;
284 | position: absolute;
285 | width: 0;
286 | }
287 |
288 | .cropper-hidden {
289 | display: none !important;
290 | }
291 |
292 | .cropper-move {
293 | cursor: move;
294 | }
295 |
296 | .cropper-crop {
297 | cursor: crosshair;
298 | }
299 |
300 | .cropper-disabled .cropper-drag-box,
301 | .cropper-disabled .cropper-face,
302 | .cropper-disabled .cropper-line,
303 | .cropper-disabled .cropper-point {
304 | cursor: not-allowed;
305 | }
306 |
307 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 1.1.3 (Oct 21, 2017)
4 |
5 | - Fixed a bug of render when disable one of `rotatable` and `scalable` options (#241).
6 |
7 | ## 1.1.2 (Oct 18, 2017)
8 |
9 | - Normalize related decimal numbers when crop an image with canvas.
10 |
11 | ## 1.1.1 (Oct 11, 2017)
12 |
13 | - Supports to load in node environment (#237).
14 | - Fixed a bug of event binding (#238).
15 |
16 | ## 1.1.0 (Oct 8, 2017)
17 |
18 | - Added 4 new options to `getCroppedCanvas` method: `minWidth`, `minHeight`, `maxWidth` and `maxHeight`.
19 | - Enhanced image scaling: the `scaleX` and `scaleY` values should only be `1` or `-1` before, but now they can be any numbers.
20 | - Improved crop box resizing behaviour in the northeast, northwest, southeast and southwest directions. (#222).
21 |
22 | ## 1.0.0 (Sep 3, 2017)
23 |
24 | - Fixed a bug of zoom out after cleared the crop box in view mode 1, 2 and 3 (#209).
25 | - Improve crop box resizing behaviour in the east, west, south and north directions (#222).
26 |
27 | ## 1.0.0-rc.3 (Jul 7, 2017)
28 |
29 | - Added two new options (`imageSmoothingEnabled` and `imageSmoothingQuality`) to `getCroppedCanvas` method.
30 | - Fixed a bug of RegExp using (#195 by @arusakov).
31 |
32 | ## 1.0.0-rc.2 (May 30, 2017)
33 |
34 | - Fixed the issue of canvas box initialization (#179).
35 |
36 | ## 1.0.0-rc.1 (Apr 30, 2017)
37 |
38 | - Change the `main` field value from `dist/cropper.js` (UMD) to `dist/cropper.common.js` (CommonJS).
39 | - Added `module` and `browser` fields to `package.json`.
40 |
41 | ## 1.0.0-rc (Mar 25, 2017)
42 |
43 | - Fixed the bug of touch zoom (#161).
44 | - Fixed the bug of window resize (#162).
45 | - Improve the `toggleDragModeOnDblclick` option (only available when the `dragMode` option is set to `crop` or `move`)
46 |
47 | ## 1.0.0-beta.2 (Feb 25, 2017)
48 |
49 | - Fixed the bug of rotate square image lead image shrink (#155).
50 | - Improved RegExps for DataURL processing (#156).
51 |
52 | ## 1.0.0-beta.1 (Jan 21, 2017)
53 |
54 | - Use CSS3 2D Transforms instead of `left` and `top` for better performance (#138).
55 | - Set `withCredentials` attribute when read the image data by XMLHttpRequest (#141).
56 |
57 | ## 1.0.0-beta (Jan 1, 2017)
58 |
59 | - Supports to set an element for preview (#113).
60 | - Improved event handler for Pointer Events (#127).
61 |
62 | ## 1.0.0-alpha (Dec 4, 2016)
63 |
64 | - Built JavaScript with Rollup.
65 | - Build CSS with PostCSS.
66 | - Fixed a bug of auto crop when replace the image (#83).
67 |
68 | ## 0.8.1 (Sep 3, 2016)
69 |
70 | - Fixed the bug of cropping (#80).
71 | - Fixed the bug of calling `ready` event twice when call `replace` method (#81).
72 | - Fixed the bug of `getCroppedCanvas` when set `scalable` or `rotatable` to `false` (#82).
73 |
74 | ## 0.8.0 (Aug 18, 2016)
75 |
76 | - Removed `build` event.
77 | - Renamed `built` event to `ready`.
78 | - Fixed the error of orientation transform.
79 | - Ported code to ECMAScript 6.
80 |
81 | ## 0.7.2 (Jun 8, 2016)
82 |
83 | - Fixed a bug of `data-*` attributes setting and getting.
84 | - Fixed the calling order of `scale` and `rotate`.
85 |
86 | ## 0.7.1 (May 28, 2016)
87 |
88 | - Improved the rotate and scale transform behaviour.
89 | - Improved the `getCroppedCanvas` method (returns the whole canvas if it is not cropped).
90 | - Check cross origin setting when load image by XMLHTTPRequest.
91 |
92 | ## 0.7.0 (Mar 20, 2016)
93 |
94 | - Supports 7 custom events: `build`, `built`, `cropstart`, `cropmove`, `cropend`, `crop` and `zoom`.
95 | - The original callback options become shortcuts of these events now.
96 | - IE8 is no longer supported after added these custom events.
97 |
98 | ## 0.6.0 (Feb 22, 2016)
99 |
100 | - Added a new parameter to the `replace` method for applying filters.
101 | - Improved the image initializing for Safari.
102 | - Fixed incorrect size limitation of the crop box (#30).
103 | - Fixed incorrect cropped canvas when scaleX or scaleY great than 1.
104 |
105 | ## 0.5.6 (Jan 18, 2016)
106 |
107 | - Fixed crossOriginUrl undefined error when exists the `crossOrigin` property.
108 | - Fixed the issue in the "destroy" method (#24).
109 | - Optimized tests.
110 |
111 | ## 0.5.5 (Jan 1, 2016)
112 |
113 | - Fixed a dimension bug in the "getCroppedCanvas" method.
114 | - Added an example for cropping round image.
115 |
116 | ## 0.5.4 (Dec 28, 2015)
117 |
118 | - Supports to zoom from event triggering point.
119 |
120 | ## 0.5.3 (Dec 24, 2015)
121 |
122 | - Limit wheel speed to prevent zoom too fast (#21)
123 | - Improve the `setCropBoxData` method (#22)
124 |
125 | ## 0.5.2 (Dec 15, 2015)
126 |
127 | - Fix event handlers
128 |
129 | ## 0.5.1 (Dec 12, 2015)
130 |
131 | - Handle Data URL (avoid to use XMLHttpRequest to open a Data URL)
132 | - Handle ajax error when load ArrayBuffer
133 | - Not to transform the image to base64 when Orientation equals to `1`
134 | - Fix some typos
135 |
136 | ## 0.5.0 (Dec 5, 2015)
137 |
138 | - Added a new option: `checkOrientation`
139 | - Added a timestamp to the url of preview image
140 |
141 | ## 0.4.0 (Dec 2, 2015)
142 |
143 | - Added a new option: `restore`
144 | - Fixed #12: Added vendor prefixes to CSS `transform`
145 |
146 | ## 0.3.3 (Nov 30, 2015)
147 |
148 | - Floor the numerical parameters for `CanvasRenderingContext2D.drawImage`
149 |
150 | ## 0.3.2 (Nov 18, 2015)
151 |
152 | - Fixed #10: improve new crop box creating
153 |
154 | ## 0.3.1 (Nov 11, 2015)
155 |
156 | - Fixed #7: reset the `crossOrigin` when call the `replace` method
157 |
158 | ## 0.3.0 (Oct 28, 2015)
159 |
160 | - Supports four view modes
161 | - Supports three drag modes
162 | - Makes the crop box's borders and handlers visible when overflow
163 | - Added some examples
164 | - Fixed some issues
165 |
166 | ### Options
167 |
168 | - Added `viewMode`
169 | - Added `dragMode`
170 | - Renamed `touchDragZoom` to `zoomOnTouch`
171 | - Renamed `mouseWheelZoom` to `zoomOnWheel`
172 | - Renamed `doubleClickToggle` to `toggleDragModeOnDblclick`
173 | - Renamed `checkImageOrigin` to `checkCrossOrigin`
174 | - Removed `strict` (supported by `viewMode: 1`)
175 | - Removed `dragCrop` (supported by `dragMode: 'crop'`)
176 |
177 | ## 0.2.1 (Oct 28, 2015)
178 |
179 | - Fix the error jQuery reference on the `setCanvasData` method
180 | - Fix typo on the `destroy` method
181 |
182 | ## 0.2.0 (Oct 25, 2015)
183 |
184 | - Added 5 new methods: `moveTo`, `zoomTo`, `rotateTo`, `scaleX` and `scaleY`
185 | - Improved 4 methods: `move`, `zoom`, `rotate` and `getCanvasData`
186 | - Improved cropping
187 |
188 | ## 0.1.1 (Oct 10, 2015)
189 |
190 | - Improved canvas limitation
191 | - Improved crop box limitation
192 | - Improved preview for cross origin image
193 |
194 | ## 0.1.0 (Sep 25, 2015)
195 |
196 | - Supports touch (mobile)
197 | - Supports zoom
198 | - Supports rotation
199 | - Supports scale (flip)
200 | - Supports canvas
201 | - Supports multiple croppers
202 | - Cross-browser support
203 | - Supports 37 options: `aspectRatio`, `data`, `preview`, `strict`, `responsive`, `checkImageOrigin`, `modal`, `guides`, `center`, `highlight`, `background`, `autoCrop`, `autoCropArea`, `dragCrop`, `movable`, `rotatable`, `scalable`, `zoomable`, `mouseWheelZoom`, `wheelZoomRatio`, `touchDragZoom`, `cropBoxMovable`, `cropBoxResizable`, `doubleClickToggle`, `minCanvasWidth`, `minCanvasHeight`, `minCropBoxWidth`, `minCropBoxHeight`, `minContainerWidth`, `minContainerHeight`, `build`, `built`, `cropstart`, `cropmove`, `cropend`, `crop`, `zoom`.
204 | - Support 22 methods: `crop`, `reset`, `clear`, `replace`, `enable`, `disable`, `destroy`, `move`, `zoom`, `rotate`, `scale`, `getData`, `setData`, `getContainerData`, `getImageData`, `getCanvasData`, `setCanvasData`, `getCropBoxData`, `setCropBoxData`, `getCroppedCanvas`, `setAspectRatio`, `setDragMode`.
205 |
--------------------------------------------------------------------------------
/docs/js/main.js:
--------------------------------------------------------------------------------
1 | window.onload = function () {
2 |
3 | 'use strict';
4 |
5 | var Cropper = window.Cropper;
6 | var URL = window.URL || window.webkitURL;
7 | var container = document.querySelector('.img-container');
8 | var image = container.getElementsByTagName('img').item(0);
9 | var download = document.getElementById('download');
10 | var actions = document.getElementById('actions');
11 | var dataX = document.getElementById('dataX');
12 | var dataY = document.getElementById('dataY');
13 | var dataHeight = document.getElementById('dataHeight');
14 | var dataWidth = document.getElementById('dataWidth');
15 | var dataRotate = document.getElementById('dataRotate');
16 | var dataScaleX = document.getElementById('dataScaleX');
17 | var dataScaleY = document.getElementById('dataScaleY');
18 | var options = {
19 | aspectRatio: 321 / 180,
20 | preview: '.img-preview',
21 | ready: function (e) {
22 | console.log(e.type);
23 | },
24 | cropstart: function (e) {
25 | console.log(e.type, e.detail.action);
26 | },
27 | cropmove: function (e) {
28 | console.log(e.type, e.detail.action);
29 | },
30 | cropend: function (e) {
31 | console.log(e.type, e.detail.action);
32 | },
33 | crop: function (e) {
34 | var data = e.detail;
35 |
36 | console.log(e.type);
37 | dataX.value = Math.round(data.x);
38 | dataY.value = Math.round(data.y);
39 | dataHeight.value = Math.round(data.height);
40 | dataWidth.value = Math.round(data.width);
41 | dataRotate.value = typeof data.rotate !== 'undefined' ? data.rotate : '';
42 | dataScaleX.value = typeof data.scaleX !== 'undefined' ? data.scaleX : '';
43 | dataScaleY.value = typeof data.scaleY !== 'undefined' ? data.scaleY : '';
44 | },
45 | zoom: function (e) {
46 | console.log(e.type, e.detail.ratio);
47 | }
48 | };
49 | var cropper = new Cropper(image, options);
50 | var originalImageURL = image.src;
51 | var uploadedImageType = 'image/jpeg';
52 | var uploadedImageURL;
53 |
54 | // Tooltip
55 | $('[data-toggle="tooltip"]').tooltip();
56 |
57 | // Buttons
58 | if (!document.createElement('canvas').getContext) {
59 | $('button[data-method="getCroppedCanvas"]').prop('disabled', true);
60 | }
61 |
62 | if (typeof document.createElement('cropper').style.transition === 'undefined') {
63 | $('button[data-method="rotate"]').prop('disabled', true);
64 | $('button[data-method="scale"]').prop('disabled', true);
65 | }
66 |
67 | // Download
68 | if (typeof download.download === 'undefined') {
69 | download.className += ' disabled';
70 | }
71 |
72 | // Options
73 | actions.querySelector('.docs-toggles').onchange = function (event) {
74 | var e = event || window.event;
75 | var target = e.target || e.srcElement;
76 | var cropBoxData;
77 | var canvasData;
78 | var isCheckbox;
79 | var isRadio;
80 |
81 | if (!cropper) {
82 | return;
83 | }
84 |
85 | if (target.tagName.toLowerCase() === 'label') {
86 | target = target.querySelector('input');
87 | }
88 |
89 | isCheckbox = target.type === 'checkbox';
90 | isRadio = target.type === 'radio';
91 |
92 | if (isCheckbox || isRadio) {
93 | if (isCheckbox) {
94 | options[target.name] = target.checked;
95 | cropBoxData = cropper.getCropBoxData();
96 | canvasData = cropper.getCanvasData();
97 |
98 | options.ready = function () {
99 | console.log('ready');
100 | cropper.setCropBoxData(cropBoxData).setCanvasData(canvasData);
101 | };
102 | } else {
103 | options[target.name] = target.value;
104 | options.ready = function () {
105 | console.log('ready');
106 | };
107 | }
108 |
109 | // Restart
110 | cropper.destroy();
111 | cropper = new Cropper(image, options);
112 | }
113 | };
114 |
115 | // Methods
116 | actions.querySelector('.docs-buttons').onclick = function (event) {
117 | var e = event || window.event;
118 | var target = e.target || e.srcElement;
119 | var cropped;
120 | var result;
121 | var input;
122 | var data;
123 |
124 | if (!cropper) {
125 | return;
126 | }
127 |
128 | while (target !== this) {
129 | if (target.getAttribute('data-method')) {
130 | break;
131 | }
132 |
133 | target = target.parentNode;
134 | }
135 |
136 | if (target === this || target.disabled || target.className.indexOf('disabled') > -1) {
137 | return;
138 | }
139 |
140 | data = {
141 | method: target.getAttribute('data-method'),
142 | target: target.getAttribute('data-target'),
143 | option: target.getAttribute('data-option') || undefined,
144 | secondOption: target.getAttribute('data-second-option') || undefined
145 | };
146 |
147 | cropped = cropper.cropped;
148 |
149 | if (data.method) {
150 | if (typeof data.target !== 'undefined') {
151 | input = document.querySelector(data.target);
152 |
153 | if (!target.hasAttribute('data-option') && data.target && input) {
154 | try {
155 | data.option = JSON.parse(input.value);
156 | } catch (e) {
157 | console.log(e.message);
158 | }
159 | }
160 | }
161 |
162 | switch (data.method) {
163 | case 'rotate':
164 | if (cropped) {
165 | cropper.clear();
166 | }
167 |
168 | break;
169 |
170 | case 'getCroppedCanvas':
171 | try {
172 | data.option = JSON.parse(data.option);
173 | } catch (e) {
174 | console.log(e.message);
175 | }
176 |
177 | if (uploadedImageType === 'image/jpeg') {
178 | if (!data.option) {
179 | data.option = {};
180 | }
181 |
182 | data.option.fillColor = '#fff';
183 | }
184 |
185 | break;
186 | }
187 |
188 | result = cropper[data.method](data.option, data.secondOption);
189 |
190 | switch (data.method) {
191 | case 'rotate':
192 | if (cropped) {
193 | cropper.crop();
194 | }
195 |
196 | break;
197 |
198 | case 'scaleX':
199 | case 'scaleY':
200 | target.setAttribute('data-option', -data.option);
201 | break;
202 |
203 | case 'getCroppedCanvas':
204 | if (result) {
205 | // Bootstrap's Modal
206 | $('#getCroppedCanvasModal').modal().find('.modal-body').html(result);
207 |
208 | if (!download.disabled) {
209 | download.href = result.toDataURL(uploadedImageType);
210 | }
211 | }
212 |
213 | break;
214 |
215 | case 'destroy':
216 | cropper = null;
217 |
218 | if (uploadedImageURL) {
219 | URL.revokeObjectURL(uploadedImageURL);
220 | uploadedImageURL = '';
221 | image.src = originalImageURL;
222 | }
223 |
224 | break;
225 | }
226 |
227 | if (typeof result === 'object' && result !== cropper && input) {
228 | try {
229 | input.value = JSON.stringify(result);
230 | } catch (e) {
231 | console.log(e.message);
232 | }
233 | }
234 | }
235 | };
236 |
237 | document.body.onkeydown = function (event) {
238 | var e = event || window.event;
239 |
240 | if (!cropper || this.scrollTop > 300) {
241 | return;
242 | }
243 |
244 | switch (e.keyCode) {
245 | case 37:
246 | e.preventDefault();
247 | cropper.move(-1, 0);
248 | break;
249 |
250 | case 38:
251 | e.preventDefault();
252 | cropper.move(0, -1);
253 | break;
254 |
255 | case 39:
256 | e.preventDefault();
257 | cropper.move(1, 0);
258 | break;
259 |
260 | case 40:
261 | e.preventDefault();
262 | cropper.move(0, 1);
263 | break;
264 | }
265 | };
266 |
267 | // Import image
268 | var inputImage = document.getElementById('inputImage');
269 |
270 | if (URL) {
271 | inputImage.onchange = function () {
272 | var files = this.files;
273 | var file;
274 |
275 | if (cropper && files && files.length) {
276 | file = files[0];
277 |
278 | if (/^image\/\w+/.test(file.type)) {
279 | uploadedImageType = file.type;
280 |
281 | if (uploadedImageURL) {
282 | URL.revokeObjectURL(uploadedImageURL);
283 | }
284 |
285 | image.src = uploadedImageURL = URL.createObjectURL(file);
286 | cropper.destroy();
287 | cropper = new Cropper(image, options);
288 | inputImage.value = null;
289 | } else {
290 | window.alert('Please choose an image file.');
291 | }
292 | }
293 | };
294 | } else {
295 | inputImage.disabled = true;
296 | inputImage.parentNode.className += ' disabled';
297 | }
298 | };
299 |
--------------------------------------------------------------------------------
/src/js/cropper.js:
--------------------------------------------------------------------------------
1 | import DEFAULTS from './defaults';
2 | import TEMPLATE from './template';
3 | import render from './render';
4 | import preview from './preview';
5 | import events from './events';
6 | import handlers from './handlers';
7 | import change from './change';
8 | import methods from './methods';
9 | import {
10 | ACTION_ALL,
11 | CLASS_HIDDEN,
12 | CLASS_HIDE,
13 | CLASS_INVISIBLE,
14 | CLASS_MODAL,
15 | CLASS_MOVE,
16 | DATA_ACTION,
17 | EVENT_CROP,
18 | EVENT_ERROR,
19 | EVENT_LOAD,
20 | EVENT_READY,
21 | NAMESPACE,
22 | REGEXP_DATA_URL,
23 | REGEXP_DATA_URL_JPEG,
24 | REGEXP_TAG_NAME,
25 | WINDOW,
26 | } from './constants';
27 | import {
28 | addClass,
29 | addListener,
30 | addTimestamp,
31 | arrayBufferToDataURL,
32 | dataURLToArrayBuffer,
33 | dispatchEvent,
34 | extend,
35 | getData,
36 | getImageNaturalSizes,
37 | getOrientation,
38 | isCrossOriginURL,
39 | isFunction,
40 | isPlainObject,
41 | parseOrientation,
42 | proxy,
43 | removeClass,
44 | removeListener,
45 | setData,
46 | } from './utilities';
47 |
48 | const AnotherCropper = WINDOW.Cropper;
49 |
50 | class Cropper {
51 | /**
52 | * Create a new Cropper.
53 | * @param {Element} element - The target element for cropping.
54 | * @param {Object} [options={}] - The configuration options.
55 | */
56 | constructor(element, options = {}) {
57 | if (!element || !REGEXP_TAG_NAME.test(element.tagName)) {
58 | throw new Error('The first argument is required and must be an
or