undo and redo adding new cropper library as it had a .git config from the original project

This commit is contained in:
zotlabs 2018-01-23 16:42:24 -08:00
parent 1e8dcaffc9
commit 5902528bae
121 changed files with 34733 additions and 0 deletions

View file

@ -0,0 +1,10 @@
{
"presets": [
["env", {
"modules": false,
"targets": {
"browsers": ["last 2 versions", "ie >= 9"]
}
}]
]
}

View file

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

View file

@ -0,0 +1,13 @@
{
"extends": "airbnb-base",
"env": {
"browser": true
},
"rules": {
"no-param-reassign": "off",
"no-restricted-properties": "off",
"valid-jsdoc": ["error", {
"requireReturn": false
}]
}
}

View file

@ -0,0 +1,7 @@
language: node_js
node_js:
- "node"
script:
- npm run lint
- npm run build
- npm test

View file

@ -0,0 +1,218 @@
# Changelog
## 1.2.2 (Jan 3, 2018)
- Fix incorrect image natural sizes in iOS Safari (#279).
## 1.2.1 (Dec 17, 2017)
- Add `style` field to `package.json`.
- Fix size error when load SVG image (#256).
## 1.2.0 (Dec 17, 2017)
- Allow to set the pivot of zoom (#144).
- Fixed a bug of rotation (#260).
## 1.1.3 (Oct 21, 2017)
- Fixed a bug of render when disable one of `rotatable` and `scalable` options (#241).
## 1.1.2 (Oct 18, 2017)
- Normalize related decimal numbers when crop an image with canvas.
## 1.1.1 (Oct 11, 2017)
- Supports to load in node environment (#237).
- Fixed a bug of event binding (#238).
## 1.1.0 (Oct 8, 2017)
- Added 4 new options to `getCroppedCanvas` method: `minWidth`, `minHeight`, `maxWidth` and `maxHeight`.
- Enhanced image scaling: the `scaleX` and `scaleY` values should only be `1` or `-1` before, but now they can be any numbers.
- Improved crop box resizing behaviour in the northeast, northwest, southeast and southwest directions. (#222).
## 1.0.0 (Sep 3, 2017)
- Fixed a bug of zoom out after cleared the crop box in view mode 1, 2 and 3 (#209).
- Improve crop box resizing behaviour in the east, west, south and north directions (#222).
## 1.0.0-rc.3 (Jul 7, 2017)
- Added two new options (`imageSmoothingEnabled` and `imageSmoothingQuality`) to `getCroppedCanvas` method.
- Fixed a bug of RegExp using (#195 by @arusakov).
## 1.0.0-rc.2 (May 30, 2017)
- Fixed the issue of canvas box initialization (#179).
## 1.0.0-rc.1 (Apr 30, 2017)
- Change the `main` field value from `dist/cropper.js` (UMD) to `dist/cropper.common.js` (CommonJS).
- Added `module` and `browser` fields to `package.json`.
## 1.0.0-rc (Mar 25, 2017)
- Fixed the bug of touch zoom (#161).
- Fixed the bug of window resize (#162).
- Improve the `toggleDragModeOnDblclick` option (only available when the `dragMode` option is set to `crop` or `move`)
## 1.0.0-beta.2 (Feb 25, 2017)
- Fixed the bug of rotate square image lead image shrink (#155).
- Improved RegExps for DataURL processing (#156).
## 1.0.0-beta.1 (Jan 21, 2017)
- Use CSS3 2D Transforms instead of `left` and `top` for better performance (#138).
- Set `withCredentials` attribute when read the image data by XMLHttpRequest (#141).
## 1.0.0-beta (Jan 1, 2017)
- Supports to set an element for preview (#113).
- Improved event handler for Pointer Events (#127).
## 1.0.0-alpha (Dec 4, 2016)
- Built JavaScript with Rollup.
- Build CSS with PostCSS.
- Fixed a bug of auto crop when replace the image (#83).
## 0.8.1 (Sep 3, 2016)
- Fixed the bug of cropping (#80).
- Fixed the bug of calling `ready` event twice when call `replace` method (#81).
- Fixed the bug of `getCroppedCanvas` when set `scalable` or `rotatable` to `false` (#82).
## 0.8.0 (Aug 18, 2016)
- Removed `build` event.
- Renamed `built` event to `ready`.
- Fixed the error of orientation transform.
- Ported code to ECMAScript 6.
## 0.7.2 (Jun 8, 2016)
- Fixed a bug of `data-*` attributes setting and getting.
- Fixed the calling order of `scale` and `rotate`.
## 0.7.1 (May 28, 2016)
- Improved the rotate and scale transform behaviour.
- Improved the `getCroppedCanvas` method (returns the whole canvas if it is not cropped).
- Check cross origin setting when load image by XMLHTTPRequest.
## 0.7.0 (Mar 20, 2016)
- Supports 7 custom events: `build`, `built`, `cropstart`, `cropmove`, `cropend`, `crop` and `zoom`.
- The original callback options become shortcuts of these events now.
- IE8 is no longer supported after added these custom events.
## 0.6.0 (Feb 22, 2016)
- Added a new parameter to the `replace` method for applying filters.
- Improved the image initializing for Safari.
- Fixed incorrect size limitation of the crop box (#30).
- Fixed incorrect cropped canvas when scaleX or scaleY great than 1.
## 0.5.6 (Jan 18, 2016)
- Fixed crossOriginUrl undefined error when exists the `crossOrigin` property.
- Fixed the issue in the "destroy" method (#24).
- Optimized tests.
## 0.5.5 (Jan 1, 2016)
- Fixed a dimension bug in the "getCroppedCanvas" method.
- Added an example for cropping round image.
## 0.5.4 (Dec 28, 2015)
- Supports to zoom from event triggering point.
## 0.5.3 (Dec 24, 2015)
- Limit wheel speed to prevent zoom too fast (#21)
- Improve the `setCropBoxData` method (#22)
## 0.5.2 (Dec 15, 2015)
- Fix event handlers
## 0.5.1 (Dec 12, 2015)
- Handle Data URL (avoid to use XMLHttpRequest to open a Data URL)
- Handle ajax error when load ArrayBuffer
- Not to transform the image to base64 when Orientation equals to `1`
- Fix some typos
## 0.5.0 (Dec 5, 2015)
- Added a new option: `checkOrientation`
- Added a timestamp to the url of preview image
## 0.4.0 (Dec 2, 2015)
- Added a new option: `restore`
- Fixed #12: Added vendor prefixes to CSS `transform`
## 0.3.3 (Nov 30, 2015)
- Floor the numerical parameters for `CanvasRenderingContext2D.drawImage`
## 0.3.2 (Nov 18, 2015)
- Fixed #10: improve new crop box creating
## 0.3.1 (Nov 11, 2015)
- Fixed #7: reset the `crossOrigin` when call the `replace` method
## 0.3.0 (Oct 28, 2015)
- Supports four view modes
- Supports three drag modes
- Makes the crop box's borders and handlers visible when overflow
- Added some examples
- Fixed some issues
### Options
- Added `viewMode`
- Added `dragMode`
- Renamed `touchDragZoom` to `zoomOnTouch`
- Renamed `mouseWheelZoom` to `zoomOnWheel`
- Renamed `doubleClickToggle` to `toggleDragModeOnDblclick`
- Renamed `checkImageOrigin` to `checkCrossOrigin`
- Removed `strict` (supported by `viewMode: 1`)
- Removed `dragCrop` (supported by `dragMode: 'crop'`)
## 0.2.1 (Oct 28, 2015)
- Fix the error jQuery reference on the `setCanvasData` method
- Fix typo on the `destroy` method
## 0.2.0 (Oct 25, 2015)
- Added 5 new methods: `moveTo`, `zoomTo`, `rotateTo`, `scaleX` and `scaleY`
- Improved 4 methods: `move`, `zoom`, `rotate` and `getCanvasData`
- Improved cropping
## 0.1.1 (Oct 10, 2015)
- Improved canvas limitation
- Improved crop box limitation
- Improved preview for cross origin image
## 0.1.0 (Sep 25, 2015)
- Supports touch (mobile)
- Supports zoom
- Supports rotation
- Supports scale (flip)
- Supports canvas
- Supports multiple croppers
- Cross-browser support
- 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`.
- 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`.

View file

@ -0,0 +1,35 @@
# Contributing to Cropper
## How to report bugs
### Make sure it is a Cropper bug
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.
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.
### Disable browser extensions
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.
### Try the latest version of Cropper
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.
### Simplify the test case
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.
### Search for related or duplicate issues
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.
### Browser support
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.
## Notes for pull request
- Run the test suites in the `test` directory first.
- Don't modify any files in the `dist` directory.
- Follow the same code style as the library.

View file

@ -0,0 +1,21 @@
Before opening an issue:
- [Search for duplicate or closed issues](https://github.com/fengyuanchen/cropperjs/issues?utf8=%E2%9C%93&q=is%3Aissue)
- Prepare a [reduced test case](https://css-tricks.com/reduced-test-cases/) for any bugs
- Read the [contributing guidelines](https://github.com/fengyuanchen/cropperjs/blob/master/CONTRIBUTING.md)
When asking general "how to" questions:
- Please do not open an issue here
- Instead, ask for help on [StackOverflow](http://stackoverflow.com/)
When reporting a bug, include:
- Operating system and version (Windows, Mac OS X, Android, iOS, Win10 Mobile)
- Browser and version (Chrome, Firefox, Safari, IE, MS Edge, Opera 15+, Android Browser)
- Reduced test cases and potential fixes using [JS Bin](https://jsbin.com), [JSFiddle](https://jsfiddle.net/) or [CodePen](https://codepen.io/)
When suggesting a feature, include:
- As much detail as possible for what we should add and why it's important to Cropper.js
- Relevant links to prior art, screenshots, or live demos whenever possible

21
library/cropperjs/LICENSE Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015-present Chen Fengyuan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

1028
library/cropperjs/README.md Normal file

File diff suppressed because it is too large Load diff

3760
library/cropperjs/dist/cropper.common.js vendored Normal file

File diff suppressed because it is too large Load diff

306
library/cropperjs/dist/cropper.css vendored Normal file
View file

@ -0,0 +1,306 @@
/*!
* Cropper.js v1.2.2
* https://github.com/fengyuanchen/cropperjs
*
* Copyright (c) 2015-2018 Chen Fengyuan
* Released under the MIT license
*
* Date: 2018-01-03T13:26:29.610Z
*/
.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 {/*Avoid margin top issue (Occur only when margin-top <= -height)
*/
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-wrap-box,
.cropper-canvas,
.cropper-drag-box,
.cropper-crop-box,
.cropper-modal {
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
}
.cropper-wrap-box,
.cropper-canvas {
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, 0.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:before,
.cropper-center:after {
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: ew-resize;
right: -3px;
top: 0;
width: 5px;
}
.cropper-line.line-n {
cursor: ns-resize;
height: 5px;
left: 0;
top: -3px;
}
.cropper-line.line-w {
cursor: ew-resize;
left: -3px;
top: 0;
width: 5px;
}
.cropper-line.line-s {
bottom: -3px;
cursor: ns-resize;
height: 5px;
left: 0;
}
.cropper-point {
background-color: #39f;
height: 5px;
opacity: .75;
width: 5px;
}
.cropper-point.point-e {
cursor: ew-resize;
margin-top: -3px;
right: -3px;
top: 50%;
}
.cropper-point.point-n {
cursor: ns-resize;
left: 50%;
margin-left: -3px;
top: -3px;
}
.cropper-point.point-w {
cursor: ew-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: nesw-resize;
right: -3px;
top: -3px;
}
.cropper-point.point-nw {
cursor: nwse-resize;
left: -3px;
top: -3px;
}
.cropper-point.point-sw {
bottom: -3px;
cursor: nesw-resize;
left: -3px;
}
.cropper-point.point-se {
bottom: -3px;
cursor: nwse-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;
}

3758
library/cropperjs/dist/cropper.esm.js vendored Normal file

File diff suppressed because it is too large Load diff

3766
library/cropperjs/dist/cropper.js vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,9 @@
/*!
* Cropper.js v1.2.2
* https://github.com/fengyuanchen/cropperjs
*
* Copyright (c) 2015-2018 Chen Fengyuan
* Released under the MIT license
*
* Date: 2018-01-03T13:26:29.610Z
*/.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:ew-resize;right:-3px;top:0;width:5px}.cropper-line.line-n{cursor:ns-resize;height:5px;left:0;top:-3px}.cropper-line.line-w{cursor:ew-resize;left:-3px;top:0;width:5px}.cropper-line.line-s{bottom:-3px;cursor:ns-resize;height:5px;left:0}.cropper-point{background-color:#39f;height:5px;opacity:.75;width:5px}.cropper-point.point-e{cursor:ew-resize;margin-top:-3px;right:-3px;top:50%}.cropper-point.point-n{cursor:ns-resize;left:50%;margin-left:-3px;top:-3px}.cropper-point.point-w{cursor:ew-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:nesw-resize;right:-3px;top:-3px}.cropper-point.point-nw{cursor:nwse-resize;left:-3px;top:-3px}.cropper-point.point-sw{bottom:-3px;cursor:nesw-resize;left:-3px}.cropper-point.point-se{bottom:-3px;cursor:nwse-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
library/cropperjs/dist/cropper.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,306 @@
/*!
* Cropper.js v1.2.2
* https://github.com/fengyuanchen/cropperjs
*
* Copyright (c) 2015-2018 Chen Fengyuan
* Released under the MIT license
*
* Date: 2018-01-03T13:26:29.610Z
*/
.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 {/*Avoid margin top issue (Occur only when margin-top <= -height)
*/
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-wrap-box,
.cropper-canvas,
.cropper-drag-box,
.cropper-crop-box,
.cropper-modal {
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
}
.cropper-wrap-box,
.cropper-canvas {
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, 0.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:before,
.cropper-center:after {
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: ew-resize;
right: -3px;
top: 0;
width: 5px;
}
.cropper-line.line-n {
cursor: ns-resize;
height: 5px;
left: 0;
top: -3px;
}
.cropper-line.line-w {
cursor: ew-resize;
left: -3px;
top: 0;
width: 5px;
}
.cropper-line.line-s {
bottom: -3px;
cursor: ns-resize;
height: 5px;
left: 0;
}
.cropper-point {
background-color: #39f;
height: 5px;
opacity: .75;
width: 5px;
}
.cropper-point.point-e {
cursor: ew-resize;
margin-top: -3px;
right: -3px;
top: 50%;
}
.cropper-point.point-n {
cursor: ns-resize;
left: 50%;
margin-left: -3px;
top: -3px;
}
.cropper-point.point-w {
cursor: ew-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: nesw-resize;
right: -3px;
top: -3px;
}
.cropper-point.point-nw {
cursor: nwse-resize;
left: -3px;
top: -3px;
}
.cropper-point.point-sw {
bottom: -3px;
cursor: nesw-resize;
left: -3px;
}
.cropper-point.point-se {
bottom: -3px;
cursor: nwse-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;
}

View file

@ -0,0 +1,252 @@
.btn {
padding-left: .75rem;
padding-right: .75rem;
}
label.btn {
margin-bottom: 0;
}
.d-flex > .btn {
flex: 1;
}
.carbonads {
border-radius: .25rem;
border: 1px solid #ccc;
font-size: .875rem;
overflow: hidden;
padding: 1rem;
}
.carbon-wrap {
overflow: hidden;
}
.carbon-img {
clear: left;
display: block;
float: left;
}
.carbon-text,
.carbon-poweredby {
display: block;
margin-left: 140px;
}
.carbon-text,
.carbon-text:hover,
.carbon-text:focus {
color: #fff;
text-decoration: none;
}
.carbon-poweredby,
.carbon-poweredby:hover,
.carbon-poweredby:focus {
color: #ddd;
text-decoration: none;
}
@media (min-width: 768px) {
.carbonads {
float: right;
margin-bottom: -1rem;
margin-top: -1rem;
max-width: 360px;
}
}
.footer {
font-size: .875rem;
}
.heart {
color: #ddd;
display: block;
height: 2rem;
line-height: 2rem;
margin-bottom: 0;
margin-top: 1rem;
position: relative;
text-align: center;
width: 100%;
}
.heart:hover {
color: #ff4136;
}
.heart::before {
border-top: 1px solid #eee;
content: " ";
display: block;
height: 0;
left: 0;
position: absolute;
right: 0;
top: 50%;
}
.heart::after {
background-color: #fff;
content: "♥";
padding-left: .5rem;
padding-right: .5rem;
position: relative;
z-index: 1;
}
.img-container,
.img-preview {
background-color: #f7f7f7;
text-align: center;
width: 100%;
}
.img-container {
margin-bottom: 1rem;
max-height: 497px;
min-height: 200px;
}
@media (min-width: 768px) {
.img-container {
min-height: 497px;
}
}
.img-container > img {
max-width: 100%;
}
.docs-preview {
margin-right: -1rem;
}
.img-preview {
float: left;
margin-bottom: .5rem;
margin-right: .5rem;
overflow: hidden;
}
.img-preview > img {
max-width: 100%;
}
.preview-lg {
height: 9rem;
width: 16rem;
}
.preview-md {
height: 4.5rem;
width: 8rem;
}
.preview-sm {
height: 2.25rem;
width: 4rem;
}
.preview-xs {
height: 1.125rem;
margin-right: 0;
width: 2rem;
}
.docs-data > .input-group {
margin-bottom: .5rem;
}
.docs-data .input-group-prepend .input-group-text {
min-width: 4rem;
}
.docs-data .input-group-append .input-group-text {
min-width: 3rem;
}
.docs-buttons > .btn,
.docs-buttons > .btn-group,
.docs-buttons > .form-control {
margin-bottom: .5rem;
margin-right: .25rem;
}
.docs-toggles > .btn,
.docs-toggles > .btn-group,
.docs-toggles > .dropdown {
margin-bottom: .5rem;
}
.docs-tooltip {
display: block;
margin: -.5rem -.75rem;
padding: .5rem .75rem;
}
.docs-tooltip > .icon {
margin: 0 -.25rem;
vertical-align: top;
}
.tooltip-inner {
white-space: normal;
}
.btn-upload .tooltip-inner,
.btn-toggle .tooltip-inner {
white-space: nowrap;
}
.btn-toggle {
padding: .5rem;
}
.btn-toggle > .docs-tooltip {
margin: -.5rem;
padding: .5rem;
}
@media (max-width: 400px) {
.btn-group-crop {
margin-right: -1rem!important;
}
.btn-group-crop > .btn {
padding-left: .5rem;
padding-right: .5rem;
}
.btn-group-crop .docs-tooltip {
margin-left: -.5rem;
margin-right: -.5rem;
padding-left: .5rem;
padding-right: .5rem;
}
}
.docs-options .dropdown-menu {
width: 100%;
}
.docs-options .dropdown-menu > li {
font-size: .875rem;
padding: .125rem 1rem;
}
.docs-options .dropdown-menu .form-check-label {
display: block;
}
.docs-cropped .modal-body {
text-align: center;
}
.docs-cropped .modal-body > img,
.docs-cropped .modal-body > canvas {
max-width: 100%;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

View file

@ -0,0 +1,578 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="JavaScript image cropper.">
<meta name="keywords" content="HTML, CSS, JS, JavaScript, image cropping, cropper, cropperjs, cropper.js, front-end, web development">
<meta name="author" content="Chen Fengyuan">
<title>Cropper.js</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.3/css/bootstrap.min.css">
<link rel="stylesheet" href="css/cropper.css">
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<!--[if lt IE 9]>
<div class="alert alert-warning alert-dismissible fade show m-0 rounded-0" role="alert">
You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<![endif]-->
<!-- Header -->
<header class="navbar navbar-light navbar-expand-md">
<div class="container">
<a class="navbar-brand" href="./">Cropper.js</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar-collapse" aria-controls="navbar-collapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse justify-content-end" id="navbar-collapse" role="navigation">
<nav class="nav navbar-nav">
<a class="nav-link" href="https://github.com/fengyuanchen/cropperjs/blob/master/README.md" data-toggle="tooltip" title="View the documentation">Docs</a>
<a class="nav-link" href="https://fengyuanchen.github.io/photo-editor" data-toggle="tooltip" title="An advanced example of Cropper.js">Photo Editor</a>
<a class="nav-link" href="https://github.com/fengyuanchen/cropperjs" data-toggle="tooltip" title="View the GitHub project">GitHub</a>
<a class="nav-link" href="https://fengyuanchen.github.io" data-toggle="tooltip" title="More projects">More</a>
<a class="nav-link" href="http://chenfengyuan.com" data-toggle="tooltip" title="About the author">About</a>
</nav>
</div>
</nav>
</header>
<!-- Jumbotron -->
<div class="jumbotron bg-primary text-white rounded-0">
<div class="container">
<div class="row">
<div class="col-md">
<h1>Cropper.js <small class="h6">v1.2.2</small></h1>
<p class="lead">JavaScript image cropper.</p>
</div>
<div class="col-md">
<div class="carbonads">
<script id="_carbonads_js" src="//cdn.carbonads.com/carbon.js?zoneid=1673&serve=C6AILKT&placement=fengyuanchen" async></script>
</div>
</div>
</div>
</div>
</div>
<!-- Content -->
<div class="container">
<div class="row">
<div class="col-md-9">
<!-- <h3>Demo:</h3> -->
<div class="img-container">
<img src="images/picture.jpg" alt="Picture">
</div>
</div>
<div class="col-md-3">
<!-- <h3>Preview:</h3> -->
<div class="docs-preview clearfix">
<div class="img-preview preview-lg"></div>
<div class="img-preview preview-md"></div>
<div class="img-preview preview-sm"></div>
<div class="img-preview preview-xs"></div>
</div>
<!-- <h3>Data:</h3> -->
<!-- <h3>Data:</h3> -->
<div class="docs-data">
<div class="input-group input-group-sm">
<span class="input-group-prepend">
<label class="input-group-text" for="dataX">X</label>
</span>
<input type="text" class="form-control" id="dataX" placeholder="x">
<span class="input-group-append">
<span class="input-group-text">px</span>
</span>
</div>
<div class="input-group input-group-sm">
<span class="input-group-prepend">
<label class="input-group-text" for="dataY">Y</label>
</span>
<input type="text" class="form-control" id="dataY" placeholder="y">
<span class="input-group-append">
<span class="input-group-text">px</span>
</span>
</div>
<div class="input-group input-group-sm">
<span class="input-group-prepend">
<label class="input-group-text" for="dataWidth">Width</label>
</span>
<input type="text" class="form-control" id="dataWidth" placeholder="width">
<span class="input-group-append">
<span class="input-group-text">px</span>
</span>
</div>
<div class="input-group input-group-sm">
<span class="input-group-prepend">
<label class="input-group-text" for="dataHeight">Height</label>
</span>
<input type="text" class="form-control" id="dataHeight" placeholder="height">
<span class="input-group-append">
<span class="input-group-text">px</span>
</span>
</div>
<div class="input-group input-group-sm">
<span class="input-group-prepend">
<label class="input-group-text" for="dataRotate">Rotate</label>
</span>
<input type="text" class="form-control" id="dataRotate" placeholder="rotate">
<span class="input-group-append">
<span class="input-group-text">deg</span>
</span>
</div>
<div class="input-group input-group-sm">
<span class="input-group-prepend">
<label class="input-group-text" for="dataScaleX">ScaleX</label>
</span>
<input type="text" class="form-control" id="dataScaleX" placeholder="scaleX">
</div>
<div class="input-group input-group-sm">
<span class="input-group-prepend">
<label class="input-group-text" for="dataScaleY">ScaleY</label>
</span>
<input type="text" class="form-control" id="dataScaleY" placeholder="scaleY">
</div>
</div>
</div>
</div>
<div class="row" id="actions">
<div class="col-md-9 docs-buttons">
<!-- <h3>Toolbar:</h3> -->
<div class="btn-group">
<button type="button" class="btn btn-primary" data-method="setDragMode" data-option="move" title="Move">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.setDragMode(&quot;move&quot;)">
<span class="fa fa-arrows"></span>
</span>
</button>
<button type="button" class="btn btn-primary" data-method="setDragMode" data-option="crop" title="Crop">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.setDragMode(&quot;crop&quot;)">
<span class="fa fa-crop"></span>
</span>
</button>
</div>
<div class="btn-group">
<button type="button" class="btn btn-primary" data-method="zoom" data-option="0.1" title="Zoom In">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.zoom(0.1)">
<span class="fa fa-search-plus"></span>
</span>
</button>
<button type="button" class="btn btn-primary" data-method="zoom" data-option="-0.1" title="Zoom Out">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.zoom(-0.1)">
<span class="fa fa-search-minus"></span>
</span>
</button>
</div>
<div class="btn-group">
<button type="button" class="btn btn-primary" data-method="move" data-option="-10" data-second-option="0" title="Move Left">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.move(-10, 0)">
<span class="fa fa-arrow-left"></span>
</span>
</button>
<button type="button" class="btn btn-primary" data-method="move" data-option="10" data-second-option="0" title="Move Right">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.move(10, 0)">
<span class="fa fa-arrow-right"></span>
</span>
</button>
<button type="button" class="btn btn-primary" data-method="move" data-option="0" data-second-option="-10" title="Move Up">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.move(0, -10)">
<span class="fa fa-arrow-up"></span>
</span>
</button>
<button type="button" class="btn btn-primary" data-method="move" data-option="0" data-second-option="10" title="Move Down">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.move(0, 10)">
<span class="fa fa-arrow-down"></span>
</span>
</button>
</div>
<div class="btn-group">
<button type="button" class="btn btn-primary" data-method="rotate" data-option="-45" title="Rotate Left">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.rotate(-45)">
<span class="fa fa-rotate-left"></span>
</span>
</button>
<button type="button" class="btn btn-primary" data-method="rotate" data-option="45" title="Rotate Right">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.rotate(45)">
<span class="fa fa-rotate-right"></span>
</span>
</button>
</div>
<div class="btn-group">
<button type="button" class="btn btn-primary" data-method="scaleX" data-option="-1" title="Flip Horizontal">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.scaleX(-1)">
<span class="fa fa-arrows-h"></span>
</span>
</button>
<button type="button" class="btn btn-primary" data-method="scaleY" data-option="-1" title="Flip Vertical">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.scaleY(-1)">
<span class="fa fa-arrows-v"></span>
</span>
</button>
</div>
<div class="btn-group">
<button type="button" class="btn btn-primary" data-method="crop" title="Crop">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.crop()">
<span class="fa fa-check"></span>
</span>
</button>
<button type="button" class="btn btn-primary" data-method="clear" title="Clear">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.clear()">
<span class="fa fa-remove"></span>
</span>
</button>
</div>
<div class="btn-group">
<button type="button" class="btn btn-primary" data-method="disable" title="Disable">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.disable()">
<span class="fa fa-lock"></span>
</span>
</button>
<button type="button" class="btn btn-primary" data-method="enable" title="Enable">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.enable()">
<span class="fa fa-unlock"></span>
</span>
</button>
</div>
<div class="btn-group">
<button type="button" class="btn btn-primary" data-method="reset" title="Reset">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.reset()">
<span class="fa fa-refresh"></span>
</span>
</button>
<label class="btn btn-primary btn-upload" for="inputImage" title="Upload image file">
<input type="file" class="sr-only" id="inputImage" name="file" accept=".jpg,.jpeg,.png,.gif,.bmp,.tiff">
<span class="docs-tooltip" data-toggle="tooltip" title="Import image with Blob URLs">
<span class="fa fa-upload"></span>
</span>
</label>
<button type="button" class="btn btn-primary" data-method="destroy" title="Destroy">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.destroy()">
<span class="fa fa-power-off"></span>
</span>
</button>
</div>
<div class="btn-group btn-group-crop">
<button type="button" class="btn btn-success" data-method="getCroppedCanvas" data-option="{ &quot;maxWidth&quot;: 4096, &quot;maxHeight&quot;: 4096 }">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.getCroppedCanvas({ maxWidth: 4096, maxHeight: 4096 })">
Get Cropped Canvas
</span>
</button>
<button type="button" class="btn btn-success" data-method="getCroppedCanvas" data-option="{ &quot;width&quot;: 160, &quot;height&quot;: 90 }">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.getCroppedCanvas({ width: 160, height: 90 })">
160&times;90
</span>
</button>
<button type="button" class="btn btn-success" data-method="getCroppedCanvas" data-option="{ &quot;width&quot;: 320, &quot;height&quot;: 180 }">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.getCroppedCanvas({ width: 320, height: 180 })">
320&times;180
</span>
</button>
</div>
<!-- Show the cropped image in modal -->
<div class="modal fade docs-cropped" id="getCroppedCanvasModal" role="dialog" aria-hidden="true" aria-labelledby="getCroppedCanvasTitle" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="getCroppedCanvasTitle">Cropped</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body"></div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<a class="btn btn-primary" id="download" href="javascript:void(0);" download="cropped.jpg">Download</a>
</div>
</div>
</div>
</div><!-- /.modal -->
<button type="button" class="btn btn-secondary" data-method="getData" data-option data-target="#putData">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.getData()">
Get Data
</span>
</button>
<button type="button" class="btn btn-secondary" data-method="setData" data-target="#putData">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.setData(data)">
Set Data
</span>
</button>
<button type="button" class="btn btn-secondary" data-method="getContainerData" data-option data-target="#putData">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.getContainerData()">
Get Container Data
</span>
</button>
<button type="button" class="btn btn-secondary" data-method="getImageData" data-option data-target="#putData">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.getImageData()">
Get Image Data
</span>
</button>
<button type="button" class="btn btn-secondary" data-method="getCanvasData" data-option data-target="#putData">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.getCanvasData()">
Get Canvas Data
</span>
</button>
<button type="button" class="btn btn-secondary" data-method="setCanvasData" data-target="#putData">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.setCanvasData(data)">
Set Canvas Data
</span>
</button>
<button type="button" class="btn btn-secondary" data-method="getCropBoxData" data-option data-target="#putData">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.getCropBoxData()">
Get Crop Box Data
</span>
</button>
<button type="button" class="btn btn-secondary" data-method="setCropBoxData" data-target="#putData">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.setCropBoxData(data)">
Set Crop Box Data
</span>
</button>
<button type="button" class="btn btn-secondary" data-method="moveTo" data-option="0">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.moveTo(0)">
Move to [0,0]
</span>
</button>
<button type="button" class="btn btn-secondary" data-method="zoomTo" data-option="1">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.zoomTo(1)">
Zoom to 100%
</span>
</button>
<button type="button" class="btn btn-secondary" data-method="rotateTo" data-option="180">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.rotateTo(180)">
Rotate 180°
</span>
</button>
<button type="button" class="btn btn-secondary" data-method="scale" data-option="-2" data-second-option="-1">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.scale(-2, -1)">
Scale (-2, -1)
</span>
</button>
<textarea class="form-control" id="putData" rows="1" placeholder="Get data to here or set data with this value"></textarea>
</div><!-- /.docs-buttons -->
<div class="col-md-3 docs-toggles">
<!-- <h3>Toggles:</h3> -->
<div class="btn-group d-flex flex-nowrap" data-toggle="buttons">
<label class="btn btn-primary active">
<input type="radio" class="sr-only" id="aspectRatio1" name="aspectRatio" value="1.7777777777777777">
<span class="docs-tooltip" data-toggle="tooltip" title="aspectRatio: 16 / 9">
16:9
</span>
</label>
<label class="btn btn-primary">
<input type="radio" class="sr-only" id="aspectRatio2" name="aspectRatio" value="1.3333333333333333">
<span class="docs-tooltip" data-toggle="tooltip" title="aspectRatio: 4 / 3">
4:3
</span>
</label>
<label class="btn btn-primary">
<input type="radio" class="sr-only" id="aspectRatio3" name="aspectRatio" value="1">
<span class="docs-tooltip" data-toggle="tooltip" title="aspectRatio: 1 / 1">
1:1
</span>
</label>
<label class="btn btn-primary">
<input type="radio" class="sr-only" id="aspectRatio4" name="aspectRatio" value="0.6666666666666666">
<span class="docs-tooltip" data-toggle="tooltip" title="aspectRatio: 2 / 3">
2:3
</span>
</label>
<label class="btn btn-primary">
<input type="radio" class="sr-only" id="aspectRatio5" name="aspectRatio" value="NaN">
<span class="docs-tooltip" data-toggle="tooltip" title="aspectRatio: NaN">
Free
</span>
</label>
</div>
<div class="btn-group d-flex flex-nowrap" data-toggle="buttons">
<label class="btn btn-primary active">
<input type="radio" class="sr-only" id="viewMode0" name="viewMode" value="0" checked>
<span class="docs-tooltip" data-toggle="tooltip" title="View Mode 0">
VM0
</span>
</label>
<label class="btn btn-primary">
<input type="radio" class="sr-only" id="viewMode1" name="viewMode" value="1">
<span class="docs-tooltip" data-toggle="tooltip" title="View Mode 1">
VM1
</span>
</label>
<label class="btn btn-primary">
<input type="radio" class="sr-only" id="viewMode2" name="viewMode" value="2">
<span class="docs-tooltip" data-toggle="tooltip" title="View Mode 2">
VM2
</span>
</label>
<label class="btn btn-primary">
<input type="radio" class="sr-only" id="viewMode3" name="viewMode" value="3">
<span class="docs-tooltip" data-toggle="tooltip" title="View Mode 3">
VM3
</span>
</label>
</div>
<div class="dropdown dropup docs-options">
<button type="button" class="btn btn-primary btn-block dropdown-toggle" id="toggleOptions" data-toggle="dropdown" aria-expanded="true">
Toggle Options
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu" aria-labelledby="toggleOptions">
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="responsive" type="checkbox" name="responsive" checked>
<label class="form-check-label" for="responsive">responsive</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="restore" type="checkbox" name="restore" checked>
<label class="form-check-label" for="restore">restore</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="checkCrossOrigin" type="checkbox" name="checkCrossOrigin" checked>
<label class="form-check-label" for="checkCrossOrigin">checkCrossOrigin</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="checkOrientation" type="checkbox" name="checkOrientation" checked>
<label class="form-check-label" for="checkOrientation">checkOrientation</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="modal" type="checkbox" name="modal" checked>
<label class="form-check-label" for="modal">modal</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="guides" type="checkbox" name="guides" checked>
<label class="form-check-label" for="guides">guides</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="center" type="checkbox" name="center" checked>
<label class="form-check-label" for="center">center</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="highlight" type="checkbox" name="highlight" checked>
<label class="form-check-label" for="highlight">highlight</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="background" type="checkbox" name="background" checked>
<label class="form-check-label" for="background">background</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="autoCrop" type="checkbox" name="autoCrop" checked>
<label class="form-check-label" for="autoCrop">autoCrop</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="movable" type="checkbox" name="movable" checked>
<label class="form-check-label" for="movable">movable</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="rotatable" type="checkbox" name="rotatable" checked>
<label class="form-check-label" for="rotatable">rotatable</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="scalable" type="checkbox" name="scalable" checked>
<label class="form-check-label" for="scalable">scalable</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="zoomable" type="checkbox" name="zoomable" checked>
<label class="form-check-label" for="zoomable">zoomable</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="zoomOnTouch" type="checkbox" name="zoomOnTouch" checked>
<label class="form-check-label" for="zoomOnTouch">zoomOnTouch</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="zoomOnWheel" type="checkbox" name="zoomOnWheel" checked>
<label class="form-check-label" for="zoomOnWheel">zoomOnWheel</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="cropBoxMovable" type="checkbox" name="cropBoxMovable" checked>
<label class="form-check-label" for="cropBoxMovable">cropBoxMovable</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="cropBoxResizable" type="checkbox" name="cropBoxResizable" checked>
<label class="form-check-label" for="cropBoxResizable">cropBoxResizable</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="toggleDragModeOnDblclick" type="checkbox" name="toggleDragModeOnDblclick" checked>
<label class="form-check-label" for="toggleDragModeOnDblclick">toggleDragModeOnDblclick</label>
</div>
</li>
</ul>
</div><!-- /.dropdown -->
<a class="btn btn-success btn-block" data-toggle="tooltip" href="https://fengyuanchen.github.io/photo-editor" title="An advanced example of Cropper.js">Photo Editor</a>
</div><!-- /.docs-toggles -->
</div>
</div>
<!-- Footer -->
<footer class="footer">
<div class="container">
<p class="heart"></p>
<nav class="nav flex-wrap justify-content-center mb-3">
<a class="nav-link" href="https://github.com/fengyuanchen/cropperjs">GitHub</a>
<a class="nav-link" href="https://github.com/fengyuanchen/cropperjs/tree/master/examples">Examples</a>
<a class="nav-link" href="https://github.com/fengyuanchen/cropperjs/blob/master/LICENSE">License</a>
<a class="nav-link" href="http://chenfengyuan.com">About</a>
</nav>
</div>
</footer>
<!-- Scripts -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.3/js/bootstrap.bundle.min.js"></script>
<script src="https://fengyuanchen.github.io/js/common.js"></script>
<script src="js/cropper.js"></script>
<script src="js/main.js"></script>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,298 @@
window.onload = function () {
'use strict';
var Cropper = window.Cropper;
var URL = window.URL || window.webkitURL;
var container = document.querySelector('.img-container');
var image = container.getElementsByTagName('img').item(0);
var download = document.getElementById('download');
var actions = document.getElementById('actions');
var dataX = document.getElementById('dataX');
var dataY = document.getElementById('dataY');
var dataHeight = document.getElementById('dataHeight');
var dataWidth = document.getElementById('dataWidth');
var dataRotate = document.getElementById('dataRotate');
var dataScaleX = document.getElementById('dataScaleX');
var dataScaleY = document.getElementById('dataScaleY');
var options = {
aspectRatio: 16 / 9,
preview: '.img-preview',
ready: function (e) {
console.log(e.type);
},
cropstart: function (e) {
console.log(e.type, e.detail.action);
},
cropmove: function (e) {
console.log(e.type, e.detail.action);
},
cropend: function (e) {
console.log(e.type, e.detail.action);
},
crop: function (e) {
var data = e.detail;
console.log(e.type);
dataX.value = Math.round(data.x);
dataY.value = Math.round(data.y);
dataHeight.value = Math.round(data.height);
dataWidth.value = Math.round(data.width);
dataRotate.value = typeof data.rotate !== 'undefined' ? data.rotate : '';
dataScaleX.value = typeof data.scaleX !== 'undefined' ? data.scaleX : '';
dataScaleY.value = typeof data.scaleY !== 'undefined' ? data.scaleY : '';
},
zoom: function (e) {
console.log(e.type, e.detail.ratio);
}
};
var cropper = new Cropper(image, options);
var originalImageURL = image.src;
var uploadedImageType = 'image/jpeg';
var uploadedImageURL;
// Tooltip
$('[data-toggle="tooltip"]').tooltip();
// Buttons
if (!document.createElement('canvas').getContext) {
$('button[data-method="getCroppedCanvas"]').prop('disabled', true);
}
if (typeof document.createElement('cropper').style.transition === 'undefined') {
$('button[data-method="rotate"]').prop('disabled', true);
$('button[data-method="scale"]').prop('disabled', true);
}
// Download
if (typeof download.download === 'undefined') {
download.className += ' disabled';
}
// Options
actions.querySelector('.docs-toggles').onchange = function (event) {
var e = event || window.event;
var target = e.target || e.srcElement;
var cropBoxData;
var canvasData;
var isCheckbox;
var isRadio;
if (!cropper) {
return;
}
if (target.tagName.toLowerCase() === 'label') {
target = target.querySelector('input');
}
isCheckbox = target.type === 'checkbox';
isRadio = target.type === 'radio';
if (isCheckbox || isRadio) {
if (isCheckbox) {
options[target.name] = target.checked;
cropBoxData = cropper.getCropBoxData();
canvasData = cropper.getCanvasData();
options.ready = function () {
console.log('ready');
cropper.setCropBoxData(cropBoxData).setCanvasData(canvasData);
};
} else {
options[target.name] = target.value;
options.ready = function () {
console.log('ready');
};
}
// Restart
cropper.destroy();
cropper = new Cropper(image, options);
}
};
// Methods
actions.querySelector('.docs-buttons').onclick = function (event) {
var e = event || window.event;
var target = e.target || e.srcElement;
var cropped;
var result;
var input;
var data;
if (!cropper) {
return;
}
while (target !== this) {
if (target.getAttribute('data-method')) {
break;
}
target = target.parentNode;
}
if (target === this || target.disabled || target.className.indexOf('disabled') > -1) {
return;
}
data = {
method: target.getAttribute('data-method'),
target: target.getAttribute('data-target'),
option: target.getAttribute('data-option') || undefined,
secondOption: target.getAttribute('data-second-option') || undefined
};
cropped = cropper.cropped;
if (data.method) {
if (typeof data.target !== 'undefined') {
input = document.querySelector(data.target);
if (!target.hasAttribute('data-option') && data.target && input) {
try {
data.option = JSON.parse(input.value);
} catch (e) {
console.log(e.message);
}
}
}
switch (data.method) {
case 'rotate':
if (cropped && options.viewMode > 0) {
cropper.clear();
}
break;
case 'getCroppedCanvas':
try {
data.option = JSON.parse(data.option);
} catch (e) {
console.log(e.message);
}
if (uploadedImageType === 'image/jpeg') {
if (!data.option) {
data.option = {};
}
data.option.fillColor = '#fff';
}
break;
}
result = cropper[data.method](data.option, data.secondOption);
switch (data.method) {
case 'rotate':
if (cropped && options.viewMode > 0) {
cropper.crop();
}
break;
case 'scaleX':
case 'scaleY':
target.setAttribute('data-option', -data.option);
break;
case 'getCroppedCanvas':
if (result) {
// Bootstrap's Modal
$('#getCroppedCanvasModal').modal().find('.modal-body').html(result);
if (!download.disabled) {
download.href = result.toDataURL(uploadedImageType);
}
}
break;
case 'destroy':
cropper = null;
if (uploadedImageURL) {
URL.revokeObjectURL(uploadedImageURL);
uploadedImageURL = '';
image.src = originalImageURL;
}
break;
}
if (typeof result === 'object' && result !== cropper && input) {
try {
input.value = JSON.stringify(result);
} catch (e) {
console.log(e.message);
}
}
}
};
document.body.onkeydown = function (event) {
var e = event || window.event;
if (!cropper || this.scrollTop > 300) {
return;
}
switch (e.keyCode) {
case 37:
e.preventDefault();
cropper.move(-1, 0);
break;
case 38:
e.preventDefault();
cropper.move(0, -1);
break;
case 39:
e.preventDefault();
cropper.move(1, 0);
break;
case 40:
e.preventDefault();
cropper.move(0, 1);
break;
}
};
// Import image
var inputImage = document.getElementById('inputImage');
if (URL) {
inputImage.onchange = function () {
var files = this.files;
var file;
if (cropper && files && files.length) {
file = files[0];
if (/^image\/\w+/.test(file.type)) {
uploadedImageType = file.type;
if (uploadedImageURL) {
URL.revokeObjectURL(uploadedImageURL);
}
image.src = uploadedImageURL = URL.createObjectURL(file);
cropper.destroy();
cropper = new Cropper(image, options);
inputImage.value = null;
} else {
window.alert('Please choose an image file.');
}
}
};
} else {
inputImage.disabled = true;
inputImage.parentNode.className += ' disabled';
}
};

View file

@ -0,0 +1,71 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Cropper.js</title>
<link rel="stylesheet" href="../dist/cropper.css">
<style>
.container {
max-width: 640px;
margin: 20px auto;
}
img {
max-width: 100%;
}
</style>
</head>
<body>
<div class="container">
<h1>Cropper with a range of aspect ratio</h1>
<div>
<img id="image" src="../docs/images/picture.jpg" alt="Picture">
</div>
</div>
<script src="../dist/cropper.js"></script>
<script>
window.addEventListener('DOMContentLoaded', function () {
var image = document.querySelector('#image');
var minAspectRatio = 0.5;
var maxAspectRatio = 1.5;
var cropper = new Cropper(image, {
ready: function () {
var cropper = this.cropper;
var containerData = cropper.getContainerData();
var cropBoxData = cropper.getCropBoxData();
var aspectRatio = cropBoxData.width / cropBoxData.height;
var newCropBoxWidth;
if (aspectRatio < minAspectRatio || aspectRatio > maxAspectRatio) {
newCropBoxWidth = cropBoxData.height * ((minAspectRatio + maxAspectRatio) / 2);
cropper.setCropBoxData({
left: (containerData.width - newCropBoxWidth) / 2,
width: newCropBoxWidth
});
}
},
cropmove: function () {
var cropper = this.cropper;
var cropBoxData = cropper.getCropBoxData();
var aspectRatio = cropBoxData.width / cropBoxData.height;
if (aspectRatio < minAspectRatio) {
cropper.setCropBoxData({
width: cropBoxData.height * minAspectRatio
});
} else if (aspectRatio > maxAspectRatio) {
cropper.setCropBoxData({
width: cropBoxData.height * maxAspectRatio
});
}
}
});
});
</script>
</body>
</html>

View file

@ -0,0 +1,103 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Cropper.js</title>
<link rel="stylesheet" href="../dist/cropper.css">
<style>
.container {
max-width: 640px;
margin: 20px auto;
}
img {
max-width: 100%;
}
.cropper-view-box,
.cropper-face {
border-radius: 50%;
}
</style>
</head>
<body>
<div class="container">
<h1>Crop a round image</h1>
<h3>Image</h3>
<div>
<img id="image" src="../docs/images/picture.jpg" alt="Picture">
</div>
<h3>Result</h3>
<p>
<button type="button" id="button">Crop</button>
</p>
<div id="result"></div>
</div>
<script src="../dist/cropper.js"></script>
<script>
(function () {
function getRoundedCanvas(sourceCanvas) {
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
var width = sourceCanvas.width;
var height = sourceCanvas.height;
canvas.width = width;
canvas.height = height;
context.imageSmoothingEnabled = true;
context.drawImage(sourceCanvas, 0, 0, width, height);
context.globalCompositeOperation = 'destination-in';
context.beginPath();
context.arc(width / 2, height / 2, Math.min(width, height) / 2, 0, 2 * Math.PI, true);
context.fill();
return canvas;
}
window.addEventListener('DOMContentLoaded', function () {
var image = document.getElementById('image');
var button = document.getElementById('button');
var result = document.getElementById('result');
var croppable = false;
var cropper = new Cropper(image, {
aspectRatio: 1,
viewMode: 1,
ready: function () {
croppable = true;
}
});
button.onclick = function () {
var croppedCanvas;
var roundedCanvas;
var roundedImage;
if (!croppable) {
return;
}
// Crop
croppedCanvas = cropper.getCroppedCanvas();
// Round
roundedCanvas = getRoundedCanvas(croppedCanvas);
// Show
roundedImage = document.createElement('img');
roundedImage.src = roundedCanvas.toDataURL()
result.innerHTML = '';
result.appendChild(roundedImage);
};
});
})();
</script>
</body>
</html>

View file

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Cropper.js</title>
<link rel="stylesheet" href="../dist/cropper.css">
<style>
.container {
max-width: 640px;
margin: 20px auto;
}
img {
max-width: 100%;
}
</style>
</head>
<body>
<div class="container">
<h1>Crop a cross origin image</h1>
<p>A cross origin image with a <code>crossorigin</code> attribute and an available <code>Access-Control-Allow-Origin</code> response header can be cropped by Cropper.</p>
<h3>Image</h3>
<div>
<img id="image" src="https://raw.githubusercontent.com/fengyuanchen/cropperjs/master/docs/images/picture.jpg" crossorigin>
</div>
<h3>Result</h3>
<div id="result"></div>
</div>
<script src="../dist/cropper.js"></script>
<script>
window.addEventListener('DOMContentLoaded', function () {
var image = document.querySelector('#image');
var result = document.querySelector('#result');
var cropper = new Cropper(image, {
ready: function () {
var image = new Image();
image.src = cropper.getCroppedCanvas().toDataURL('image/jpeg');
result.appendChild(image);
},
});
});
</script>
</body>
</html>

View file

@ -0,0 +1,64 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Cropper.js</title>
<link rel="stylesheet" href="../dist/cropper.css">
<style>
.container {
max-width: 640px;
margin: 20px auto;
}
img {
max-width: 100%;
}
</style>
</head>
<body>
<div class="container">
<h1>Crop on canvas with Cropper</h1>
<h3>Image</h3>
<div class="img-container">
<img id="image" src="../docs/images/picture.jpg" alt="Picture">
</div>
<h3>Canvas</h3>
<div class="img-container">
<canvas id="canvas"></canvas>
</div>
</div>
<script src="../dist/cropper.js"></script>
<script>
function start() {
var width = this.offsetWidth;
var height = this.offsetHeight;
var cropper;
canvas.width = width;
canvas.height = height;
canvas.getContext('2d').drawImage(
this,
0, 0, this.naturalWidth, this.naturalHeight,
0, 0, width, height
);
cropper = new Cropper(canvas);
}
window.addEventListener('DOMContentLoaded', function () {
var canvas = document.getElementById('canvas');
var image = document.getElementById('image');
if (image.complete) {
start.call(image);
} else {
image.onload = start;
}
});
</script>
</body>
</html>

View file

@ -0,0 +1,76 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Cropper.js</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css">
<link rel="stylesheet" href="../dist/cropper.css">
<style>
.img-container img {
max-width: 100%;
}
</style>
</head>
<body>
<div class="container">
<h1>Cropper in a Bootstrap modal</h1>
<!-- Button trigger modal -->
<button type="button" class="btn btn-primary" data-target="#modal" data-toggle="modal">
Launch the demo
</button>
<!-- Modal -->
<div class="modal fade" id="modal" role="dialog" aria-labelledby="modalLabel" tabindex="-1">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalLabel">Cropper</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="img-container">
<img id="image" src="../docs/images/picture.jpg" alt="Picture">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js"></script>
<script src="../dist/cropper.js"></script>
<script>
window.addEventListener('DOMContentLoaded', function () {
var image = document.getElementById('image');
var cropBoxData;
var canvasData;
var cropper;
$('#modal').on('shown.bs.modal', function () {
cropper = new Cropper(image, {
autoCropArea: 0.5,
ready: function () {
// Strict mode: set crop box data first
cropper.setCropBoxData(cropBoxData).setCanvasData(canvasData);
}
});
}).on('hidden.bs.modal', function () {
cropBoxData = cropper.getCropBoxData();
canvasData = cropper.getCanvasData();
cropper.destroy();
});
});
</script>
</body>
</html>

View file

@ -0,0 +1,123 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Cropper.js</title>
<link rel="stylesheet" href="../dist/cropper.css">
<style>
.container {
max-width: 960px;
margin: 20px auto;
}
img {
max-width: 100%;
}
.row,
.preview {
overflow: hidden;
}
.col {
float: left;
}
.col-6 {
width: 50%;
}
.col-3 {
width: 25%;
}
.col-2 {
width: 16.7%;
}
.col-1 {
width: 8.3%;
}
</style>
</head>
<body>
<div class="container">
<h1>Customize preview for Cropper</h1>
<div class="row">
<div class="col col-6">
<img id="image" src="../docs/images/picture.jpg" alt="Picture">
</div>
<div class="col col-3">
<div class="preview"></div>
</div>
<div class="col col-2">
<div class="preview"></div>
</div>
<div class="col col-1">
<div class="preview"></div>
</div>
</div>
</div>
<script src="../dist/cropper.js"></script>
<script>
function each(arr, callback) {
var length = arr.length;
var i;
for (i = 0; i < length; i++) {
callback.call(arr, arr[i], i, arr);
}
return arr;
}
window.addEventListener('DOMContentLoaded', function () {
var image = document.querySelector('#image');
var previews = document.querySelectorAll('.preview');
var cropper = new Cropper(image, {
ready: function () {
var clone = this.cloneNode();
clone.className = ''
clone.style.cssText = (
'display: block;' +
'width: 100%;' +
'min-width: 0;' +
'min-height: 0;' +
'max-width: none;' +
'max-height: none;'
);
each(previews, function (elem) {
elem.appendChild(clone.cloneNode());
});
},
crop: function (e) {
var data = e.detail;
var cropper = this.cropper;
var imageData = cropper.getImageData();
var previewAspectRatio = data.width / data.height;
each(previews, function (elem) {
var previewImage = elem.getElementsByTagName('img').item(0);
var previewWidth = elem.offsetWidth;
var previewHeight = previewWidth / previewAspectRatio;
var imageScaledRatio = data.width / previewWidth;
elem.style.height = previewHeight + 'px';
previewImage.style.width = imageData.naturalWidth / imageScaledRatio + 'px';
previewImage.style.height = imageData.naturalHeight / imageScaledRatio + 'px';
previewImage.style.marginLeft = -data.x / imageScaledRatio + 'px';
previewImage.style.marginTop = -data.y / imageScaledRatio + 'px';
});
}
});
});
</script>
</body>
</html>

View file

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Cropper.js</title>
<link rel="stylesheet" href="../dist/cropper.css">
<style>
.container {
max-width: 640px;
margin: 20px auto;
}
img {
max-width: 100%;
}
</style>
</head>
<body>
<div class="container">
<h1>Cropper with fixed crop box</h1>
<div>
<img id="image" src="../docs/images/picture.jpg" alt="Picture">
</div>
</div>
<script src="../dist/cropper.js"></script>
<script>
window.addEventListener('DOMContentLoaded', function () {
var image = document.querySelector('#image');
var cropper = new Cropper(image, {
dragMode: 'move',
aspectRatio: 16 / 9,
autoCropArea: 0.65,
restore: false,
guides: false,
center: false,
highlight: false,
cropBoxMovable: false,
cropBoxResizable: false,
toggleDragModeOnDblclick: false,
});
});
</script>
</body>
</html>

View file

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Cropper.js</title>
<link rel="stylesheet" href="../dist/cropper.css">
<style>
.container {
max-width: 640px;
margin: 20px auto;
}
img {
max-width: 100%;
}
</style>
</head>
<body>
<div class="container">
<h1>Cropper with full crop box</h1>
<div>
<img id="image" src="../docs/images/picture.jpg" alt="Picture">
</div>
</div>
<script src="../dist/cropper.js"></script>
<script>
window.addEventListener('DOMContentLoaded', function () {
var image = document.querySelector('#image');
var cropper = new Cropper(image, {
viewMode: 3,
dragMode: 'move',
autoCropArea: 1,
restore: false,
modal: false,
guides: false,
highlight: false,
cropBoxMovable: false,
cropBoxResizable: false,
toggleDragModeOnDblclick: false,
});
});
</script>
</body>
</html>

View file

@ -0,0 +1,123 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Cropper.js</title>
<link rel="stylesheet" href="../dist/cropper.css">
<style>
.container {
max-width: 640px;
margin: 20px auto;
}
img {
max-width: 100%;
}
.cropper-view-box,
.cropper-face {
background-color: black;
opacity: 1;
}
</style>
</head>
<body>
<div class="container">
<h1>Mask an image (Redaction)</h1>
<h3>Image</h3>
<div>
<img id="image" src="../docs/images/picture.jpg" alt="Picture">
</div>
<h3>Result</h3>
<p><button type="button" id="button">Mask</button></p>
<div id="result"></div>
</div>
<script src="../dist/cropper.js"></script>
<script>
(function () {
function getMaskedCanvas(sourceCanvas, sourceImage, cropper) {
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
var maskWidth = cropper.getData().width;
var maskHeight = cropper.getData().height;
var maskTop = cropper.getData().y;
var maskLeft = cropper.getData().x;
var imageWidth = cropper.getImageData().naturalWidth;
var imageHeight = cropper.getImageData().naturalHeight;
var imageLeft = cropper.getImageData().left;
var imageTop = cropper.getImageData().top;
var imageAspect = cropper.getImageData().aspectRatio;
canvas.width = imageWidth;
canvas.height = imageHeight;
// debug
console.log('Image Width: ' + imageWidth + ' Image Height: ' + imageHeight + ' Image Aspect Ratio: ' + imageAspect );
console.log('Image Left: ' + imageLeft + ' Image Top: ' + imageTop );
console.log('Mask Width: ' + maskWidth + ' Mask Height: ' + maskHeight + ' Mask Left: ' + maskLeft + ' Mask Top: ' + maskTop);
context.imageSmoothingEnabled = true;
context.drawImage(image, 0, 0, imageWidth, imageHeight);
context.fillRect(maskLeft, maskTop, maskWidth, maskHeight);
return canvas;
}
window.addEventListener('DOMContentLoaded', function () {
var image = document.getElementById('image');
var button = document.getElementById('button');
var result = document.getElementById('result');
var croppable = false;
var cropper = new Cropper(image, {
viewMode: 0,
guides: true,
center: true,
highlight: true,
cropBoxMovable: true,
cropBoxResizable: true,
ready: function () {
croppable = true;
}
});
button.onclick = function () {
var croppedCanvas;
var roundedCanvas;
var roundedImage;
if (!croppable) {
return;
}
// Crop
croppedCanvas = cropper.getCroppedCanvas();
// Mask
maskedCanvas = getMaskedCanvas(croppedCanvas, image, cropper);
// Show
maskedImage = document.createElement('img');
maskedImage.src = maskedCanvas.toDataURL()
result.innerHTML = '';
result.appendChild(maskedImage);
};
});
})();
</script>
</body>
</html>

View file

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Cropper.js</title>
<link rel="stylesheet" href="../dist/cropper.css">
<style>
.container {
max-width: 640px;
margin: 20px auto;
}
img {
max-width: 100%;
}
</style>
</head>
<body>
<div class="container">
<h1>Multiple Croppers</h1>
<h3>Cropper</h3>
<div>
<img src="../docs/images/picture.jpg" alt="Picture">
</div>
<h3>Another</h3>
<div>
<img src="../docs/images/picture.jpg" alt="Picture">
</div>
</div>
<script src="../dist/cropper.js"></script>
<script>
window.addEventListener('DOMContentLoaded', function () {
var images = document.querySelectorAll('img');
var length = images.length;
var croppers = [];
var i;
for (i = 0; i < length; i++) {
croppers.push(new Cropper(images[i]));
}
});
</script>
</body>
</html>

View file

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Cropper.js</title>
<link rel="stylesheet" href="../dist/cropper.css">
<style>
.container {
max-width: 640px;
margin: 20px auto;
}
img {
width: 100%;
}
</style>
</head>
<body>
<div class="container">
<h1>Cropper with responsive container</h1>
<p>
<button type="button" id="replace">Replace Image</button>
</p>
<div>
<img id="image" src="../docs/images/picture.jpg" alt="Picture">
</div>
</div>
<script src="../dist/cropper.js"></script>
<script>
window.addEventListener('DOMContentLoaded', function () {
var image = document.querySelector('#image');
var cropper = new Cropper(image, {
movable: false,
zoomable: false,
rotatable: false,
scalable: false
});
document.getElementById('replace').onclick = function () {
cropper.replace('../docs/images/picture-2.jpg');
};
});
</script>
</body>
</html>

9111
library/cropperjs/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,76 @@
{
"name": "cropperjs",
"description": "JavaScript image cropper.",
"version": "1.2.2",
"main": "dist/cropper.common.js",
"module": "dist/cropper.esm.js",
"browser": "dist/cropper.js",
"style": "dist/cropper.css",
"repository": "fengyuanchen/cropperjs",
"homepage": "https://fengyuanchen.github.io/cropperjs",
"license": "MIT",
"author": {
"name": "Chen Fengyuan",
"url": "http://chenfengyuan.com"
},
"files": [
"src",
"dist"
],
"keywords": [
"image",
"crop",
"cropping",
"move",
"zoom",
"rotate",
"scale",
"cropper",
"cropperjs",
"cropper.js",
"html",
"css",
"javascript",
"front-end",
"web",
"development"
],
"scripts": {
"build": "npm run build:css && npm run build:js",
"build:css": "postcss src/css/cropper.css -o dist/cropper.css --no-map",
"build:js": "rollup -c",
"clear": "del-cli dist",
"compress": "npm run compress:css && npm run compress:js",
"compress:css": "postcss dist/cropper.css -u cssnano -o dist/cropper.min.css --no-map",
"compress:js": "uglifyjs dist/cropper.js -o dist/cropper.min.js -c -m --comments /^!/",
"copy": "cpy dist/cropper.css docs/css",
"lint": "eslint src/js --fix",
"release": "npm run clear && npm run lint && npm run build && npm run compress && npm run copy && npm test",
"start": "npm-run-all --parallel watch:*",
"test": "node-qunit-phantomjs test/index.html --timeout 10",
"watch:css": "postcss src/css/cropper.css -o docs/css/cropper.css -m -w",
"watch:js": "rollup -c -m -w"
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-plugin-external-helpers": "^6.22.0",
"babel-preset-env": "^1.6.1",
"cpy-cli": "^1.0.1",
"cssnano": "^3.10.0",
"del-cli": "^1.1.0",
"eslint": "^4.14.0",
"eslint-config-airbnb-base": "^12.1.0",
"eslint-plugin-import": "^2.8.0",
"node-qunit-phantomjs": "^2.0.0",
"npm-run-all": "^4.1.2",
"postcss-cli": "^4.1.1",
"postcss-cssnext": "^3.0.2",
"postcss-header": "^1.0.0",
"postcss-url": "^7.3.0",
"rollup": "^0.53.3",
"rollup-plugin-babel": "^3.0.3",
"rollup-watch": "^4.3.1",
"stylefmt": "^6.0.0",
"uglify-js": "^3.3.4"
}
}

View file

@ -0,0 +1,14 @@
const rollupConfig = require('./rollup.config');
module.exports = {
plugins: {
'postcss-cssnext': {},
'postcss-url': {
url: 'inline',
},
'postcss-header': {
header: rollupConfig.banner,
},
stylefmt: {},
},
};

View file

@ -0,0 +1,50 @@
const babel = require('rollup-plugin-babel');
const pkg = require('./package');
const now = new Date();
const banner = `/*!
* Cropper.js v${pkg.version}
* https://github.com/${pkg.repository}
*
* Copyright (c) 2015-${now.getFullYear()} ${pkg.author.name}
* Released under the ${pkg.license} license
*
* Date: ${now.toISOString()}
*/
`;
module.exports = {
// Export banner for PostCSS
banner,
input: 'src/js/cropper.js',
output: [
{
banner,
file: 'dist/cropper.js',
format: 'umd',
name: 'Cropper',
},
{
banner,
file: 'dist/cropper.common.js',
format: 'cjs',
},
{
banner,
file: 'dist/cropper.esm.js',
format: 'es',
},
{
banner,
file: 'docs/js/cropper.js',
format: 'umd',
name: 'Cropper',
},
],
plugins: [
babel({
exclude: 'node_modules/**',
plugins: ['external-helpers'],
}),
],
};

View file

@ -0,0 +1,290 @@
:root {
--blue: #39f;
}
.cropper-container {
direction: ltr;
font-size: 0;
line-height: 0;
position: relative;
touch-action: none;
user-select: none;
& img {
/* Avoid margin top issue (Occur only when margin-top <= -height) */
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-wrap-box,
.cropper-canvas,
.cropper-drag-box,
.cropper-crop-box,
.cropper-modal {
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
}
.cropper-wrap-box,
.cropper-canvas {
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: color(var(--blue) alpha(75%));
outline: 1px solid var(--blue);
overflow: hidden;
width: 100%;
}
.cropper-dashed {
border: 0 dashed #eee;
display: block;
opacity: .5;
position: absolute;
&.dashed-h {
border-bottom-width: 1px;
border-top-width: 1px;
height: calc(100 / 3)%;
left: 0;
top: calc(100 / 3)%;
width: 100%;
}
&.dashed-v {
border-left-width: 1px;
border-right-width: 1px;
height: 100%;
left: calc(100 / 3)%;
top: 0;
width: calc(100 / 3)%;
}
}
.cropper-center {
display: block;
height: 0;
left: 50%;
opacity: .75;
position: absolute;
top: 50%;
width: 0;
&:before,
&:after {
background-color: #eee;
content: ' ';
display: block;
position: absolute;
}
&:before {
height: 1px;
left: -3px;
top: 0;
width: 7px;
}
&: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: var(--blue);
&.line-e {
cursor: ew-resize;
right: -3px;
top: 0;
width: 5px;
}
&.line-n {
cursor: ns-resize;
height: 5px;
left: 0;
top: -3px;
}
&.line-w {
cursor: ew-resize;
left: -3px;
top: 0;
width: 5px;
}
&.line-s {
bottom: -3px;
cursor: ns-resize;
height: 5px;
left: 0;
}
}
.cropper-point {
background-color: var(--blue);
height: 5px;
opacity: .75;
width: 5px;
&.point-e {
cursor: ew-resize;
margin-top: -3px;
right: -3px;
top: 50%;
}
&.point-n {
cursor: ns-resize;
left: 50%;
margin-left: -3px;
top: -3px;
}
&.point-w {
cursor: ew-resize;
left: -3px;
margin-top: -3px;
top: 50%;
}
&.point-s {
bottom: -3px;
cursor: s-resize;
left: 50%;
margin-left: -3px;
}
&.point-ne {
cursor: nesw-resize;
right: -3px;
top: -3px;
}
&.point-nw {
cursor: nwse-resize;
left: -3px;
top: -3px;
}
&.point-sw {
bottom: -3px;
cursor: nesw-resize;
left: -3px;
}
&.point-se {
bottom: -3px;
cursor: nwse-resize;
height: 20px;
opacity: 1;
right: -3px;
width: 20px;
@media (min-width: 768px) {
height: 15px;
width: 15px;
}
@media (min-width: 992px) {
height: 10px;
width: 10px;
}
@media (min-width: 1200px) {
height: 5px;
opacity: .75;
width: 5px;
}
}
&.point-se:before {
background-color: var(--blue);
bottom: -50%;
content: ' ';
display: block;
height: 200%;
opacity: 0;
position: absolute;
right: -50%;
width: 200%;
}
}
.cropper-invisible {
opacity: 0;
}
.cropper-bg {
background-image: url('../images/bg.png');
}
.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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

View file

@ -0,0 +1,471 @@
import {
ACTION_ALL,
ACTION_CROP,
ACTION_EAST,
ACTION_MOVE,
ACTION_NORTH,
ACTION_NORTH_EAST,
ACTION_NORTH_WEST,
ACTION_SOUTH,
ACTION_SOUTH_EAST,
ACTION_SOUTH_WEST,
ACTION_WEST,
ACTION_ZOOM,
CLASS_HIDDEN,
} from './constants';
import {
each,
getMaxZoomRatio,
getOffset,
removeClass,
} from './utilities';
export default {
change(e) {
const {
options,
canvasData,
containerData,
cropBoxData,
pointers,
} = this;
let { action } = this;
let { aspectRatio } = options;
let {
left,
top,
width,
height,
} = cropBoxData;
const right = left + width;
const bottom = top + height;
let minLeft = 0;
let minTop = 0;
let maxWidth = containerData.width;
let maxHeight = containerData.height;
let renderable = true;
let offset;
// Locking aspect ratio in "free mode" by holding shift key
if (!aspectRatio && e.shiftKey) {
aspectRatio = width && height ? width / height : 1;
}
if (this.limited) {
({ minLeft, minTop } = cropBoxData);
maxWidth = minLeft + Math.min(
containerData.width,
canvasData.width,
canvasData.left + canvasData.width,
);
maxHeight = minTop + Math.min(
containerData.height,
canvasData.height,
canvasData.top + canvasData.height,
);
}
const pointer = pointers[Object.keys(pointers)[0]];
const range = {
x: pointer.endX - pointer.startX,
y: pointer.endY - pointer.startY,
};
const check = (side) => {
switch (side) {
case ACTION_EAST:
if (right + range.x > maxWidth) {
range.x = maxWidth - right;
}
break;
case ACTION_WEST:
if (left + range.x < minLeft) {
range.x = minLeft - left;
}
break;
case ACTION_NORTH:
if (top + range.y < minTop) {
range.y = minTop - top;
}
break;
case ACTION_SOUTH:
if (bottom + range.y > maxHeight) {
range.y = maxHeight - bottom;
}
break;
default:
}
};
switch (action) {
// Move crop box
case ACTION_ALL:
left += range.x;
top += range.y;
break;
// Resize crop box
case ACTION_EAST:
if (range.x >= 0 && (right >= maxWidth || (aspectRatio &&
(top <= minTop || bottom >= maxHeight)))) {
renderable = false;
break;
}
check(ACTION_EAST);
width += range.x;
if (aspectRatio) {
height = width / aspectRatio;
top -= (range.x / aspectRatio) / 2;
}
if (width < 0) {
action = ACTION_WEST;
width = 0;
}
break;
case ACTION_NORTH:
if (range.y <= 0 && (top <= minTop || (aspectRatio &&
(left <= minLeft || right >= maxWidth)))) {
renderable = false;
break;
}
check(ACTION_NORTH);
height -= range.y;
top += range.y;
if (aspectRatio) {
width = height * aspectRatio;
left += (range.y * aspectRatio) / 2;
}
if (height < 0) {
action = ACTION_SOUTH;
height = 0;
}
break;
case ACTION_WEST:
if (range.x <= 0 && (left <= minLeft || (aspectRatio &&
(top <= minTop || bottom >= maxHeight)))) {
renderable = false;
break;
}
check(ACTION_WEST);
width -= range.x;
left += range.x;
if (aspectRatio) {
height = width / aspectRatio;
top += (range.x / aspectRatio) / 2;
}
if (width < 0) {
action = ACTION_EAST;
width = 0;
}
break;
case ACTION_SOUTH:
if (range.y >= 0 && (bottom >= maxHeight || (aspectRatio &&
(left <= minLeft || right >= maxWidth)))) {
renderable = false;
break;
}
check(ACTION_SOUTH);
height += range.y;
if (aspectRatio) {
width = height * aspectRatio;
left -= (range.y * aspectRatio) / 2;
}
if (height < 0) {
action = ACTION_NORTH;
height = 0;
}
break;
case ACTION_NORTH_EAST:
if (aspectRatio) {
if (range.y <= 0 && (top <= minTop || right >= maxWidth)) {
renderable = false;
break;
}
check(ACTION_NORTH);
height -= range.y;
top += range.y;
width = height * aspectRatio;
} else {
check(ACTION_NORTH);
check(ACTION_EAST);
if (range.x >= 0) {
if (right < maxWidth) {
width += range.x;
} else if (range.y <= 0 && top <= minTop) {
renderable = false;
}
} else {
width += range.x;
}
if (range.y <= 0) {
if (top > minTop) {
height -= range.y;
top += range.y;
}
} else {
height -= range.y;
top += range.y;
}
}
if (width < 0 && height < 0) {
action = ACTION_SOUTH_WEST;
height = 0;
width = 0;
} else if (width < 0) {
action = ACTION_NORTH_WEST;
width = 0;
} else if (height < 0) {
action = ACTION_SOUTH_EAST;
height = 0;
}
break;
case ACTION_NORTH_WEST:
if (aspectRatio) {
if (range.y <= 0 && (top <= minTop || left <= minLeft)) {
renderable = false;
break;
}
check(ACTION_NORTH);
height -= range.y;
top += range.y;
width = height * aspectRatio;
left += range.y * aspectRatio;
} else {
check(ACTION_NORTH);
check(ACTION_WEST);
if (range.x <= 0) {
if (left > minLeft) {
width -= range.x;
left += range.x;
} else if (range.y <= 0 && top <= minTop) {
renderable = false;
}
} else {
width -= range.x;
left += range.x;
}
if (range.y <= 0) {
if (top > minTop) {
height -= range.y;
top += range.y;
}
} else {
height -= range.y;
top += range.y;
}
}
if (width < 0 && height < 0) {
action = ACTION_SOUTH_EAST;
height = 0;
width = 0;
} else if (width < 0) {
action = ACTION_NORTH_EAST;
width = 0;
} else if (height < 0) {
action = ACTION_SOUTH_WEST;
height = 0;
}
break;
case ACTION_SOUTH_WEST:
if (aspectRatio) {
if (range.x <= 0 && (left <= minLeft || bottom >= maxHeight)) {
renderable = false;
break;
}
check(ACTION_WEST);
width -= range.x;
left += range.x;
height = width / aspectRatio;
} else {
check(ACTION_SOUTH);
check(ACTION_WEST);
if (range.x <= 0) {
if (left > minLeft) {
width -= range.x;
left += range.x;
} else if (range.y >= 0 && bottom >= maxHeight) {
renderable = false;
}
} else {
width -= range.x;
left += range.x;
}
if (range.y >= 0) {
if (bottom < maxHeight) {
height += range.y;
}
} else {
height += range.y;
}
}
if (width < 0 && height < 0) {
action = ACTION_NORTH_EAST;
height = 0;
width = 0;
} else if (width < 0) {
action = ACTION_SOUTH_EAST;
width = 0;
} else if (height < 0) {
action = ACTION_NORTH_WEST;
height = 0;
}
break;
case ACTION_SOUTH_EAST:
if (aspectRatio) {
if (range.x >= 0 && (right >= maxWidth || bottom >= maxHeight)) {
renderable = false;
break;
}
check(ACTION_EAST);
width += range.x;
height = width / aspectRatio;
} else {
check(ACTION_SOUTH);
check(ACTION_EAST);
if (range.x >= 0) {
if (right < maxWidth) {
width += range.x;
} else if (range.y >= 0 && bottom >= maxHeight) {
renderable = false;
}
} else {
width += range.x;
}
if (range.y >= 0) {
if (bottom < maxHeight) {
height += range.y;
}
} else {
height += range.y;
}
}
if (width < 0 && height < 0) {
action = ACTION_NORTH_WEST;
height = 0;
width = 0;
} else if (width < 0) {
action = ACTION_SOUTH_WEST;
width = 0;
} else if (height < 0) {
action = ACTION_NORTH_EAST;
height = 0;
}
break;
// Move canvas
case ACTION_MOVE:
this.move(range.x, range.y);
renderable = false;
break;
// Zoom canvas
case ACTION_ZOOM:
this.zoom(getMaxZoomRatio(pointers), e);
renderable = false;
break;
// Create crop box
case ACTION_CROP:
if (!range.x || !range.y) {
renderable = false;
break;
}
offset = getOffset(this.cropper);
left = pointer.startX - offset.left;
top = pointer.startY - offset.top;
width = cropBoxData.minWidth;
height = cropBoxData.minHeight;
if (range.x > 0) {
action = range.y > 0 ? ACTION_SOUTH_EAST : ACTION_NORTH_EAST;
} else if (range.x < 0) {
left -= width;
action = range.y > 0 ? ACTION_SOUTH_WEST : ACTION_NORTH_WEST;
}
if (range.y < 0) {
top -= height;
}
// Show the crop box if is hidden
if (!this.cropped) {
removeClass(this.cropBox, CLASS_HIDDEN);
this.cropped = true;
if (this.limited) {
this.limitCropBox(true, true);
}
}
break;
default:
}
if (renderable) {
cropBoxData.width = width;
cropBoxData.height = height;
cropBoxData.left = left;
cropBoxData.top = top;
this.action = action;
this.renderCropBox();
}
// Override
each(pointers, (p) => {
p.startX = p.endX;
p.startY = p.endY;
});
},
};

View file

@ -0,0 +1,56 @@
export const WINDOW = typeof window !== 'undefined' ? window : {};
export const NAMESPACE = 'cropper';
// Actions
export const ACTION_ALL = 'all';
export const ACTION_CROP = 'crop';
export const ACTION_MOVE = 'move';
export const ACTION_ZOOM = 'zoom';
export const ACTION_EAST = 'e';
export const ACTION_WEST = 'w';
export const ACTION_SOUTH = 's';
export const ACTION_NORTH = 'n';
export const ACTION_NORTH_EAST = 'ne';
export const ACTION_NORTH_WEST = 'nw';
export const ACTION_SOUTH_EAST = 'se';
export const ACTION_SOUTH_WEST = 'sw';
// Classes
export const CLASS_CROP = `${NAMESPACE}-crop`;
export const CLASS_DISABLED = `${NAMESPACE}-disabled`;
export const CLASS_HIDDEN = `${NAMESPACE}-hidden`;
export const CLASS_HIDE = `${NAMESPACE}-hide`;
export const CLASS_INVISIBLE = `${NAMESPACE}-invisible`;
export const CLASS_MODAL = `${NAMESPACE}-modal`;
export const CLASS_MOVE = `${NAMESPACE}-move`;
// Data keys
export const DATA_ACTION = 'action';
export const DATA_PREVIEW = 'preview';
// Drag modes
export const DRAG_MODE_CROP = 'crop';
export const DRAG_MODE_MOVE = 'move';
export const DRAG_MODE_NONE = 'none';
// Events
export const EVENT_CROP = 'crop';
export const EVENT_CROP_END = 'cropend';
export const EVENT_CROP_MOVE = 'cropmove';
export const EVENT_CROP_START = 'cropstart';
export const EVENT_DBLCLICK = 'dblclick';
export const EVENT_ERROR = 'error';
export const EVENT_LOAD = 'load';
export const EVENT_POINTER_DOWN = WINDOW.PointerEvent ? 'pointerdown' : 'touchstart mousedown';
export const EVENT_POINTER_MOVE = WINDOW.PointerEvent ? 'pointermove' : 'touchmove mousemove';
export const EVENT_POINTER_UP = WINDOW.PointerEvent ? 'pointerup pointercancel' : 'touchend touchcancel mouseup';
export const EVENT_READY = 'ready';
export const EVENT_RESIZE = 'resize';
export const EVENT_WHEEL = 'wheel mousewheel DOMMouseScroll';
export const EVENT_ZOOM = 'zoom';
// RegExps
export const REGEXP_ACTIONS = /^(?:e|w|s|n|se|sw|ne|nw|all|crop|move|zoom)$/;
export const REGEXP_DATA_URL = /^data:/;
export const REGEXP_DATA_URL_JPEG = /^data:image\/jpeg;base64,/;
export const REGEXP_TAG_NAME = /^(?:img|canvas)$/i;

View file

@ -0,0 +1,421 @@
import DEFAULTS from './defaults';
import TEMPLATE from './template';
import render from './render';
import preview from './preview';
import events from './events';
import handlers from './handlers';
import change from './change';
import methods from './methods';
import {
ACTION_ALL,
CLASS_HIDDEN,
CLASS_HIDE,
CLASS_INVISIBLE,
CLASS_MODAL,
CLASS_MOVE,
DATA_ACTION,
EVENT_CROP,
EVENT_ERROR,
EVENT_LOAD,
EVENT_READY,
NAMESPACE,
REGEXP_DATA_URL,
REGEXP_DATA_URL_JPEG,
REGEXP_TAG_NAME,
WINDOW,
} from './constants';
import {
addClass,
addListener,
addTimestamp,
arrayBufferToDataURL,
dataURLToArrayBuffer,
dispatchEvent,
extend,
getData,
getImageNaturalSizes,
getOrientation,
isCrossOriginURL,
isFunction,
isPlainObject,
parseOrientation,
proxy,
removeClass,
removeListener,
setData,
} from './utilities';
const AnotherCropper = WINDOW.Cropper;
class Cropper {
/**
* Create a new Cropper.
* @param {Element} element - The target element for cropping.
* @param {Object} [options={}] - The configuration options.
*/
constructor(element, options = {}) {
if (!element || !REGEXP_TAG_NAME.test(element.tagName)) {
throw new Error('The first argument is required and must be an <img> or <canvas> element.');
}
this.element = element;
this.options = extend({}, DEFAULTS, isPlainObject(options) && options);
this.complete = false;
this.cropped = false;
this.disabled = false;
this.isImg = false;
this.limited = false;
this.loaded = false;
this.ready = false;
this.replaced = false;
this.wheeling = false;
this.originalUrl = '';
this.canvasData = null;
this.cropBoxData = null;
this.previews = null;
this.pointers = {};
this.init();
}
init() {
const { element } = this;
const tagName = element.tagName.toLowerCase();
let url;
if (getData(element, NAMESPACE)) {
return;
}
setData(element, NAMESPACE, this);
if (tagName === 'img') {
this.isImg = true;
// e.g.: "img/picture.jpg"
url = element.getAttribute('src') || '';
this.originalUrl = url;
// Stop when it's a blank image
if (!url) {
return;
}
// e.g.: "http://example.com/img/picture.jpg"
url = element.src;
} else if (tagName === 'canvas' && window.HTMLCanvasElement) {
url = element.toDataURL();
}
this.load(url);
}
load(url) {
if (!url) {
return;
}
this.url = url;
this.imageData = {};
const { element, options } = this;
if (!options.checkOrientation || !window.ArrayBuffer) {
this.clone();
return;
}
// XMLHttpRequest disallows to open a Data URL in some browsers like IE11 and Safari
if (REGEXP_DATA_URL.test(url)) {
if (REGEXP_DATA_URL_JPEG.test(url)) {
this.read(dataURLToArrayBuffer(url));
} else {
this.clone();
}
return;
}
const xhr = new XMLHttpRequest();
xhr.onerror = () => {
this.clone();
};
xhr.onload = () => {
this.read(xhr.response);
};
// Bust cache when there is a "crossOrigin" property
if (options.checkCrossOrigin && isCrossOriginURL(url) && element.crossOrigin) {
url = addTimestamp(url);
}
xhr.open('get', url);
xhr.responseType = 'arraybuffer';
xhr.withCredentials = element.crossOrigin === 'use-credentials';
xhr.send();
}
read(arrayBuffer) {
const { options, imageData } = this;
const orientation = getOrientation(arrayBuffer);
let rotate = 0;
let scaleX = 1;
let scaleY = 1;
if (orientation > 1) {
this.url = arrayBufferToDataURL(arrayBuffer, 'image/jpeg');
({ rotate, scaleX, scaleY } = parseOrientation(orientation));
}
if (options.rotatable) {
imageData.rotate = rotate;
}
if (options.scalable) {
imageData.scaleX = scaleX;
imageData.scaleY = scaleY;
}
this.clone();
}
clone() {
const { element, url } = this;
let crossOrigin;
let crossOriginUrl;
if (this.options.checkCrossOrigin && isCrossOriginURL(url)) {
({ crossOrigin } = element);
if (crossOrigin) {
crossOriginUrl = url;
} else {
crossOrigin = 'anonymous';
// Bust cache when there is not a "crossOrigin" property
crossOriginUrl = addTimestamp(url);
}
}
this.crossOrigin = crossOrigin;
this.crossOriginUrl = crossOriginUrl;
const image = document.createElement('img');
if (crossOrigin) {
image.crossOrigin = crossOrigin;
}
image.src = crossOriginUrl || url;
const start = proxy(this.start, this);
const stop = proxy(this.stop, this);
this.image = image;
this.onStart = start;
this.onStop = stop;
if (this.isImg) {
if (element.complete) {
this.start();
} else {
addListener(element, EVENT_LOAD, start);
}
} else {
addListener(image, EVENT_LOAD, start);
addListener(image, EVENT_ERROR, stop);
addClass(image, CLASS_HIDE);
element.parentNode.insertBefore(image, element.nextSibling);
}
}
start(event) {
const image = this.isImg ? this.element : this.image;
if (event) {
removeListener(image, EVENT_LOAD, this.onStart);
removeListener(image, EVENT_ERROR, this.onStop);
}
getImageNaturalSizes(image, (naturalWidth, naturalHeight) => {
extend(this.imageData, {
naturalWidth,
naturalHeight,
aspectRatio: naturalWidth / naturalHeight,
});
this.loaded = true;
this.build();
});
}
stop() {
const { image } = this;
removeListener(image, EVENT_LOAD, this.onStart);
removeListener(image, EVENT_ERROR, this.onStop);
image.parentNode.removeChild(image);
this.image = null;
}
build() {
if (!this.loaded) {
return;
}
// Unbuild first when replace
if (this.ready) {
this.unbuild();
}
const { element, options, image } = this;
// Create cropper elements
const container = element.parentNode;
const template = document.createElement('div');
template.innerHTML = TEMPLATE;
const cropper = template.querySelector(`.${NAMESPACE}-container`);
const canvas = cropper.querySelector(`.${NAMESPACE}-canvas`);
const dragBox = cropper.querySelector(`.${NAMESPACE}-drag-box`);
const cropBox = cropper.querySelector(`.${NAMESPACE}-crop-box`);
const face = cropBox.querySelector(`.${NAMESPACE}-face`);
this.container = container;
this.cropper = cropper;
this.canvas = canvas;
this.dragBox = dragBox;
this.cropBox = cropBox;
this.viewBox = cropper.querySelector(`.${NAMESPACE}-view-box`);
this.face = face;
canvas.appendChild(image);
// Hide the original image
addClass(element, CLASS_HIDDEN);
// Inserts the cropper after to the current image
container.insertBefore(cropper, element.nextSibling);
// Show the image if is hidden
if (!this.isImg) {
removeClass(image, CLASS_HIDE);
}
this.initPreview();
this.bind();
options.aspectRatio = Math.max(0, options.aspectRatio) || NaN;
options.viewMode = Math.max(0, Math.min(3, Math.round(options.viewMode))) || 0;
this.cropped = options.autoCrop;
if (options.autoCrop) {
if (options.modal) {
addClass(dragBox, CLASS_MODAL);
}
} else {
addClass(cropBox, CLASS_HIDDEN);
}
if (!options.guides) {
addClass(cropBox.getElementsByClassName(`${NAMESPACE}-dashed`), CLASS_HIDDEN);
}
if (!options.center) {
addClass(cropBox.getElementsByClassName(`${NAMESPACE}-center`), CLASS_HIDDEN);
}
if (options.background) {
addClass(cropper, `${NAMESPACE}-bg`);
}
if (!options.highlight) {
addClass(face, CLASS_INVISIBLE);
}
if (options.cropBoxMovable) {
addClass(face, CLASS_MOVE);
setData(face, DATA_ACTION, ACTION_ALL);
}
if (!options.cropBoxResizable) {
addClass(cropBox.getElementsByClassName(`${NAMESPACE}-line`), CLASS_HIDDEN);
addClass(cropBox.getElementsByClassName(`${NAMESPACE}-point`), CLASS_HIDDEN);
}
this.setDragMode(options.dragMode);
this.render();
this.ready = true;
this.setData(options.data);
// Call the "ready" option asynchronously to keep "image.cropper" is defined
this.completing = setTimeout(() => {
if (isFunction(options.ready)) {
addListener(element, EVENT_READY, options.ready, {
once: true,
});
}
dispatchEvent(element, EVENT_READY);
dispatchEvent(element, EVENT_CROP, this.getData());
this.complete = true;
}, 0);
}
unbuild() {
if (!this.ready) {
return;
}
if (!this.complete) {
clearTimeout(this.completing);
}
this.ready = false;
this.complete = false;
this.initialImageData = null;
// Clear `initialCanvasData` is necessary when replace
this.initialCanvasData = null;
this.initialCropBoxData = null;
this.containerData = null;
this.canvasData = null;
// Clear `cropBoxData` is necessary when replace
this.cropBoxData = null;
this.unbind();
this.resetPreview();
this.previews = null;
this.viewBox = null;
this.cropBox = null;
this.dragBox = null;
this.canvas = null;
this.container = null;
this.cropper.parentNode.removeChild(this.cropper);
this.cropper = null;
}
/**
* Get the no conflict cropper class.
* @returns {Cropper} The cropper class.
*/
static noConflict() {
window.Cropper = AnotherCropper;
return Cropper;
}
/**
* Change the default options.
* @param {Object} options - The new default options.
*/
static setDefaults(options) {
extend(DEFAULTS, isPlainObject(options) && options);
}
}
extend(Cropper.prototype, render, preview, events, handlers, change, methods);
export default Cropper;

View file

@ -0,0 +1,99 @@
import {
DRAG_MODE_CROP,
} from './constants';
export default {
// Define the view mode of the cropper
viewMode: 0, // 0, 1, 2, 3
// Define the dragging mode of the cropper
dragMode: DRAG_MODE_CROP, // 'crop', 'move' or 'none'
// Define the aspect ratio of the crop box
aspectRatio: NaN,
// An object with the previous cropping result data
data: null,
// A selector for adding extra containers to preview
preview: '',
// Re-render the cropper when resize the window
responsive: true,
// Restore the cropped area after resize the window
restore: true,
// Check if the current image is a cross-origin image
checkCrossOrigin: true,
// Check the current image's Exif Orientation information
checkOrientation: true,
// Show the black modal
modal: true,
// Show the dashed lines for guiding
guides: true,
// Show the center indicator for guiding
center: true,
// Show the white modal to highlight the crop box
highlight: true,
// Show the grid background
background: true,
// Enable to crop the image automatically when initialize
autoCrop: true,
// Define the percentage of automatic cropping area when initializes
autoCropArea: 0.8,
// Enable to move the image
movable: true,
// Enable to rotate the image
rotatable: true,
// Enable to scale the image
scalable: true,
// Enable to zoom the image
zoomable: true,
// Enable to zoom the image by dragging touch
zoomOnTouch: true,
// Enable to zoom the image by wheeling mouse
zoomOnWheel: true,
// Define zoom ratio when zoom the image by wheeling mouse
wheelZoomRatio: 0.1,
// Enable to move the crop box
cropBoxMovable: true,
// Enable to resize the crop box
cropBoxResizable: true,
// Toggle drag mode between "crop" and "move" when click twice on the cropper
toggleDragModeOnDblclick: true,
// Size limitation
minCanvasWidth: 0,
minCanvasHeight: 0,
minCropBoxWidth: 0,
minCropBoxHeight: 0,
minContainerWidth: 200,
minContainerHeight: 100,
// Shortcuts of events
ready: null,
cropstart: null,
cropmove: null,
cropend: null,
crop: null,
zoom: null,
};

View file

@ -0,0 +1,111 @@
import {
EVENT_CROP,
EVENT_CROP_END,
EVENT_CROP_MOVE,
EVENT_CROP_START,
EVENT_DBLCLICK,
EVENT_POINTER_DOWN,
EVENT_POINTER_MOVE,
EVENT_POINTER_UP,
EVENT_RESIZE,
EVENT_WHEEL,
EVENT_ZOOM,
} from './constants';
import {
addListener,
isFunction,
proxy,
removeListener,
} from './utilities';
export default {
bind() {
const { element, options, cropper } = this;
if (isFunction(options.cropstart)) {
addListener(element, EVENT_CROP_START, options.cropstart);
}
if (isFunction(options.cropmove)) {
addListener(element, EVENT_CROP_MOVE, options.cropmove);
}
if (isFunction(options.cropend)) {
addListener(element, EVENT_CROP_END, options.cropend);
}
if (isFunction(options.crop)) {
addListener(element, EVENT_CROP, options.crop);
}
if (isFunction(options.zoom)) {
addListener(element, EVENT_ZOOM, options.zoom);
}
addListener(cropper, EVENT_POINTER_DOWN, (this.onCropStart = proxy(this.cropStart, this)));
if (options.zoomable && options.zoomOnWheel) {
addListener(cropper, EVENT_WHEEL, (this.onWheel = proxy(this.wheel, this)));
}
if (options.toggleDragModeOnDblclick) {
addListener(cropper, EVENT_DBLCLICK, (this.onDblclick = proxy(this.dblclick, this)));
}
addListener(
element.ownerDocument,
EVENT_POINTER_MOVE,
(this.onCropMove = proxy(this.cropMove, this)),
);
addListener(
element.ownerDocument,
EVENT_POINTER_UP,
(this.onCropEnd = proxy(this.cropEnd, this)),
);
if (options.responsive) {
addListener(window, EVENT_RESIZE, (this.onResize = proxy(this.resize, this)));
}
},
unbind() {
const { element, options, cropper } = this;
if (isFunction(options.cropstart)) {
removeListener(element, EVENT_CROP_START, options.cropstart);
}
if (isFunction(options.cropmove)) {
removeListener(element, EVENT_CROP_MOVE, options.cropmove);
}
if (isFunction(options.cropend)) {
removeListener(element, EVENT_CROP_END, options.cropend);
}
if (isFunction(options.crop)) {
removeListener(element, EVENT_CROP, options.crop);
}
if (isFunction(options.zoom)) {
removeListener(element, EVENT_ZOOM, options.zoom);
}
removeListener(cropper, EVENT_POINTER_DOWN, this.onCropStart);
if (options.zoomable && options.zoomOnWheel) {
removeListener(cropper, EVENT_WHEEL, this.onWheel);
}
if (options.toggleDragModeOnDblclick) {
removeListener(cropper, EVENT_DBLCLICK, this.onDblclick);
}
removeListener(element.ownerDocument, EVENT_POINTER_MOVE, this.onCropMove);
removeListener(element.ownerDocument, EVENT_POINTER_UP, this.onCropEnd);
if (options.responsive) {
removeListener(window, EVENT_RESIZE, this.onResize);
}
},
};

View file

@ -0,0 +1,212 @@
import {
ACTION_CROP,
ACTION_ZOOM,
CLASS_CROP,
CLASS_MODAL,
DATA_ACTION,
DRAG_MODE_CROP,
DRAG_MODE_MOVE,
DRAG_MODE_NONE,
EVENT_CROP_END,
EVENT_CROP_MOVE,
EVENT_CROP_START,
REGEXP_ACTIONS,
} from './constants';
import {
addClass,
dispatchEvent,
each,
extend,
getData,
getPointer,
hasClass,
toggleClass,
} from './utilities';
export default {
resize() {
const { options, container, containerData } = this;
const minContainerWidth = Number(options.minContainerWidth) || 200;
const minContainerHeight = Number(options.minContainerHeight) || 100;
if (this.disabled || containerData.width <= minContainerWidth ||
containerData.height <= minContainerHeight) {
return;
}
const ratio = container.offsetWidth / containerData.width;
// Resize when width changed or height changed
if (ratio !== 1 || container.offsetHeight !== containerData.height) {
let canvasData;
let cropBoxData;
if (options.restore) {
canvasData = this.getCanvasData();
cropBoxData = this.getCropBoxData();
}
this.render();
if (options.restore) {
this.setCanvasData(each(canvasData, (n, i) => {
canvasData[i] = n * ratio;
}));
this.setCropBoxData(each(cropBoxData, (n, i) => {
cropBoxData[i] = n * ratio;
}));
}
}
},
dblclick() {
if (this.disabled || this.options.dragMode === DRAG_MODE_NONE) {
return;
}
this.setDragMode(hasClass(this.dragBox, CLASS_CROP) ? DRAG_MODE_MOVE : DRAG_MODE_CROP);
},
wheel(e) {
const ratio = Number(this.options.wheelZoomRatio) || 0.1;
let delta = 1;
if (this.disabled) {
return;
}
e.preventDefault();
// Limit wheel speed to prevent zoom too fast (#21)
if (this.wheeling) {
return;
}
this.wheeling = true;
setTimeout(() => {
this.wheeling = false;
}, 50);
if (e.deltaY) {
delta = e.deltaY > 0 ? 1 : -1;
} else if (e.wheelDelta) {
delta = -e.wheelDelta / 120;
} else if (e.detail) {
delta = e.detail > 0 ? 1 : -1;
}
this.zoom(-delta * ratio, e);
},
cropStart(e) {
if (this.disabled) {
return;
}
const { options, pointers } = this;
let action;
if (e.changedTouches) {
// Handle touch event
each(e.changedTouches, (touch) => {
pointers[touch.identifier] = getPointer(touch);
});
} else {
// Handle mouse event and pointer event
pointers[e.pointerId || 0] = getPointer(e);
}
if (Object.keys(pointers).length > 1 && options.zoomable && options.zoomOnTouch) {
action = ACTION_ZOOM;
} else {
action = getData(e.target, DATA_ACTION);
}
if (!REGEXP_ACTIONS.test(action)) {
return;
}
if (dispatchEvent(this.element, EVENT_CROP_START, {
originalEvent: e,
action,
}) === false) {
return;
}
e.preventDefault();
this.action = action;
this.cropping = false;
if (action === ACTION_CROP) {
this.cropping = true;
addClass(this.dragBox, CLASS_MODAL);
}
},
cropMove(e) {
const { action } = this;
if (this.disabled || !action) {
return;
}
const { pointers } = this;
e.preventDefault();
if (dispatchEvent(this.element, EVENT_CROP_MOVE, {
originalEvent: e,
action,
}) === false) {
return;
}
if (e.changedTouches) {
each(e.changedTouches, (touch) => {
extend(pointers[touch.identifier], getPointer(touch, true));
});
} else {
extend(pointers[e.pointerId || 0], getPointer(e, true));
}
this.change(e);
},
cropEnd(e) {
if (this.disabled) {
return;
}
const { action, pointers } = this;
if (e.changedTouches) {
each(e.changedTouches, (touch) => {
delete pointers[touch.identifier];
});
} else {
delete pointers[e.pointerId || 0];
}
if (!action) {
return;
}
e.preventDefault();
if (!Object.keys(pointers).length) {
this.action = '';
}
if (this.cropping) {
this.cropping = false;
toggleClass(this.dragBox, CLASS_MODAL, this.cropped && this.options.modal);
}
dispatchEvent(this.element, EVENT_CROP_END, {
originalEvent: e,
action,
});
},
};

View file

@ -0,0 +1,842 @@
import {
CLASS_CROP,
CLASS_DISABLED,
CLASS_HIDDEN,
CLASS_MODAL,
CLASS_MOVE,
DATA_ACTION,
DRAG_MODE_CROP,
DRAG_MODE_MOVE,
DRAG_MODE_NONE,
EVENT_LOAD,
EVENT_ZOOM,
NAMESPACE,
} from './constants';
import {
addClass,
dispatchEvent,
each,
extend,
getAdjustedSizes,
getOffset,
getPointersCenter,
getSourceCanvas,
isFunction,
isNumber,
isPlainObject,
isUndefined,
normalizeDecimalNumber,
removeClass,
removeData,
removeListener,
setData,
toggleClass,
} from './utilities';
export default {
// Show the crop box manually
crop() {
if (this.ready && !this.disabled) {
if (!this.cropped) {
this.cropped = true;
this.limitCropBox(true, true);
if (this.options.modal) {
addClass(this.dragBox, CLASS_MODAL);
}
removeClass(this.cropBox, CLASS_HIDDEN);
}
this.setCropBoxData(this.initialCropBoxData);
}
return this;
},
// Reset the image and crop box to their initial states
reset() {
if (this.ready && !this.disabled) {
this.imageData = extend({}, this.initialImageData);
this.canvasData = extend({}, this.initialCanvasData);
this.cropBoxData = extend({}, this.initialCropBoxData);
this.renderCanvas();
if (this.cropped) {
this.renderCropBox();
}
}
return this;
},
// Clear the crop box
clear() {
if (this.cropped && !this.disabled) {
extend(this.cropBoxData, {
left: 0,
top: 0,
width: 0,
height: 0,
});
this.cropped = false;
this.renderCropBox();
this.limitCanvas(true, true);
// Render canvas after crop box rendered
this.renderCanvas();
removeClass(this.dragBox, CLASS_MODAL);
addClass(this.cropBox, CLASS_HIDDEN);
}
return this;
},
/**
* Replace the image's src and rebuild the cropper
* @param {string} url - The new URL.
* @param {boolean} [onlyColorChanged] - Indicate if the new image only changed color.
* @returns {Object} this
*/
replace(url, onlyColorChanged = false) {
if (!this.disabled && url) {
if (this.isImg) {
this.element.src = url;
}
if (onlyColorChanged) {
this.url = url;
this.image.src = url;
if (this.ready) {
this.image2.src = url;
each(this.previews, (element) => {
element.getElementsByTagName('img')[0].src = url;
});
}
} else {
if (this.isImg) {
this.replaced = true;
}
// Clear previous data
this.options.data = null;
this.load(url);
}
}
return this;
},
// Enable (unfreeze) the cropper
enable() {
if (this.ready) {
this.disabled = false;
removeClass(this.cropper, CLASS_DISABLED);
}
return this;
},
// Disable (freeze) the cropper
disable() {
if (this.ready) {
this.disabled = true;
addClass(this.cropper, CLASS_DISABLED);
}
return this;
},
// Destroy the cropper and remove the instance from the image
destroy() {
const { element, image } = this;
if (this.loaded) {
if (this.isImg && this.replaced) {
element.src = this.originalUrl;
}
this.unbuild();
removeClass(element, CLASS_HIDDEN);
} else if (this.isImg) {
removeListener(element, EVENT_LOAD, this.onStart);
} else if (image) {
image.parentNode.removeChild(image);
}
removeData(element, NAMESPACE);
return this;
},
/**
* Move the canvas with relative offsets
* @param {number} offsetX - The relative offset distance on the x-axis.
* @param {number} offsetY - The relative offset distance on the y-axis.
* @returns {Object} this
*/
move(offsetX, offsetY) {
const { left, top } = this.canvasData;
return this.moveTo(
isUndefined(offsetX) ? offsetX : (left + Number(offsetX)),
isUndefined(offsetY) ? offsetY : (top + Number(offsetY)),
);
},
/**
* Move the canvas to an absolute point
* @param {number} x - The x-axis coordinate.
* @param {number} [y=x] - The y-axis coordinate.
* @returns {Object} this
*/
moveTo(x, y = x) {
const { canvasData } = this;
let changed = false;
x = Number(x);
y = Number(y);
if (this.ready && !this.disabled && this.options.movable) {
if (isNumber(x)) {
canvasData.left = x;
changed = true;
}
if (isNumber(y)) {
canvasData.top = y;
changed = true;
}
if (changed) {
this.renderCanvas(true);
}
}
return this;
},
/**
* Zoom the canvas with a relative ratio
* @param {number} ratio - The target ratio.
* @param {Event} _originalEvent - The original event if any.
* @returns {Object} this
*/
zoom(ratio, _originalEvent) {
const { canvasData } = this;
ratio = Number(ratio);
if (ratio < 0) {
ratio = 1 / (1 - ratio);
} else {
ratio = 1 + ratio;
}
return this.zoomTo((canvasData.width * ratio) / canvasData.naturalWidth, null, _originalEvent);
},
/**
* Zoom the canvas to an absolute ratio
* @param {number} ratio - The target ratio.
* @param {Object} pivot - The zoom pivot point coordinate.
* @param {Event} _originalEvent - The original event if any.
* @returns {Object} this
*/
zoomTo(ratio, pivot, _originalEvent) {
const { options, canvasData } = this;
const {
width,
height,
naturalWidth,
naturalHeight,
} = canvasData;
ratio = Number(ratio);
if (ratio >= 0 && this.ready && !this.disabled && options.zoomable) {
const newWidth = naturalWidth * ratio;
const newHeight = naturalHeight * ratio;
if (dispatchEvent(this.element, EVENT_ZOOM, {
originalEvent: _originalEvent,
oldRatio: width / naturalWidth,
ratio: newWidth / naturalWidth,
}) === false) {
return this;
}
if (_originalEvent) {
const { pointers } = this;
const offset = getOffset(this.cropper);
const center = pointers && Object.keys(pointers).length ? getPointersCenter(pointers) : {
pageX: _originalEvent.pageX,
pageY: _originalEvent.pageY,
};
// Zoom from the triggering point of the event
canvasData.left -= (newWidth - width) * (
((center.pageX - offset.left) - canvasData.left) / width
);
canvasData.top -= (newHeight - height) * (
((center.pageY - offset.top) - canvasData.top) / height
);
} else if (isPlainObject(pivot) && isNumber(pivot.x) && isNumber(pivot.y)) {
canvasData.left -= (newWidth - width) * (
(pivot.x - canvasData.left) / width
);
canvasData.top -= (newHeight - height) * (
(pivot.y - canvasData.top) / height
);
} else {
// Zoom from the center of the canvas
canvasData.left -= (newWidth - width) / 2;
canvasData.top -= (newHeight - height) / 2;
}
canvasData.width = newWidth;
canvasData.height = newHeight;
this.renderCanvas(true);
}
return this;
},
/**
* Rotate the canvas with a relative degree
* @param {number} degree - The rotate degree.
* @returns {Object} this
*/
rotate(degree) {
return this.rotateTo((this.imageData.rotate || 0) + Number(degree));
},
/**
* Rotate the canvas to an absolute degree
* @param {number} degree - The rotate degree.
* @returns {Object} this
*/
rotateTo(degree) {
degree = Number(degree);
if (isNumber(degree) && this.ready && !this.disabled && this.options.rotatable) {
this.imageData.rotate = degree % 360;
this.renderCanvas(true, true);
}
return this;
},
/**
* Scale the image on the x-axis.
* @param {number} scaleX - The scale ratio on the x-axis.
* @returns {Object} this
*/
scaleX(scaleX) {
const { scaleY } = this.imageData;
return this.scale(scaleX, isNumber(scaleY) ? scaleY : 1);
},
/**
* Scale the image on the y-axis.
* @param {number} scaleY - The scale ratio on the y-axis.
* @returns {Object} this
*/
scaleY(scaleY) {
const { scaleX } = this.imageData;
return this.scale(isNumber(scaleX) ? scaleX : 1, scaleY);
},
/**
* Scale the image
* @param {number} scaleX - The scale ratio on the x-axis.
* @param {number} [scaleY=scaleX] - The scale ratio on the y-axis.
* @returns {Object} this
*/
scale(scaleX, scaleY = scaleX) {
const { imageData } = this;
let transformed = false;
scaleX = Number(scaleX);
scaleY = Number(scaleY);
if (this.ready && !this.disabled && this.options.scalable) {
if (isNumber(scaleX)) {
imageData.scaleX = scaleX;
transformed = true;
}
if (isNumber(scaleY)) {
imageData.scaleY = scaleY;
transformed = true;
}
if (transformed) {
this.renderCanvas(true, true);
}
}
return this;
},
/**
* Get the cropped area position and size data (base on the original image)
* @param {boolean} [rounded=false] - Indicate if round the data values or not.
* @returns {Object} The result cropped data.
*/
getData(rounded = false) {
const {
options,
imageData,
canvasData,
cropBoxData,
} = this;
let data;
if (this.ready && this.cropped) {
data = {
x: cropBoxData.left - canvasData.left,
y: cropBoxData.top - canvasData.top,
width: cropBoxData.width,
height: cropBoxData.height,
};
const ratio = imageData.width / imageData.naturalWidth;
each(data, (n, i) => {
n /= ratio;
data[i] = rounded ? Math.round(n) : n;
});
} else {
data = {
x: 0,
y: 0,
width: 0,
height: 0,
};
}
if (options.rotatable) {
data.rotate = imageData.rotate || 0;
}
if (options.scalable) {
data.scaleX = imageData.scaleX || 1;
data.scaleY = imageData.scaleY || 1;
}
return data;
},
/**
* Set the cropped area position and size with new data
* @param {Object} data - The new data.
* @returns {Object} this
*/
setData(data) {
const { options, imageData, canvasData } = this;
const cropBoxData = {};
if (isFunction(data)) {
data = data.call(this.element);
}
if (this.ready && !this.disabled && isPlainObject(data)) {
let transformed = false;
if (options.rotatable) {
if (isNumber(data.rotate) && data.rotate !== imageData.rotate) {
imageData.rotate = data.rotate;
transformed = true;
}
}
if (options.scalable) {
if (isNumber(data.scaleX) && data.scaleX !== imageData.scaleX) {
imageData.scaleX = data.scaleX;
transformed = true;
}
if (isNumber(data.scaleY) && data.scaleY !== imageData.scaleY) {
imageData.scaleY = data.scaleY;
transformed = true;
}
}
if (transformed) {
this.renderCanvas(true, true);
}
const ratio = imageData.width / imageData.naturalWidth;
if (isNumber(data.x)) {
cropBoxData.left = (data.x * ratio) + canvasData.left;
}
if (isNumber(data.y)) {
cropBoxData.top = (data.y * ratio) + canvasData.top;
}
if (isNumber(data.width)) {
cropBoxData.width = data.width * ratio;
}
if (isNumber(data.height)) {
cropBoxData.height = data.height * ratio;
}
this.setCropBoxData(cropBoxData);
}
return this;
},
/**
* Get the container size data.
* @returns {Object} The result container data.
*/
getContainerData() {
return this.ready ? extend({}, this.containerData) : {};
},
/**
* Get the image position and size data.
* @returns {Object} The result image data.
*/
getImageData() {
return this.loaded ? extend({}, this.imageData) : {};
},
/**
* Get the canvas position and size data.
* @returns {Object} The result canvas data.
*/
getCanvasData() {
const { canvasData } = this;
const data = {};
if (this.ready) {
each([
'left',
'top',
'width',
'height',
'naturalWidth',
'naturalHeight',
], (n) => {
data[n] = canvasData[n];
});
}
return data;
},
/**
* Set the canvas position and size with new data.
* @param {Object} data - The new canvas data.
* @returns {Object} this
*/
setCanvasData(data) {
const { canvasData } = this;
const { aspectRatio } = canvasData;
if (isFunction(data)) {
data = data.call(this.element);
}
if (this.ready && !this.disabled && isPlainObject(data)) {
if (isNumber(data.left)) {
canvasData.left = data.left;
}
if (isNumber(data.top)) {
canvasData.top = data.top;
}
if (isNumber(data.width)) {
canvasData.width = data.width;
canvasData.height = data.width / aspectRatio;
} else if (isNumber(data.height)) {
canvasData.height = data.height;
canvasData.width = data.height * aspectRatio;
}
this.renderCanvas(true);
}
return this;
},
/**
* Get the crop box position and size data.
* @returns {Object} The result crop box data.
*/
getCropBoxData() {
const { cropBoxData } = this;
let data;
if (this.ready && this.cropped) {
data = {
left: cropBoxData.left,
top: cropBoxData.top,
width: cropBoxData.width,
height: cropBoxData.height,
};
}
return data || {};
},
/**
* Set the crop box position and size with new data.
* @param {Object} data - The new crop box data.
* @returns {Object} this
*/
setCropBoxData(data) {
const { cropBoxData } = this;
const { aspectRatio } = this.options;
let widthChanged;
let heightChanged;
if (isFunction(data)) {
data = data.call(this.element);
}
if (this.ready && this.cropped && !this.disabled && isPlainObject(data)) {
if (isNumber(data.left)) {
cropBoxData.left = data.left;
}
if (isNumber(data.top)) {
cropBoxData.top = data.top;
}
if (isNumber(data.width) && data.width !== cropBoxData.width) {
widthChanged = true;
cropBoxData.width = data.width;
}
if (isNumber(data.height) && data.height !== cropBoxData.height) {
heightChanged = true;
cropBoxData.height = data.height;
}
if (aspectRatio) {
if (widthChanged) {
cropBoxData.height = cropBoxData.width / aspectRatio;
} else if (heightChanged) {
cropBoxData.width = cropBoxData.height * aspectRatio;
}
}
this.renderCropBox();
}
return this;
},
/**
* Get a canvas drawn the cropped image.
* @param {Object} [options={}] - The config options.
* @returns {HTMLCanvasElement} - The result canvas.
*/
getCroppedCanvas(options = {}) {
if (!this.ready || !window.HTMLCanvasElement) {
return null;
}
const { canvasData } = this;
const source = getSourceCanvas(this.image, this.imageData, canvasData, options);
// Returns the source canvas if it is not cropped.
if (!this.cropped) {
return source;
}
let {
x: initialX,
y: initialY,
width: initialWidth,
height: initialHeight,
} = this.getData();
const ratio = source.width / Math.floor(canvasData.naturalWidth);
if (ratio !== 1) {
initialX *= ratio;
initialY *= ratio;
initialWidth *= ratio;
initialHeight *= ratio;
}
const aspectRatio = initialWidth / initialHeight;
const maxSizes = getAdjustedSizes({
aspectRatio,
width: options.maxWidth || Infinity,
height: options.maxHeight || Infinity,
});
const minSizes = getAdjustedSizes({
aspectRatio,
width: options.minWidth || 0,
height: options.minHeight || 0,
}, 'cover');
let {
width,
height,
} = getAdjustedSizes({
aspectRatio,
width: options.width || (ratio !== 1 ? source.width : initialWidth),
height: options.height || (ratio !== 1 ? source.height : initialHeight),
});
width = Math.min(maxSizes.width, Math.max(minSizes.width, width));
height = Math.min(maxSizes.height, Math.max(minSizes.height, height));
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.width = normalizeDecimalNumber(width);
canvas.height = normalizeDecimalNumber(height);
context.fillStyle = options.fillColor || 'transparent';
context.fillRect(0, 0, width, height);
const { imageSmoothingEnabled = true, imageSmoothingQuality } = options;
context.imageSmoothingEnabled = imageSmoothingEnabled;
if (imageSmoothingQuality) {
context.imageSmoothingQuality = imageSmoothingQuality;
}
// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D.drawImage
const sourceWidth = source.width;
const sourceHeight = source.height;
// Source canvas parameters
let srcX = initialX;
let srcY = initialY;
let srcWidth;
let srcHeight;
// Destination canvas parameters
let dstX;
let dstY;
let dstWidth;
let dstHeight;
if (srcX <= -initialWidth || srcX > sourceWidth) {
srcX = 0;
srcWidth = 0;
dstX = 0;
dstWidth = 0;
} else if (srcX <= 0) {
dstX = -srcX;
srcX = 0;
srcWidth = Math.min(sourceWidth, initialWidth + srcX);
dstWidth = srcWidth;
} else if (srcX <= sourceWidth) {
dstX = 0;
srcWidth = Math.min(initialWidth, sourceWidth - srcX);
dstWidth = srcWidth;
}
if (srcWidth <= 0 || srcY <= -initialHeight || srcY > sourceHeight) {
srcY = 0;
srcHeight = 0;
dstY = 0;
dstHeight = 0;
} else if (srcY <= 0) {
dstY = -srcY;
srcY = 0;
srcHeight = Math.min(sourceHeight, initialHeight + srcY);
dstHeight = srcHeight;
} else if (srcY <= sourceHeight) {
dstY = 0;
srcHeight = Math.min(initialHeight, sourceHeight - srcY);
dstHeight = srcHeight;
}
// All the numerical parameters should be integer for `drawImage`
// https://github.com/fengyuanchen/cropper/issues/476
const params = [
srcX,
srcY,
srcWidth,
srcHeight,
];
// Avoid "IndexSizeError"
if (dstWidth > 0 && dstHeight > 0) {
const scale = width / initialWidth;
params.push(
dstX * scale,
dstY * scale,
dstWidth * scale,
dstHeight * scale,
);
}
context.drawImage(source, ...params.map(param => Math.floor(normalizeDecimalNumber(param))));
return canvas;
},
/**
* Change the aspect ratio of the crop box.
* @param {number} aspectRatio - The new aspect ratio.
* @returns {Object} this
*/
setAspectRatio(aspectRatio) {
const { options } = this;
if (!this.disabled && !isUndefined(aspectRatio)) {
// 0 -> NaN
options.aspectRatio = Math.max(0, aspectRatio) || NaN;
if (this.ready) {
this.initCropBox();
if (this.cropped) {
this.renderCropBox();
}
}
}
return this;
},
/**
* Change the drag mode.
* @param {string} mode - The new drag mode.
* @returns {Object} this
*/
setDragMode(mode) {
const { options, dragBox, face } = this;
if (this.loaded && !this.disabled) {
const croppable = mode === DRAG_MODE_CROP;
const movable = options.movable && mode === DRAG_MODE_MOVE;
mode = (croppable || movable) ? mode : DRAG_MODE_NONE;
setData(dragBox, DATA_ACTION, mode);
toggleClass(dragBox, CLASS_CROP, croppable);
toggleClass(dragBox, CLASS_MOVE, movable);
if (!options.cropBoxMovable) {
// Sync drag mode to crop box when it is not movable
setData(face, DATA_ACTION, mode);
toggleClass(face, CLASS_CROP, croppable);
toggleClass(face, CLASS_MOVE, movable);
}
}
return this;
},
};

View file

@ -0,0 +1,142 @@
import {
DATA_PREVIEW,
} from './constants';
import {
each,
empty,
extend,
getData,
getTransforms,
removeData,
setData,
setStyle,
} from './utilities';
export default {
initPreview() {
const { crossOrigin } = this;
const { preview } = this.options;
const url = crossOrigin ? this.crossOriginUrl : this.url;
const image = document.createElement('img');
if (crossOrigin) {
image.crossOrigin = crossOrigin;
}
image.src = url;
this.viewBox.appendChild(image);
this.image2 = image;
if (!preview) {
return;
}
const previews = preview.querySelector ? [preview] : document.querySelectorAll(preview);
this.previews = previews;
each(previews, (element) => {
const img = document.createElement('img');
// Save the original size for recover
setData(element, DATA_PREVIEW, {
width: element.offsetWidth,
height: element.offsetHeight,
html: element.innerHTML,
});
if (crossOrigin) {
img.crossOrigin = crossOrigin;
}
img.src = url;
/**
* Override img element styles
* Add `display:block` to avoid margin top issue
* Add `height:auto` to override `height` attribute on IE8
* (Occur only when margin-top <= -height)
*/
img.style.cssText = (
'display:block;' +
'width:100%;' +
'height:auto;' +
'min-width:0!important;' +
'min-height:0!important;' +
'max-width:none!important;' +
'max-height:none!important;' +
'image-orientation:0deg!important;"'
);
empty(element);
element.appendChild(img);
});
},
resetPreview() {
each(this.previews, (element) => {
const data = getData(element, DATA_PREVIEW);
setStyle(element, {
width: data.width,
height: data.height,
});
element.innerHTML = data.html;
removeData(element, DATA_PREVIEW);
});
},
preview() {
const { imageData, canvasData, cropBoxData } = this;
const { width: cropBoxWidth, height: cropBoxHeight } = cropBoxData;
const { width, height } = imageData;
const left = cropBoxData.left - canvasData.left - imageData.left;
const top = cropBoxData.top - canvasData.top - imageData.top;
if (!this.cropped || this.disabled) {
return;
}
setStyle(this.image2, extend({
width,
height,
}, getTransforms(extend({
translateX: -left,
translateY: -top,
}, imageData))));
each(this.previews, (element) => {
const data = getData(element, DATA_PREVIEW);
const originalWidth = data.width;
const originalHeight = data.height;
let newWidth = originalWidth;
let newHeight = originalHeight;
let ratio = 1;
if (cropBoxWidth) {
ratio = originalWidth / cropBoxWidth;
newHeight = cropBoxHeight * ratio;
}
if (cropBoxHeight && newHeight > originalHeight) {
ratio = originalHeight / cropBoxHeight;
newWidth = cropBoxWidth * ratio;
newHeight = originalHeight;
}
setStyle(element, {
width: newWidth,
height: newHeight,
});
setStyle(element.getElementsByTagName('img')[0], extend({
width: width * ratio,
height: height * ratio,
}, getTransforms(extend({
translateX: -left * ratio,
translateY: -top * ratio,
}, imageData))));
});
},
};

View file

@ -0,0 +1,495 @@
import {
ACTION_ALL,
ACTION_MOVE,
CLASS_HIDDEN,
DATA_ACTION,
EVENT_CROP,
} from './constants';
import {
addClass,
dispatchEvent,
extend,
getAdjustedSizes,
getRotatedSizes,
getTransforms,
removeClass,
setData,
setStyle,
} from './utilities';
export default {
render() {
this.initContainer();
this.initCanvas();
this.initCropBox();
this.renderCanvas();
if (this.cropped) {
this.renderCropBox();
}
},
initContainer() {
const {
element,
options,
container,
cropper,
} = this;
addClass(cropper, CLASS_HIDDEN);
removeClass(element, CLASS_HIDDEN);
const containerData = {
width: Math.max(
container.offsetWidth,
Number(options.minContainerWidth) || 200,
),
height: Math.max(
container.offsetHeight,
Number(options.minContainerHeight) || 100,
),
};
this.containerData = containerData;
setStyle(cropper, {
width: containerData.width,
height: containerData.height,
});
addClass(element, CLASS_HIDDEN);
removeClass(cropper, CLASS_HIDDEN);
},
// Canvas (image wrapper)
initCanvas() {
const { containerData, imageData } = this;
const { viewMode } = this.options;
const rotated = Math.abs(imageData.rotate) % 180 === 90;
const naturalWidth = rotated ? imageData.naturalHeight : imageData.naturalWidth;
const naturalHeight = rotated ? imageData.naturalWidth : imageData.naturalHeight;
const aspectRatio = naturalWidth / naturalHeight;
let canvasWidth = containerData.width;
let canvasHeight = containerData.height;
if (containerData.height * aspectRatio > containerData.width) {
if (viewMode === 3) {
canvasWidth = containerData.height * aspectRatio;
} else {
canvasHeight = containerData.width / aspectRatio;
}
} else if (viewMode === 3) {
canvasHeight = containerData.width / aspectRatio;
} else {
canvasWidth = containerData.height * aspectRatio;
}
const canvasData = {
aspectRatio,
naturalWidth,
naturalHeight,
width: canvasWidth,
height: canvasHeight,
};
canvasData.left = (containerData.width - canvasWidth) / 2;
canvasData.top = (containerData.height - canvasHeight) / 2;
canvasData.oldLeft = canvasData.left;
canvasData.oldTop = canvasData.top;
this.canvasData = canvasData;
this.limited = (viewMode === 1 || viewMode === 2);
this.limitCanvas(true, true);
this.initialImageData = extend({}, imageData);
this.initialCanvasData = extend({}, canvasData);
},
limitCanvas(sizeLimited, positionLimited) {
const {
options,
containerData,
canvasData,
cropBoxData,
} = this;
const { viewMode } = options;
const { aspectRatio } = canvasData;
const cropped = this.cropped && cropBoxData;
if (sizeLimited) {
let minCanvasWidth = Number(options.minCanvasWidth) || 0;
let minCanvasHeight = Number(options.minCanvasHeight) || 0;
if (viewMode > 1) {
minCanvasWidth = Math.max(minCanvasWidth, containerData.width);
minCanvasHeight = Math.max(minCanvasHeight, containerData.height);
if (viewMode === 3) {
if (minCanvasHeight * aspectRatio > minCanvasWidth) {
minCanvasWidth = minCanvasHeight * aspectRatio;
} else {
minCanvasHeight = minCanvasWidth / aspectRatio;
}
}
} else if (viewMode > 0) {
if (minCanvasWidth) {
minCanvasWidth = Math.max(
minCanvasWidth,
cropped ? cropBoxData.width : 0,
);
} else if (minCanvasHeight) {
minCanvasHeight = Math.max(
minCanvasHeight,
cropped ? cropBoxData.height : 0,
);
} else if (cropped) {
minCanvasWidth = cropBoxData.width;
minCanvasHeight = cropBoxData.height;
if (minCanvasHeight * aspectRatio > minCanvasWidth) {
minCanvasWidth = minCanvasHeight * aspectRatio;
} else {
minCanvasHeight = minCanvasWidth / aspectRatio;
}
}
}
({ width: minCanvasWidth, height: minCanvasHeight } = getAdjustedSizes({
aspectRatio,
width: minCanvasWidth,
height: minCanvasHeight,
}, 'cover'));
canvasData.minWidth = minCanvasWidth;
canvasData.minHeight = minCanvasHeight;
canvasData.maxWidth = Infinity;
canvasData.maxHeight = Infinity;
}
if (positionLimited) {
if (viewMode) {
const newCanvasLeft = containerData.width - canvasData.width;
const newCanvasTop = containerData.height - canvasData.height;
canvasData.minLeft = Math.min(0, newCanvasLeft);
canvasData.minTop = Math.min(0, newCanvasTop);
canvasData.maxLeft = Math.max(0, newCanvasLeft);
canvasData.maxTop = Math.max(0, newCanvasTop);
if (cropped && this.limited) {
canvasData.minLeft = Math.min(
cropBoxData.left,
cropBoxData.left + (cropBoxData.width - canvasData.width),
);
canvasData.minTop = Math.min(
cropBoxData.top,
cropBoxData.top + (cropBoxData.height - canvasData.height),
);
canvasData.maxLeft = cropBoxData.left;
canvasData.maxTop = cropBoxData.top;
if (viewMode === 2) {
if (canvasData.width >= containerData.width) {
canvasData.minLeft = Math.min(0, newCanvasLeft);
canvasData.maxLeft = Math.max(0, newCanvasLeft);
}
if (canvasData.height >= containerData.height) {
canvasData.minTop = Math.min(0, newCanvasTop);
canvasData.maxTop = Math.max(0, newCanvasTop);
}
}
}
} else {
canvasData.minLeft = -canvasData.width;
canvasData.minTop = -canvasData.height;
canvasData.maxLeft = containerData.width;
canvasData.maxTop = containerData.height;
}
}
},
renderCanvas(changed, transformed) {
const { canvasData, imageData } = this;
if (transformed) {
const { width: naturalWidth, height: naturalHeight } = getRotatedSizes({
width: imageData.naturalWidth * Math.abs(imageData.scaleX || 1),
height: imageData.naturalHeight * Math.abs(imageData.scaleY || 1),
degree: imageData.rotate || 0,
});
const width = canvasData.width * (naturalWidth / canvasData.naturalWidth);
const height = canvasData.height * (naturalHeight / canvasData.naturalHeight);
canvasData.left -= (width - canvasData.width) / 2;
canvasData.top -= (height - canvasData.height) / 2;
canvasData.width = width;
canvasData.height = height;
canvasData.aspectRatio = naturalWidth / naturalHeight;
canvasData.naturalWidth = naturalWidth;
canvasData.naturalHeight = naturalHeight;
this.limitCanvas(true, false);
}
if (canvasData.width > canvasData.maxWidth ||
canvasData.width < canvasData.minWidth) {
canvasData.left = canvasData.oldLeft;
}
if (canvasData.height > canvasData.maxHeight ||
canvasData.height < canvasData.minHeight) {
canvasData.top = canvasData.oldTop;
}
canvasData.width = Math.min(
Math.max(canvasData.width, canvasData.minWidth),
canvasData.maxWidth,
);
canvasData.height = Math.min(
Math.max(canvasData.height, canvasData.minHeight),
canvasData.maxHeight,
);
this.limitCanvas(false, true);
canvasData.left = Math.min(
Math.max(canvasData.left, canvasData.minLeft),
canvasData.maxLeft,
);
canvasData.top = Math.min(
Math.max(canvasData.top, canvasData.minTop),
canvasData.maxTop,
);
canvasData.oldLeft = canvasData.left;
canvasData.oldTop = canvasData.top;
setStyle(this.canvas, extend({
width: canvasData.width,
height: canvasData.height,
}, getTransforms({
translateX: canvasData.left,
translateY: canvasData.top,
})));
this.renderImage(changed);
if (this.cropped && this.limited) {
this.limitCropBox(true, true);
}
},
renderImage(changed) {
const { canvasData, imageData } = this;
const width = imageData.naturalWidth * (canvasData.width / canvasData.naturalWidth);
const height = imageData.naturalHeight * (canvasData.height / canvasData.naturalHeight);
extend(imageData, {
width,
height,
left: (canvasData.width - width) / 2,
top: (canvasData.height - height) / 2,
});
setStyle(this.image, extend({
width: imageData.width,
height: imageData.height,
}, getTransforms(extend({
translateX: imageData.left,
translateY: imageData.top,
}, imageData))));
if (changed) {
this.output();
}
},
initCropBox() {
const { options, canvasData } = this;
const { aspectRatio } = options;
const autoCropArea = Number(options.autoCropArea) || 0.8;
const cropBoxData = {
width: canvasData.width,
height: canvasData.height,
};
if (aspectRatio) {
if (canvasData.height * aspectRatio > canvasData.width) {
cropBoxData.height = cropBoxData.width / aspectRatio;
} else {
cropBoxData.width = cropBoxData.height * aspectRatio;
}
}
this.cropBoxData = cropBoxData;
this.limitCropBox(true, true);
// Initialize auto crop area
cropBoxData.width = Math.min(
Math.max(cropBoxData.width, cropBoxData.minWidth),
cropBoxData.maxWidth,
);
cropBoxData.height = Math.min(
Math.max(cropBoxData.height, cropBoxData.minHeight),
cropBoxData.maxHeight,
);
// The width/height of auto crop area must large than "minWidth/Height"
cropBoxData.width = Math.max(
cropBoxData.minWidth,
cropBoxData.width * autoCropArea,
);
cropBoxData.height = Math.max(
cropBoxData.minHeight,
cropBoxData.height * autoCropArea,
);
cropBoxData.left = (
canvasData.left + ((canvasData.width - cropBoxData.width) / 2)
);
cropBoxData.top = (
canvasData.top + ((canvasData.height - cropBoxData.height) / 2)
);
cropBoxData.oldLeft = cropBoxData.left;
cropBoxData.oldTop = cropBoxData.top;
this.initialCropBoxData = extend({}, cropBoxData);
},
limitCropBox(sizeLimited, positionLimited) {
const {
options,
containerData,
canvasData,
cropBoxData,
limited,
} = this;
const { aspectRatio } = options;
if (sizeLimited) {
let minCropBoxWidth = Number(options.minCropBoxWidth) || 0;
let minCropBoxHeight = Number(options.minCropBoxHeight) || 0;
let maxCropBoxWidth = Math.min(
containerData.width,
limited ? canvasData.width : containerData.width,
);
let maxCropBoxHeight = Math.min(
containerData.height,
limited ? canvasData.height : containerData.height,
);
// The min/maxCropBoxWidth/Height must be less than container's width/height
minCropBoxWidth = Math.min(minCropBoxWidth, containerData.width);
minCropBoxHeight = Math.min(minCropBoxHeight, containerData.height);
if (aspectRatio) {
if (minCropBoxWidth && minCropBoxHeight) {
if (minCropBoxHeight * aspectRatio > minCropBoxWidth) {
minCropBoxHeight = minCropBoxWidth / aspectRatio;
} else {
minCropBoxWidth = minCropBoxHeight * aspectRatio;
}
} else if (minCropBoxWidth) {
minCropBoxHeight = minCropBoxWidth / aspectRatio;
} else if (minCropBoxHeight) {
minCropBoxWidth = minCropBoxHeight * aspectRatio;
}
if (maxCropBoxHeight * aspectRatio > maxCropBoxWidth) {
maxCropBoxHeight = maxCropBoxWidth / aspectRatio;
} else {
maxCropBoxWidth = maxCropBoxHeight * aspectRatio;
}
}
// The minWidth/Height must be less than maxWidth/Height
cropBoxData.minWidth = Math.min(minCropBoxWidth, maxCropBoxWidth);
cropBoxData.minHeight = Math.min(minCropBoxHeight, maxCropBoxHeight);
cropBoxData.maxWidth = maxCropBoxWidth;
cropBoxData.maxHeight = maxCropBoxHeight;
}
if (positionLimited) {
if (limited) {
cropBoxData.minLeft = Math.max(0, canvasData.left);
cropBoxData.minTop = Math.max(0, canvasData.top);
cropBoxData.maxLeft = Math.min(
containerData.width,
canvasData.left + canvasData.width,
) - cropBoxData.width;
cropBoxData.maxTop = Math.min(
containerData.height,
canvasData.top + canvasData.height,
) - cropBoxData.height;
} else {
cropBoxData.minLeft = 0;
cropBoxData.minTop = 0;
cropBoxData.maxLeft = containerData.width - cropBoxData.width;
cropBoxData.maxTop = containerData.height - cropBoxData.height;
}
}
},
renderCropBox() {
const { options, containerData, cropBoxData } = this;
if (cropBoxData.width > cropBoxData.maxWidth ||
cropBoxData.width < cropBoxData.minWidth) {
cropBoxData.left = cropBoxData.oldLeft;
}
if (cropBoxData.height > cropBoxData.maxHeight ||
cropBoxData.height < cropBoxData.minHeight) {
cropBoxData.top = cropBoxData.oldTop;
}
cropBoxData.width = Math.min(
Math.max(cropBoxData.width, cropBoxData.minWidth),
cropBoxData.maxWidth,
);
cropBoxData.height = Math.min(
Math.max(cropBoxData.height, cropBoxData.minHeight),
cropBoxData.maxHeight,
);
this.limitCropBox(false, true);
cropBoxData.left = Math.min(
Math.max(cropBoxData.left, cropBoxData.minLeft),
cropBoxData.maxLeft,
);
cropBoxData.top = Math.min(
Math.max(cropBoxData.top, cropBoxData.minTop),
cropBoxData.maxTop,
);
cropBoxData.oldLeft = cropBoxData.left;
cropBoxData.oldTop = cropBoxData.top;
if (options.movable && options.cropBoxMovable) {
// Turn to move the canvas when the crop box is equal to the container
setData(this.face, DATA_ACTION, cropBoxData.width >= containerData.width &&
cropBoxData.height >= containerData.height ? ACTION_MOVE : ACTION_ALL);
}
setStyle(this.cropBox, extend({
width: cropBoxData.width,
height: cropBoxData.height,
}, getTransforms({
translateX: cropBoxData.left,
translateY: cropBoxData.top,
})));
if (this.cropped && this.limited) {
this.limitCanvas(true, true);
}
if (!this.disabled) {
this.output();
}
},
output() {
this.preview();
if (this.complete) {
dispatchEvent(this.element, EVENT_CROP, this.getData());
}
},
};

View file

@ -0,0 +1,27 @@
export default (
'<div class="cropper-container">' +
'<div class="cropper-wrap-box">' +
'<div class="cropper-canvas"></div>' +
'</div>' +
'<div class="cropper-drag-box"></div>' +
'<div class="cropper-crop-box">' +
'<span class="cropper-view-box"></span>' +
'<span class="cropper-dashed dashed-h"></span>' +
'<span class="cropper-dashed dashed-v"></span>' +
'<span class="cropper-center"></span>' +
'<span class="cropper-face"></span>' +
'<span class="cropper-line line-e" data-action="e"></span>' +
'<span class="cropper-line line-n" data-action="n"></span>' +
'<span class="cropper-line line-w" data-action="w"></span>' +
'<span class="cropper-line line-s" data-action="s"></span>' +
'<span class="cropper-point point-e" data-action="e"></span>' +
'<span class="cropper-point point-n" data-action="n"></span>' +
'<span class="cropper-point point-w" data-action="w"></span>' +
'<span class="cropper-point point-s" data-action="s"></span>' +
'<span class="cropper-point point-ne" data-action="ne"></span>' +
'<span class="cropper-point point-nw" data-action="nw"></span>' +
'<span class="cropper-point point-sw" data-action="sw"></span>' +
'<span class="cropper-point point-se" data-action="se"></span>' +
'</div>' +
'</div>'
);

View file

@ -0,0 +1,985 @@
import {
WINDOW,
} from './constants';
/**
* Check if the given value is not a number.
*/
export const isNaN = Number.isNaN || WINDOW.isNaN;
/**
* Check if the given value is a number.
* @param {*} value - The value to check.
* @returns {boolean} Returns `true` if the given value is a number, else `false`.
*/
export function isNumber(value) {
return typeof value === 'number' && !isNaN(value);
}
/**
* Check if the given value is undefined.
* @param {*} value - The value to check.
* @returns {boolean} Returns `true` if the given value is undefined, else `false`.
*/
export function isUndefined(value) {
return typeof value === 'undefined';
}
/**
* Check if the given value is an object.
* @param {*} value - The value to check.
* @returns {boolean} Returns `true` if the given value is an object, else `false`.
*/
export function isObject(value) {
return typeof value === 'object' && value !== null;
}
const { hasOwnProperty } = Object.prototype;
/**
* Check if the given value is a plain object.
* @param {*} value - The value to check.
* @returns {boolean} Returns `true` if the given value is a plain object, else `false`.
*/
export function isPlainObject(value) {
if (!isObject(value)) {
return false;
}
try {
const { constructor } = value;
const { prototype } = constructor;
return constructor && prototype && hasOwnProperty.call(prototype, 'isPrototypeOf');
} catch (e) {
return false;
}
}
/**
* Check if the given value is a function.
* @param {*} value - The value to check.
* @returns {boolean} Returns `true` if the given value is a function, else `false`.
*/
export function isFunction(value) {
return typeof value === 'function';
}
/**
* Iterate the given data.
* @param {*} data - The data to iterate.
* @param {Function} callback - The process function for each element.
* @returns {*} The original data.
*/
export function each(data, callback) {
if (data && isFunction(callback)) {
if (Array.isArray(data) || isNumber(data.length)/* array-like */) {
const { length } = data;
let i;
for (i = 0; i < length; i += 1) {
if (callback.call(data, data[i], i, data) === false) {
break;
}
}
} else if (isObject(data)) {
Object.keys(data).forEach((key) => {
callback.call(data, data[key], key, data);
});
}
}
return data;
}
/**
* Extend the given object.
* @param {*} obj - The object to be extended.
* @param {*} args - The rest objects which will be merged to the first object.
* @returns {Object} The extended object.
*/
export function extend(obj, ...args) {
if (isObject(obj) && args.length > 0) {
if (Object.assign) {
return Object.assign(obj, ...args);
}
args.forEach((arg) => {
if (isObject(arg)) {
Object.keys(arg).forEach((key) => {
obj[key] = arg[key];
});
}
});
}
return obj;
}
/**
* Takes a function and returns a new one that will always have a particular context.
* @param {Function} fn - The target function.
* @param {Object} context - The new context for the function.
* @returns {boolean} The new function.
*/
export function proxy(fn, context, ...args) {
return (...args2) => fn.apply(context, args.concat(args2));
}
const REGEXP_DECIMALS = /\.\d*(?:0|9){12}\d*$/i;
/**
* Normalize decimal number.
* Check out {@link http://0.30000000000000004.com/ }
* @param {number} value - The value to normalize.
* @param {number} [times=100000000000] - The times for normalizing.
* @returns {number} Returns the normalized number.
*/
export function normalizeDecimalNumber(value, times = 100000000000) {
return REGEXP_DECIMALS.test(value) ? (Math.round(value * times) / times) : value;
}
const REGEXP_SUFFIX = /^(?:width|height|left|top|marginLeft|marginTop)$/;
/**
* Apply styles to the given element.
* @param {Element} element - The target element.
* @param {Object} styles - The styles for applying.
*/
export function setStyle(element, styles) {
const { style } = element;
each(styles, (value, property) => {
if (REGEXP_SUFFIX.test(property) && isNumber(value)) {
value += 'px';
}
style[property] = value;
});
}
/**
* Check if the given element has a special class.
* @param {Element} element - The element to check.
* @param {string} value - The class to search.
* @returns {boolean} Returns `true` if the special class was found.
*/
export function hasClass(element, value) {
return element.classList ?
element.classList.contains(value) :
element.className.indexOf(value) > -1;
}
/**
* Add classes to the given element.
* @param {Element} element - The target element.
* @param {string} value - The classes to be added.
*/
export function addClass(element, value) {
if (!value) {
return;
}
if (isNumber(element.length)) {
each(element, (elem) => {
addClass(elem, value);
});
return;
}
if (element.classList) {
element.classList.add(value);
return;
}
const className = element.className.trim();
if (!className) {
element.className = value;
} else if (className.indexOf(value) < 0) {
element.className = `${className} ${value}`;
}
}
/**
* Remove classes from the given element.
* @param {Element} element - The target element.
* @param {string} value - The classes to be removed.
*/
export function removeClass(element, value) {
if (!value) {
return;
}
if (isNumber(element.length)) {
each(element, (elem) => {
removeClass(elem, value);
});
return;
}
if (element.classList) {
element.classList.remove(value);
return;
}
if (element.className.indexOf(value) >= 0) {
element.className = element.className.replace(value, '');
}
}
/**
* Add or remove classes from the given element.
* @param {Element} element - The target element.
* @param {string} value - The classes to be toggled.
* @param {boolean} added - Add only.
*/
export function toggleClass(element, value, added) {
if (!value) {
return;
}
if (isNumber(element.length)) {
each(element, (elem) => {
toggleClass(elem, value, added);
});
return;
}
// IE10-11 doesn't support the second parameter of `classList.toggle`
if (added) {
addClass(element, value);
} else {
removeClass(element, value);
}
}
const REGEXP_HYPHENATE = /([a-z\d])([A-Z])/g;
/**
* Hyphenate the given value.
* @param {string} value - The value to hyphenate.
* @returns {string} The hyphenated value.
*/
export function hyphenate(value) {
return value.replace(REGEXP_HYPHENATE, '$1-$2').toLowerCase();
}
/**
* Get data from the given element.
* @param {Element} element - The target element.
* @param {string} name - The data key to get.
* @returns {string} The data value.
*/
export function getData(element, name) {
if (isObject(element[name])) {
return element[name];
} else if (element.dataset) {
return element.dataset[name];
}
return element.getAttribute(`data-${hyphenate(name)}`);
}
/**
* Set data to the given element.
* @param {Element} element - The target element.
* @param {string} name - The data key to set.
* @param {string} data - The data value.
*/
export function setData(element, name, data) {
if (isObject(data)) {
element[name] = data;
} else if (element.dataset) {
element.dataset[name] = data;
} else {
element.setAttribute(`data-${hyphenate(name)}`, data);
}
}
/**
* Remove data from the given element.
* @param {Element} element - The target element.
* @param {string} name - The data key to remove.
*/
export function removeData(element, name) {
if (isObject(element[name])) {
try {
delete element[name];
} catch (e) {
element[name] = null;
}
} else if (element.dataset) {
// #128 Safari not allows to delete dataset property
try {
delete element.dataset[name];
} catch (e) {
element.dataset[name] = null;
}
} else {
element.removeAttribute(`data-${hyphenate(name)}`);
}
}
const REGEXP_SPACES = /\s\s*/;
/**
* Remove event listener from the target element.
* @param {Element} element - The event target.
* @param {string} type - The event type(s).
* @param {Function} listener - The event listener.
* @param {Object} options - The event options.
*/
export function removeListener(element, type, listener, options = {}) {
if (!isFunction(listener)) {
return;
}
const types = type.trim().split(REGEXP_SPACES);
if (types.length > 1) {
each(types, (t) => {
removeListener(element, t, listener, options);
});
return;
}
if (element.removeEventListener) {
element.removeEventListener(type, listener, options);
} else if (element.detachEvent) {
element.detachEvent(`on${type}`, listener);
}
}
/**
* Add event listener to the target element.
* @param {Element} element - The event target.
* @param {string} type - The event type(s).
* @param {Function} listener - The event listener.
* @param {Object} options - The event options.
*/
export function addListener(element, type, listener, options = {}) {
if (!isFunction(listener)) {
return;
}
const types = type.trim().split(REGEXP_SPACES);
if (types.length > 1) {
each(types, (t) => {
addListener(element, t, listener, options);
});
return;
}
if (options.once) {
const originalListener = listener;
listener = (...args) => {
removeListener(element, type, listener, options);
return originalListener.apply(element, args);
};
}
if (element.addEventListener) {
element.addEventListener(type, listener, options);
} else if (element.attachEvent) {
element.attachEvent(`on${type}`, listener);
}
}
/**
* Dispatch event on the target element.
* @param {Element} element - The event target.
* @param {string} type - The event type(s).
* @param {Object} data - The additional event data.
* @returns {boolean} Indicate if the event is default prevented or not.
*/
export function dispatchEvent(element, type, data) {
if (element.dispatchEvent) {
let event;
// Event and CustomEvent on IE9-11 are global objects, not constructors
if (isFunction(Event) && isFunction(CustomEvent)) {
if (isUndefined(data)) {
event = new Event(type, {
bubbles: true,
cancelable: true,
});
} else {
event = new CustomEvent(type, {
detail: data,
bubbles: true,
cancelable: true,
});
}
} else if (isUndefined(data)) {
event = document.createEvent('Event');
event.initEvent(type, true, true);
} else {
event = document.createEvent('CustomEvent');
event.initCustomEvent(type, true, true, data);
}
// IE9+
return element.dispatchEvent(event);
} else if (element.fireEvent) {
// IE6-10 (native events only)
return element.fireEvent(`on${type}`);
}
return true;
}
/**
* Get the offset base on the document.
* @param {Element} element - The target element.
* @returns {Object} The offset data.
*/
export function getOffset(element) {
const doc = document.documentElement;
const box = element.getBoundingClientRect();
return {
left: box.left + (
(window.scrollX || (doc && doc.scrollLeft) || 0) - ((doc && doc.clientLeft) || 0)
),
top: box.top + (
(window.scrollY || (doc && doc.scrollTop) || 0) - ((doc && doc.clientTop) || 0)
),
};
}
/**
* Empty an element.
* @param {Element} element - The element to empty.
*/
export function empty(element) {
while (element.firstChild) {
element.removeChild(element.firstChild);
}
}
const { location } = WINDOW;
const REGEXP_ORIGINS = /^(https?:)\/\/([^:/?#]+):?(\d*)/i;
/**
* Check if the given URL is a cross origin URL.
* @param {string} url - The target URL.
* @returns {boolean} Returns `true` if the given URL is a cross origin URL, else `false`.
*/
export function isCrossOriginURL(url) {
const parts = url.match(REGEXP_ORIGINS);
return parts && (
parts[1] !== location.protocol ||
parts[2] !== location.hostname ||
parts[3] !== location.port
);
}
/**
* Add timestamp to the given URL.
* @param {string} url - The target URL.
* @returns {string} The result URL.
*/
export function addTimestamp(url) {
const timestamp = `timestamp=${(new Date()).getTime()}`;
return (url + (url.indexOf('?') === -1 ? '?' : '&') + timestamp);
}
/**
* Get transforms base on the given object.
* @param {Object} obj - The target object.
* @returns {string} A string contains transform values.
*/
export function getTransforms({
rotate,
scaleX,
scaleY,
translateX,
translateY,
}) {
const values = [];
if (isNumber(translateX) && translateX !== 0) {
values.push(`translateX(${translateX}px)`);
}
if (isNumber(translateY) && translateY !== 0) {
values.push(`translateY(${translateY}px)`);
}
// Rotate should come first before scale to match orientation transform
if (isNumber(rotate) && rotate !== 0) {
values.push(`rotate(${rotate}deg)`);
}
if (isNumber(scaleX) && scaleX !== 1) {
values.push(`scaleX(${scaleX})`);
}
if (isNumber(scaleY) && scaleY !== 1) {
values.push(`scaleY(${scaleY})`);
}
const transform = values.length ? values.join(' ') : 'none';
return {
WebkitTransform: transform,
msTransform: transform,
transform,
};
}
const { navigator } = WINDOW;
const IS_SAFARI_OR_UIWEBVIEW = navigator && /(Macintosh|iPhone|iPod|iPad).*AppleWebKit/i.test(navigator.userAgent);
/**
* Get an image's natural sizes.
* @param {string} image - The target image.
* @param {Function} callback - The callback function.
*/
export function getImageNaturalSizes(image, callback) {
// Modern browsers (except Safari)
if (image.naturalWidth && !IS_SAFARI_OR_UIWEBVIEW) {
callback(image.naturalWidth, image.naturalHeight);
return;
}
const newImage = document.createElement('img');
const body = document.body || document.documentElement;
newImage.onload = () => {
callback(newImage.width, newImage.height);
if (!IS_SAFARI_OR_UIWEBVIEW) {
body.removeChild(newImage);
}
};
newImage.src = image.src;
// iOS Safari will convert the image automatically
// with its orientation once append it into DOM (#279)
if (!IS_SAFARI_OR_UIWEBVIEW) {
newImage.style.cssText = (
'left:0;' +
'max-height:none!important;' +
'max-width:none!important;' +
'min-height:0!important;' +
'min-width:0!important;' +
'opacity:0;' +
'position:absolute;' +
'top:0;' +
'z-index:-1;'
);
body.appendChild(newImage);
}
}
/**
* Get the max ratio of a group of pointers.
* @param {string} pointers - The target pointers.
* @returns {number} The result ratio.
*/
export function getMaxZoomRatio(pointers) {
const pointers2 = extend({}, pointers);
const ratios = [];
each(pointers, (pointer, pointerId) => {
delete pointers2[pointerId];
each(pointers2, (pointer2) => {
const x1 = Math.abs(pointer.startX - pointer2.startX);
const y1 = Math.abs(pointer.startY - pointer2.startY);
const x2 = Math.abs(pointer.endX - pointer2.endX);
const y2 = Math.abs(pointer.endY - pointer2.endY);
const z1 = Math.sqrt((x1 * x1) + (y1 * y1));
const z2 = Math.sqrt((x2 * x2) + (y2 * y2));
const ratio = (z2 - z1) / z1;
ratios.push(ratio);
});
});
ratios.sort((a, b) => Math.abs(a) < Math.abs(b));
return ratios[0];
}
/**
* Get a pointer from an event object.
* @param {Object} event - The target event object.
* @param {boolean} endOnly - Indicates if only returns the end point coordinate or not.
* @returns {Object} The result pointer contains start and/or end point coordinates.
*/
export function getPointer({ pageX, pageY }, endOnly) {
const end = {
endX: pageX,
endY: pageY,
};
if (endOnly) {
return end;
}
return extend({
startX: pageX,
startY: pageY,
}, end);
}
/**
* Get the center point coordinate of a group of pointers.
* @param {Object} pointers - The target pointers.
* @returns {Object} The center point coordinate.
*/
export function getPointersCenter(pointers) {
let pageX = 0;
let pageY = 0;
let count = 0;
each(pointers, ({ startX, startY }) => {
pageX += startX;
pageY += startY;
count += 1;
});
pageX /= count;
pageY /= count;
return {
pageX,
pageY,
};
}
/**
* Check if the given value is a finite number.
*/
export const isFinite = Number.isFinite || WINDOW.isFinite;
/**
* Get the max sizes in a rectangle under the given aspect ratio.
* @param {Object} data - The original sizes.
* @param {string} [type='contain'] - The adjust type.
* @returns {Object} The result sizes.
*/
export function getAdjustedSizes(
{
aspectRatio,
height,
width,
},
type = 'contain', // or 'cover'
) {
const isValidNumber = value => isFinite(value) && value > 0;
if (isValidNumber(width) && isValidNumber(height)) {
const adjustedWidth = height * aspectRatio;
if ((type === 'contain' && adjustedWidth > width) || (type === 'cover' && adjustedWidth < width)) {
height = width / aspectRatio;
} else {
width = height * aspectRatio;
}
} else if (isValidNumber(width)) {
height = width / aspectRatio;
} else if (isValidNumber(height)) {
width = height * aspectRatio;
}
return {
width,
height,
};
}
/**
* Get the new sizes of a rectangle after rotated.
* @param {Object} data - The original sizes.
* @returns {Object} The result sizes.
*/
export function getRotatedSizes({ width, height, degree }) {
degree = Math.abs(degree) % 180;
if (degree === 90) {
return {
width: height,
height: width,
};
}
const arc = ((degree % 90) * Math.PI) / 180;
const sinArc = Math.sin(arc);
const cosArc = Math.cos(arc);
const newWidth = (width * cosArc) + (height * sinArc);
const newHeight = (width * sinArc) + (height * cosArc);
return degree > 90 ? {
width: newHeight,
height: newWidth,
} : {
width: newWidth,
height: newHeight,
};
}
/**
* Get a canvas which drew the given image.
* @param {HTMLImageElement} image - The image for drawing.
* @param {Object} imageData - The image data.
* @param {Object} canvasData - The canvas data.
* @param {Object} options - The options.
* @returns {HTMLCanvasElement} The result canvas.
*/
export function getSourceCanvas(
image,
{
rotate = 0,
scaleX = 1,
scaleY = 1,
},
{
aspectRatio,
naturalWidth,
naturalHeight,
},
{
fillColor = 'transparent',
imageSmoothingEnabled = true,
imageSmoothingQuality = 'low',
maxWidth = Infinity,
maxHeight = Infinity,
minWidth = 0,
minHeight = 0,
},
) {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const maxSizes = getAdjustedSizes({
aspectRatio,
width: maxWidth,
height: maxHeight,
});
const minSizes = getAdjustedSizes({
aspectRatio,
width: minWidth,
height: minHeight,
}, 'cover');
const width = Math.min(maxSizes.width, Math.max(minSizes.width, naturalWidth));
const height = Math.min(maxSizes.height, Math.max(minSizes.height, naturalHeight));
const params = [
-width / 2,
-height / 2,
width,
height,
];
canvas.width = normalizeDecimalNumber(width);
canvas.height = normalizeDecimalNumber(height);
context.fillStyle = fillColor;
context.fillRect(0, 0, width, height);
context.save();
context.translate(width / 2, height / 2);
context.rotate((rotate * Math.PI) / 180);
context.scale(scaleX, scaleY);
context.imageSmoothingEnabled = imageSmoothingEnabled;
context.imageSmoothingQuality = imageSmoothingQuality;
context.drawImage(image, ...params.map(param => Math.floor(normalizeDecimalNumber(param))));
context.restore();
return canvas;
}
const { fromCharCode } = String;
/**
* Get string from char code in data view.
* @param {DataView} dataView - The data view for read.
* @param {number} start - The start index.
* @param {number} length - The read length.
* @returns {string} The read result.
*/
export function getStringFromCharCode(dataView, start, length) {
let str = '';
let i;
length += start;
for (i = start; i < length; i += 1) {
str += fromCharCode(dataView.getUint8(i));
}
return str;
}
const REGEXP_DATA_URL_HEAD = /^data:.*,/;
/**
* Transform Data URL to array buffer.
* @param {string} dataURL - The Data URL to transform.
* @returns {ArrayBuffer} The result array buffer.
*/
export function dataURLToArrayBuffer(dataURL) {
const base64 = dataURL.replace(REGEXP_DATA_URL_HEAD, '');
const binary = atob(base64);
const arrayBuffer = new ArrayBuffer(binary.length);
const uint8 = new Uint8Array(arrayBuffer);
each(uint8, (value, i) => {
uint8[i] = binary.charCodeAt(i);
});
return arrayBuffer;
}
/**
* Transform array buffer to Data URL.
* @param {ArrayBuffer} arrayBuffer - The array buffer to transform.
* @param {string} mimeType - The mime type of the Data URL.
* @returns {string} The result Data URL.
*/
export function arrayBufferToDataURL(arrayBuffer, mimeType) {
const uint8 = new Uint8Array(arrayBuffer);
let data = '';
// TypedArray.prototype.forEach is not supported in some browsers.
each(uint8, (value) => {
data += fromCharCode(value);
});
return `data:${mimeType};base64,${btoa(data)}`;
}
/**
* Get orientation value from given array buffer.
* @param {ArrayBuffer} arrayBuffer - The array buffer to read.
* @returns {number} The read orientation value.
*/
export function getOrientation(arrayBuffer) {
const dataView = new DataView(arrayBuffer);
let orientation;
let littleEndian;
let app1Start;
let ifdStart;
// Only handle JPEG image (start by 0xFFD8)
if (dataView.getUint8(0) === 0xFF && dataView.getUint8(1) === 0xD8) {
const length = dataView.byteLength;
let offset = 2;
while (offset < length) {
if (dataView.getUint8(offset) === 0xFF && dataView.getUint8(offset + 1) === 0xE1) {
app1Start = offset;
break;
}
offset += 1;
}
}
if (app1Start) {
const exifIDCode = app1Start + 4;
const tiffOffset = app1Start + 10;
if (getStringFromCharCode(dataView, exifIDCode, 4) === 'Exif') {
const endianness = dataView.getUint16(tiffOffset);
littleEndian = endianness === 0x4949;
if (littleEndian || endianness === 0x4D4D /* bigEndian */) {
if (dataView.getUint16(tiffOffset + 2, littleEndian) === 0x002A) {
const firstIFDOffset = dataView.getUint32(tiffOffset + 4, littleEndian);
if (firstIFDOffset >= 0x00000008) {
ifdStart = tiffOffset + firstIFDOffset;
}
}
}
}
}
if (ifdStart) {
const length = dataView.getUint16(ifdStart, littleEndian);
let offset;
let i;
for (i = 0; i < length; i += 1) {
offset = ifdStart + (i * 12) + 2;
if (dataView.getUint16(offset, littleEndian) === 0x0112 /* Orientation */) {
// 8 is the offset of the current tag's value
offset += 8;
// Get the original orientation value
orientation = dataView.getUint16(offset, littleEndian);
// Override the orientation with its default value
dataView.setUint16(offset, 1, littleEndian);
break;
}
}
}
return orientation;
}
/**
* Parse Exif Orientation value.
* @param {number} orientation - The orientation to parse.
* @returns {Object} The parsed result.
*/
export function parseOrientation(orientation) {
let rotate = 0;
let scaleX = 1;
let scaleY = 1;
switch (orientation) {
// Flip horizontal
case 2:
scaleX = -1;
break;
// Rotate left 180°
case 3:
rotate = -180;
break;
// Flip vertical
case 4:
scaleY = -1;
break;
// Flip vertical and rotate right 90°
case 5:
rotate = 90;
scaleY = -1;
break;
// Rotate right 90°
case 6:
rotate = 90;
break;
// Flip horizontal and rotate right 90°
case 7:
rotate = 90;
scaleX = -1;
break;
// Rotate left 90°
case 8:
rotate = -90;
break;
default:
}
return {
rotate,
scaleX,
scaleY,
};
}

View file

@ -0,0 +1,9 @@
.container {
max-width: 640px;
max-height: 360px;
margin: 20px auto;
}
.container > img {
max-width: 100%;
}

View file

@ -0,0 +1,21 @@
QUnit.test('events#crop', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(7);
image.addEventListener('crop', function (e) {
assert.ok(util.isNumber(e.detail.x));
assert.ok(util.isNumber(e.detail.y));
assert.ok(util.isNumber(e.detail.width));
assert.ok(util.isNumber(e.detail.height));
assert.ok(util.isNumber(e.detail.rotate));
assert.ok(util.isNumber(e.detail.scaleX));
assert.ok(util.isNumber(e.detail.scaleY));
done();
});
return new Cropper(image);
});

View file

@ -0,0 +1,24 @@
QUnit.test('events#cropend', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(1);
image.addEventListener('ready', function () {
var PointerEvent = window.PointerEvent;
var cropper = this.cropper;
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerdown' : 'mousedown');
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointermove' : 'mousemove');
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerup' : 'mouseup');
done();
});
image.addEventListener('cropend', function (e) {
assert.strictEqual(e.detail.action, 'crop');
});
return new Cropper(image);
});

View file

@ -0,0 +1,24 @@
QUnit.test('events#cropmove', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(1);
image.addEventListener('ready', function () {
var PointerEvent = window.PointerEvent;
var cropper = this.cropper;
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerdown' : 'mousedown');
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointermove' : 'mousemove');
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerup' : 'mouseup');
done();
});
image.addEventListener('cropmove', function (e) {
assert.strictEqual(e.detail.action, 'crop');
});
return new Cropper(image);
});

View file

@ -0,0 +1,56 @@
QUnit.test('events#cropstart', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(1);
image.addEventListener('ready', function () {
var PointerEvent = window.PointerEvent;
var cropper = this.cropper;
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerdown' : 'mousedown');
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerup' : 'mouseup');
done();
});
image.addEventListener('cropstart', function (e) {
assert.strictEqual(e.detail.action, 'crop');
});
return new Cropper(image);
});
QUnit.test('events#cropstart: default prevented', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(0);
image.addEventListener('ready', function () {
var PointerEvent = window.PointerEvent;
var cropper = this.cropper;
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerdown' : 'mousedown');
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointermove' : 'mousemove');
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerup' : 'mouseup');
done();
});
image.addEventListener('cropstart', function (e) {
e.preventDefault();
});
image.addEventListener('cropmove', function () {
assert.ok(false);
});
image.addEventListener('cropend', function () {
assert.ok(false);
});
return new Cropper(image);
});

View file

@ -0,0 +1,15 @@
QUnit.test('events#ready', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(1);
image.addEventListener('ready', function () {
assert.ok(true);
done();
});
return new Cropper(image);
});

View file

@ -0,0 +1,46 @@
QUnit.test('events#zoom', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(3);
image.addEventListener('ready', function () {
var cropper = this.cropper;
cropper.zoom(0.1);
done();
});
image.addEventListener('zoom', function (e) {
assert.ok(e.detail.ratio > 0);
assert.ok(e.detail.oldRatio > 0);
assert.ok(e.detail.ratio > e.detail.oldRatio);
});
return new Cropper(image);
});
QUnit.test('events#zoom: default prevented', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(1);
image.addEventListener('ready', function () {
var cropper = this.cropper;
var canvasData = cropper.getCanvasData();
assert.deepEqual(cropper.zoom(0.1).getCanvasData(), canvasData);
done();
});
image.addEventListener('zoom', function (e) {
e.preventDefault();
});
return new Cropper(image);
});

View file

@ -0,0 +1,90 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Cropper.js</title>
<link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.4.0.css">
<link rel="stylesheet" href="../dist/cropper.css">
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script src="https://code.jquery.com/qunit/qunit-2.4.0.js"></script>
<script src="../dist/cropper.js"></script>
<script src="js/main.js"></script>
<!-- Options -->
<script src="options/viewMode.js"></script>
<script src="options/dragMode.js"></script>
<script src="options/aspectRatio.js"></script>
<script src="options/data.js"></script>
<!-- <script src="options/checkCrossOrigin.js"></script> -->
<script src="options/checkOrientation.js"></script>
<script src="options/modal.js"></script>
<script src="options/guides.js"></script>
<script src="options/center.js"></script>
<script src="options/highlight.js"></script>
<script src="options/background.js"></script>
<script src="options/autoCrop.js"></script>
<script src="options/movable.js"></script>
<script src="options/rotatable.js"></script>
<script src="options/scalable.js"></script>
<script src="options/zoomable.js"></script>
<script src="options/zoomOnWheel.js"></script>
<script src="options/cropBoxMovable.js"></script>
<script src="options/cropBoxResizable.js"></script>
<script src="options/toggleDragModeOnDblclick.js"></script>
<script src="options/minCanvasWidth.js"></script>
<script src="options/minCanvasHeight.js"></script>
<script src="options/minCropBoxWidth.js"></script>
<script src="options/minCropBoxHeight.js"></script>
<script src="options/minContainerWidth.js"></script>
<script src="options/minContainerHeight.js"></script>
<script src="options/ready.js"></script>
<script src="options/cropstart.js"></script>
<script src="options/cropmove.js"></script>
<script src="options/cropend.js"></script>
<script src="options/crop.js"></script>
<script src="options/zoom.js"></script>
<!-- Methods -->
<script src="methods/crop.js"></script>
<script src="methods/reset.js"></script>
<script src="methods/clear.js"></script>
<script src="methods/replace.js"></script>
<script src="methods/enable.js"></script>
<script src="methods/disable.js"></script>
<script src="methods/destroy.js"></script>
<script src="methods/move.js"></script>
<script src="methods/moveTo.js"></script>
<script src="methods/zoom.js"></script>
<script src="methods/zoomTo.js"></script>
<script src="methods/rotate.js"></script>
<script src="methods/rotateTo.js"></script>
<script src="methods/scale.js"></script>
<script src="methods/scaleX.js"></script>
<script src="methods/scaleY.js"></script>
<script src="methods/getData.js"></script>
<script src="methods/setData.js"></script>
<script src="methods/getContainerData.js"></script>
<script src="methods/getImageData.js"></script>
<script src="methods/getCanvasData.js"></script>
<script src="methods/setCanvasData.js"></script>
<script src="methods/getCropBoxData.js"></script>
<script src="methods/setCropBoxData.js"></script>
<script src="methods/getCroppedCanvas.js"></script>
<script src="methods/setAspectRatio.js"></script>
<script src="methods/setDragMode.js"></script>
<!-- Events -->
<script src="events/ready.js"></script>
<script src="events/cropstart.js"></script>
<script src="events/cropmove.js"></script>
<script src="events/cropend.js"></script>
<script src="events/crop.js"></script>
<script src="events/zoom.js"></script>
</body>
</html>

View file

@ -0,0 +1,74 @@
window.Util = {
isNumber: function (n) {
return typeof n === 'number' && !isNaN(n);
},
isFunction: function (fn) {
return typeof fn === 'function';
},
hasClass: function (element, className) {
return element.classList.contains(className);
},
getByClass: function (element, className) {
return element.getElementsByClassName ?
element.getElementsByClassName(className) :
element.querySelectorAll('.' + className);
},
createImage: function (attrs) {
var container = document.createElement('div');
var image = new Image();
var attr;
if (typeof attrs !== 'object') {
attrs = {};
}
if (!attrs.src) {
attrs.src = '../docs/images/picture.jpg';
}
for (attr in attrs) {
if (attrs.hasOwnProperty(attr)) {
image[attr] = attrs[attr];
}
}
container.className = 'container';
container.appendChild(image);
document.body.appendChild(container);
return image;
},
dispatchEvent: function (element, type, data) {
var event;
if (element.dispatchEvent) {
// Event and CustomEvent on IE9-11 are global objects, not constructors
if (typeof Event === 'function' && typeof CustomEvent === 'function') {
if (!data) {
event = new Event(type, {
bubbles: true,
cancelable: true
});
} else {
event = new CustomEvent(type, {
detail: data,
bubbles: true,
cancelable: true
});
}
} else if (!data) {
event = document.createEvent('Event');
event.initEvent(type, true, true);
} else {
event = document.createEvent('CustomEvent');
event.initCustomEvent(type, true, true, data);
}
// IE9+
return element.dispatchEvent(event);
} else if (element.fireEvent) {
// IE6-10 (native events only)
return element.fireEvent('on' + type);
}
}
};

View file

@ -0,0 +1,29 @@
QUnit.test('methods#clear', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(4);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
cropper.clear();
assert.notOk(cropper.cropped);
assert.deepEqual(cropper.getData(), {
x: 0,
y: 0,
width: 0,
height: 0,
rotate: 0,
scaleX: 1,
scaleY: 1
});
assert.deepEqual(cropper.getCropBoxData(), {});
assert.ok(util.hasClass(cropper.cropBox, 'cropper-hidden'));
done();
}
});
});

View file

@ -0,0 +1,24 @@
QUnit.test('methods#crop', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(4);
return new Cropper(image, {
autoCrop: false,
ready: function () {
var cropper = this.cropper;
assert.notOk(cropper.cropped);
assert.ok(util.hasClass(cropper.cropBox, 'cropper-hidden'));
cropper.crop();
assert.ok(cropper.cropped);
assert.notOk(util.hasClass(cropper.cropBox, 'cropper-hidden'));
done();
}
});
});

View file

@ -0,0 +1,22 @@
QUnit.test('methods#destroy', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(4);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
assert.ok(typeof cropper === 'object');
assert.ok(util.hasClass(image, 'cropper-hidden'));
cropper.destroy();
assert.ok(typeof this.cropper === 'undefined');
assert.notOk(util.hasClass(image, 'cropper-hidden'));
done();
}
});
});

View file

@ -0,0 +1,60 @@
QUnit.test('methods#disable', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(11);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
var options = cropper.options;
var cropBoxData;
var canvasData;
var imageData;
var action;
cropper.disable();
assert.ok(cropper.disabled);
assert.ok(util.hasClass(cropper.cropper, 'cropper-disabled'));
cropBoxData = cropper.getCropBoxData();
cropper.clear();
assert.deepEqual(cropper.getCropBoxData(), cropBoxData);
imageData = cropper.getImageData();
cropper.move(10, 10);
assert.deepEqual(cropper.getImageData(), imageData);
cropper.zoom(0.5);
assert.strictEqual(cropper.getImageData().ratio, imageData.ratio);
cropper.rotate(15);
assert.strictEqual(cropper.getImageData().rotate, imageData.rotate);
cropper.scale(-1);
assert.strictEqual(cropper.getImageData().scaleX, imageData.scaleX);
canvasData = cropper.getCanvasData();
cropper.setCanvasData({
width: canvasData.width - 160
});
assert.deepEqual(cropper.getCanvasData(), canvasData);
cropBoxData = cropper.getCropBoxData();
cropper.setCropBoxData({
height: cropBoxData.height - 90
});
assert.deepEqual(cropper.getCropBoxData(), cropBoxData);
cropper.setAspectRatio(0.618);
assert.ok(isNaN(options.aspectRatio));
action = cropper.dragBox.dataset.action;
cropper.setDragMode('none');
assert.strictEqual(cropper.dragBox.dataset.action, action);
done();
}
});
});

View file

@ -0,0 +1,23 @@
QUnit.test('methods#enable', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(4);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
cropper.disable();
assert.ok(cropper.disabled);
assert.ok(util.hasClass(cropper.cropper, 'cropper-disabled'));
cropper.enable();
assert.notOk(cropper.disabled);
assert.notOk(util.hasClass(cropper.cropper, 'cropper-disabled'));
done();
}
});
});

View file

@ -0,0 +1,23 @@
QUnit.test('methods#getCanvasData', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(6);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
var canvasData = cropper.getCanvasData();
assert.ok(util.isNumber(canvasData.left));
assert.ok(util.isNumber(canvasData.top));
assert.ok(util.isNumber(canvasData.width));
assert.ok(util.isNumber(canvasData.height));
assert.ok(util.isNumber(canvasData.naturalWidth));
assert.ok(util.isNumber(canvasData.naturalHeight));
done();
}
});
});

View file

@ -0,0 +1,19 @@
QUnit.test('methods#getContainerData', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(2);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
var containerData = cropper.getContainerData();
assert.ok(util.isNumber(containerData.width));
assert.ok(util.isNumber(containerData.height));
done();
}
});
});

View file

@ -0,0 +1,21 @@
QUnit.test('methods#getCropBoxData', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(4);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
var cropBoxData = cropper.getCropBoxData();
assert.ok(util.isNumber(cropBoxData.left));
assert.ok(util.isNumber(cropBoxData.top));
assert.ok(util.isNumber(cropBoxData.width));
assert.ok(util.isNumber(cropBoxData.height));
done();
}
});
});

View file

@ -0,0 +1,33 @@
QUnit.test('methods#getCroppedCanvas', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(7);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
var canvas = cropper.getCroppedCanvas({
width: 160,
height: 90
});
var pixelData;
assert.ok(canvas instanceof HTMLCanvasElement);
assert.strictEqual(canvas.width, 160);
assert.strictEqual(canvas.height, 90);
canvas = cropper.rotate(90).getCroppedCanvas({
fillColor: '#010101'
});
pixelData = canvas.getContext('2d').getImageData(0, 0, 1, 1).data;
assert.strictEqual(pixelData[0], 1, 'red is 1');
assert.strictEqual(pixelData[1], 1, 'green is 1');
assert.strictEqual(pixelData[2], 1, 'blue is 1');
assert.strictEqual(pixelData[3], 255, 'color is opaque');
done();
}
});
});

View file

@ -0,0 +1,46 @@
QUnit.test('methods#getData', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(7);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
var data = cropper.getData();
assert.ok(util.isNumber(data.x));
assert.ok(util.isNumber(data.y));
assert.ok(util.isNumber(data.width));
assert.ok(util.isNumber(data.height));
assert.ok(util.isNumber(data.rotate));
assert.ok(util.isNumber(data.scaleX));
assert.ok(util.isNumber(data.scaleY));
done();
}
});
});
QUnit.test('methods#getData: rounded', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(4);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
var data = cropper.getData(true);
assert.ok(data.x % 1 === 0);
assert.ok(data.y % 1 === 0);
assert.ok(data.width % 1 === 0);
assert.ok(data.height % 1 === 0);
done();
}
});
});

View file

@ -0,0 +1,31 @@
QUnit.test('methods#getImageData', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(10);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
var imageData = cropper.getImageData();
assert.ok(util.isNumber(imageData.naturalWidth));
assert.ok(util.isNumber(imageData.naturalHeight));
assert.ok(util.isNumber(imageData.aspectRatio));
assert.ok(util.isNumber(imageData.left));
assert.ok(util.isNumber(imageData.top));
assert.ok(util.isNumber(imageData.width));
assert.ok(util.isNumber(imageData.height));
imageData = cropper.rotateTo(45).getImageData();
assert.strictEqual(imageData.rotate, 45);
imageData = cropper.scale(-1, -1).getImageData();
assert.strictEqual(imageData.scaleX, -1);
assert.strictEqual(imageData.scaleY, -1);
done();
}
});
});

View file

@ -0,0 +1,20 @@
QUnit.test('methods#move', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(2);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
var canvasData = cropper.getCanvasData();
var changedCanvasData = cropper.move(1, 1).getCanvasData();
assert.strictEqual(changedCanvasData.left, canvasData.left + 1);
assert.strictEqual(changedCanvasData.top, canvasData.top + 1);
done();
}
});
});

View file

@ -0,0 +1,19 @@
QUnit.test('methods#moveTo', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(2);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
var canvasData = cropper.moveTo(0, 0).getCanvasData();
assert.strictEqual(canvasData.left, 0);
assert.strictEqual(canvasData.top, 0);
done();
}
});
});

View file

@ -0,0 +1,23 @@
QUnit.test('methods#replace', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(2);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
cropper.options.ready = function () {
assert.notOk(cropper.cropped);
cropper.crop();
assert.ok(cropper.cropped);
done();
};
cropper.options.autoCrop = false;
cropper.replace('../docs/images/picture-2.jpg');
}
});
});

View file

@ -0,0 +1,35 @@
QUnit.test('methods#reset', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(4);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
var canvasData = cropper.getCanvasData();
var cropBoxData = cropper.getCropBoxData();
cropper.setCanvasData({
top: canvasData.top + 10,
width: canvasData.width - 10
});
assert.notDeepEqual(cropper.getCanvasData(), canvasData);
cropper.setCropBoxData({
left: cropBoxData.left + 10,
height: cropBoxData.height - 10
});
assert.notDeepEqual(cropper.getCropBoxData(), cropBoxData);
cropper.reset();
assert.deepEqual(cropper.getCanvasData(), canvasData);
assert.deepEqual(cropper.getCropBoxData(), cropBoxData);
done();
}
});
});

View file

@ -0,0 +1,19 @@
QUnit.test('methods#rotate', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(3);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
assert.strictEqual(cropper.rotate(360).getImageData().rotate, 0);
assert.strictEqual(cropper.rotate(90).getImageData().rotate, 90);
assert.strictEqual(cropper.rotate(-180).getImageData().rotate, -90);
done();
}
});
});

View file

@ -0,0 +1,20 @@
QUnit.test('methods#rotateTo', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(4);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
assert.strictEqual(cropper.rotateTo(360).getImageData().rotate, 0);
assert.strictEqual(cropper.rotateTo(90).getImageData().rotate, 90);
assert.strictEqual(cropper.rotateTo(0).getImageData().rotate, 0);
assert.strictEqual(cropper.rotateTo(-180).getImageData().rotate, -180);
done();
}
});
});

View file

@ -0,0 +1,19 @@
QUnit.test('methods#scale', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(2);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
var imageData = cropper.scale(-1, -1).getImageData();
assert.strictEqual(imageData.scaleX, -1);
assert.strictEqual(imageData.scaleY, -1);
done();
}
});
});

View file

@ -0,0 +1,18 @@
QUnit.test('methods#scaleX', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(1);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
var imageData = cropper.scaleX(-1).getImageData();
assert.strictEqual(imageData.scaleX, -1);
done();
}
});
});

View file

@ -0,0 +1,18 @@
QUnit.test('methods#scaleY', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(1);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
var imageData = cropper.scaleY(-1).getImageData();
assert.strictEqual(imageData.scaleY, -1);
done();
}
});
});

View file

@ -0,0 +1,20 @@
QUnit.test('methods#setAspectRatio', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(2);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
var options = cropper.options;
assert.ok(isNaN(options.aspectRatio));
cropper.setAspectRatio(1);
assert.strictEqual(options.aspectRatio, 1);
done();
}
});
});

View file

@ -0,0 +1,34 @@
QUnit.test('methods#setCanvasData', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(6);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
var canvasData = cropper.getCanvasData();
var changedCanvasData = cropper.setCanvasData({
left: 16,
top: 9
}).getCanvasData();
assert.notStrictEqual(changedCanvasData.left, canvasData.left);
assert.notStrictEqual(changedCanvasData.top, canvasData.top);
assert.strictEqual(changedCanvasData.width, canvasData.width);
assert.strictEqual(changedCanvasData.height, canvasData.height);
canvasData = cropper.getCanvasData();
changedCanvasData = cropper.setCanvasData({
width: 320,
height: 180
}).getCanvasData();
assert.notStrictEqual(changedCanvasData.width, canvasData.width);
assert.notStrictEqual(changedCanvasData.height, canvasData.height);
done();
}
});
});

View file

@ -0,0 +1,36 @@
QUnit.test('methods#setCropBoxData', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(8);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
var cropBoxData = cropper.getCropBoxData();
var changedCropBoxData = cropper.setCropBoxData({
left: 16,
top: 9
}).getCropBoxData();
assert.notStrictEqual(changedCropBoxData.left, cropBoxData.left);
assert.notStrictEqual(changedCropBoxData.top, cropBoxData.top);
assert.strictEqual(changedCropBoxData.width, cropBoxData.width);
assert.strictEqual(changedCropBoxData.height, cropBoxData.height);
cropBoxData = cropper.getCropBoxData();
changedCropBoxData = cropper.setCropBoxData({
width: 320,
height: 180
}).getCropBoxData();
assert.strictEqual(changedCropBoxData.left, cropBoxData.left);
assert.strictEqual(changedCropBoxData.top, cropBoxData.top);
assert.notStrictEqual(changedCropBoxData.width, cropBoxData.width);
assert.notStrictEqual(changedCropBoxData.height, cropBoxData.height);
done();
}
});
});

View file

@ -0,0 +1,36 @@
QUnit.test('methods#setData', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(8);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
var data = cropper.getData();
var changedData = cropper.setData({
x: 16,
y: 9
}).getData();
assert.notStrictEqual(changedData.x, data.x);
assert.notStrictEqual(changedData.y, data.y);
assert.strictEqual(changedData.width, data.width);
assert.strictEqual(changedData.height, data.height);
data = cropper.getData();
changedData = cropper.setData({
width: 320,
height: 180
}).getData();
assert.strictEqual(changedData.x, data.x);
assert.strictEqual(changedData.y, data.y);
assert.notStrictEqual(changedData.width, data.width);
assert.notStrictEqual(changedData.height, data.height);
done();
}
});
});

View file

@ -0,0 +1,27 @@
QUnit.test('methods#setDragMode', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(4);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
var dragBox = cropper.dragBox;
assert.strictEqual(dragBox.dataset.action, 'crop');
cropper.setDragMode('move');
assert.strictEqual(dragBox.dataset.action, 'move');
cropper.setDragMode('crop');
assert.strictEqual(dragBox.dataset.action, 'crop');
cropper.setDragMode('none');
assert.strictEqual(dragBox.dataset.action, 'none');
done();
}
});
});

View file

@ -0,0 +1,20 @@
QUnit.test('methods#zoom', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(2);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
var canvasData = cropper.getCanvasData();
var changedCanvasData = cropper.zoom(0.1).getCanvasData();
assert.ok(changedCanvasData.width > canvasData.width);
assert.ok(changedCanvasData.height > canvasData.height);
done();
}
});
});

View file

@ -0,0 +1,21 @@
QUnit.test('methods#zoomTo', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(3);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
var imageData = cropper.zoomTo(1).getImageData();
var canvasData = cropper.getCanvasData();
assert.strictEqual(imageData.width, imageData.naturalWidth);
assert.strictEqual(canvasData.width, canvasData.naturalWidth);
assert.strictEqual(canvasData.naturalWidth, imageData.naturalWidth);
done();
}
});
});

View file

@ -0,0 +1,39 @@
QUnit.test('options#aspectRatio: NaN', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(1);
return new Cropper(image, {
// aspectRatio: NaN,
ready: function () {
var cropper = this.cropper;
assert.ok(isNaN(cropper.options.aspectRatio));
done();
}
});
});
QUnit.test('options#aspectRatio: 1', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(1);
return new Cropper(image, {
aspectRatio: 1,
ready: function () {
var cropper = this.cropper;
assert.strictEqual(cropper.options.aspectRatio, 1);
done();
}
});
});

View file

@ -0,0 +1,39 @@
QUnit.test('options#autoCrop: true', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(1);
return new Cropper(image, {
// autoCrop: true,
ready: function () {
var cropper = this.cropper;
assert.ok(cropper.cropped);
done();
}
});
});
QUnit.test('options#autoCrop: false', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(1);
return new Cropper(image, {
autoCrop: false,
ready: function () {
var cropper = this.cropper;
assert.notOk(cropper.cropped);
done();
}
});
});

View file

@ -0,0 +1,40 @@
QUnit.test('options#background: true', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(1);
return new Cropper(image, {
// background: true,
ready: function () {
var cropper = this.cropper;
assert.ok(util.hasClass(cropper.cropper, 'cropper-bg'));
done();
}
});
});
QUnit.test('options#background: false', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(1);
return new Cropper(image, {
background: false,
ready: function () {
var cropper = this.cropper;
assert.notOk(util.hasClass(cropper.cropper, 'cropper-bg'));
done();
}
});
});

View file

@ -0,0 +1,41 @@
QUnit.test('options#center: true', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(1);
return new Cropper(image, {
// center: true,
ready: function () {
var cropper = this.cropper;
var center = util.getByClass(cropper.cropBox, 'cropper-center');
assert.notOk(util.hasClass(center[0], 'cropper-hidden'));
done();
}
});
});
QUnit.test('options#center: false', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(1);
return new Cropper(image, {
center: false,
ready: function () {
var cropper = this.cropper;
var center = util.getByClass(cropper.cropBox, 'cropper-center');
assert.ok(util.hasClass(center[0], 'cropper-hidden'));
done();
}
});
});

View file

@ -0,0 +1,67 @@
QUnit.test('options#checkCrossOrigin: true', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage({
src: 'https://fengyuanchen.github.io/cropperjs/images/picture.jpg'
});
assert.expect(2);
return new Cropper(image, {
// checkCrossOrigin: true,
ready: function () {
var cropper = this.cropper;
assert.strictEqual(cropper.image.crossOrigin, 'anonymous');
assert.ok(cropper.image.src.indexOf('timestamp') >= 0);
done();
}
});
});
QUnit.test('options#checkCrossOrigin: false', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage({
src: 'https://fengyuanchen.github.io/cropperjs/images/picture.jpg'
});
assert.expect(2);
return new Cropper(image, {
checkCrossOrigin: false,
ready: function () {
var cropper = this.cropper;
assert.notStrictEqual(cropper.image.crossOrigin, 'anonymous');
assert.ok(cropper.image.src.indexOf('timestamp') < 0);
done();
}
});
});
QUnit.test('options#checkCrossOrigin: exists crossOrigin attribute', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage({
src: 'https://fengyuanchen.github.io/cropperjs/images/picture.jpg',
crossOrigin: 'anonymous'
});
assert.expect(2);
return new Cropper(image, {
ready: function () {
var cropper = this.cropper;
assert.strictEqual(cropper.image.crossOrigin, 'anonymous');
assert.ok(cropper.image.src.indexOf('timestamp') < 0);
done();
}
});
});

View file

@ -0,0 +1,43 @@
QUnit.test('options#checkOrientation: true', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage({
src: '../docs/images/picture-3.jpg'
});
assert.expect(1);
return new Cropper(image, {
// checkOrientation: true,
ready: function () {
var cropper = this.cropper;
assert.notStrictEqual(cropper.getData().rotate, 0);
done();
}
});
});
QUnit.test('options#checkOrientation: false', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage({
src: '../docs/images/picture-3.jpg'
});
assert.expect(1);
return new Cropper(image, {
checkOrientation: false,
ready: function () {
var cropper = this.cropper;
assert.strictEqual(cropper.getData().rotate, 0);
done();
}
});
});

View file

@ -0,0 +1,21 @@
QUnit.test('options#crop', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(7);
return new Cropper(image, {
crop: function (e) {
assert.ok(util.isNumber(e.detail.x));
assert.ok(util.isNumber(e.detail.y));
assert.ok(util.isNumber(e.detail.width));
assert.ok(util.isNumber(e.detail.height));
assert.ok(util.isNumber(e.detail.rotate));
assert.ok(util.isNumber(e.detail.scaleX));
assert.ok(util.isNumber(e.detail.scaleY));
done();
}
});
});

View file

@ -0,0 +1,41 @@
QUnit.test('options#cropBoxMovable: true', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(1);
return new Cropper(image, {
// cropBoxMovable: true,
ready: function () {
var cropper = this.cropper;
var face = util.getByClass(cropper.cropBox, 'cropper-face');
assert.strictEqual(face[0].dataset.action, 'all');
done();
}
});
});
QUnit.test('options#cropBoxMovable: false', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(1);
return new Cropper(image, {
cropBoxMovable: false,
ready: function () {
var cropper = this.cropper;
var face = util.getByClass(cropper.cropBox, 'cropper-face');
assert.strictEqual(face[0].dataset.action, cropper.options.dragMode);
done();
}
});
});

View file

@ -0,0 +1,65 @@
QUnit.test('options#cropBoxResizable: true', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(12);
return new Cropper(image, {
// cropBoxResizable: true,
ready: function () {
var cropper = this.cropper;
var line = util.getByClass(cropper.cropBox, 'cropper-line');
var point = util.getByClass(cropper.cropBox, 'cropper-point');
assert.notOk(util.hasClass(line[0], 'cropper-hidden'));
assert.notOk(util.hasClass(line[1], 'cropper-hidden'));
assert.notOk(util.hasClass(line[2], 'cropper-hidden'));
assert.notOk(util.hasClass(line[3], 'cropper-hidden'));
assert.notOk(util.hasClass(point[0], 'cropper-hidden'));
assert.notOk(util.hasClass(point[1], 'cropper-hidden'));
assert.notOk(util.hasClass(point[2], 'cropper-hidden'));
assert.notOk(util.hasClass(point[3], 'cropper-hidden'));
assert.notOk(util.hasClass(point[4], 'cropper-hidden'));
assert.notOk(util.hasClass(point[5], 'cropper-hidden'));
assert.notOk(util.hasClass(point[6], 'cropper-hidden'));
assert.notOk(util.hasClass(point[7], 'cropper-hidden'));
done();
}
});
});
QUnit.test('options#cropBoxResizable: false', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(12);
return new Cropper(image, {
cropBoxResizable: false,
ready: function () {
var cropper = this.cropper;
var line = util.getByClass(cropper.cropBox, 'cropper-line');
var point = util.getByClass(cropper.cropBox, 'cropper-point');
assert.ok(util.hasClass(line[0], 'cropper-hidden'));
assert.ok(util.hasClass(line[1], 'cropper-hidden'));
assert.ok(util.hasClass(line[2], 'cropper-hidden'));
assert.ok(util.hasClass(line[3], 'cropper-hidden'));
assert.ok(util.hasClass(point[0], 'cropper-hidden'));
assert.ok(util.hasClass(point[1], 'cropper-hidden'));
assert.ok(util.hasClass(point[2], 'cropper-hidden'));
assert.ok(util.hasClass(point[3], 'cropper-hidden'));
assert.ok(util.hasClass(point[4], 'cropper-hidden'));
assert.ok(util.hasClass(point[5], 'cropper-hidden'));
assert.ok(util.hasClass(point[6], 'cropper-hidden'));
assert.ok(util.hasClass(point[7], 'cropper-hidden'));
done();
}
});
});

View file

@ -0,0 +1,24 @@
QUnit.test('options#cropend', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(1);
return new Cropper(image, {
ready: function () {
var PointerEvent = window.PointerEvent;
var cropper = this.cropper;
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerdown' : 'mousedown');
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointermove' : 'mousemove');
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerup' : 'mouseup');
done();
},
cropend: function (e) {
assert.strictEqual(e.detail.action, 'crop');
}
});
});

View file

@ -0,0 +1,24 @@
QUnit.test('options#cropmove', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(1);
return new Cropper(image, {
ready: function () {
var PointerEvent = window.PointerEvent;
var cropper = this.cropper;
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerdown' : 'mousedown');
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointermove' : 'mousemove');
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerup' : 'mouseup');
done();
},
cropmove: function (e) {
assert.strictEqual(e.detail.action, 'crop');
}
});
});

Some files were not shown because too many files have changed in this diff Show more