Repository: ElemeFE/image-cropper
Branch: master
Commit: 7a2c321893cd
Files: 19
Total size: 65.8 KB
Directory structure:
gitextract_w4s35rzg/
├── .gitignore
├── LICENSE
├── README.MD
├── bower.json
├── demo/
│ ├── angular-demo.html
│ ├── cropper-demo.css
│ ├── standalone-noar.html
│ └── standalone.html
├── dist/
│ ├── cropper.css
│ └── cropper.js
├── doc/
│ └── README.MD
├── package.json
└── src/
├── angular.js
├── build-dom.js
├── cropper.js
├── draggable.js
├── export.js
├── index.js
└── resizer.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# dist/*.js
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
# IDE
.idea
demo/**/*.bundle.js
# Prototype File
prototype
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2015 饿了么前端
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.
================================================
FILE: README.MD
================================================
# Image Cropper
A image cropper for cropping user avatar, no dependencies.
Document & Demo: http://elemefe.github.io/image-cropper/
# Build & Watch
Before Build & Watch, you should install browserify & watchify use:
```Shell
npm install
```
Build your own js file after modify source code (including angular & standalone):
```Shell
npm run build
```
Build standalone dist:
```Shell
npm run build-plain
```
Watch source code then auto build:
```Shell
npm run watch
```
================================================
FILE: bower.json
================================================
{
"name": "image-cropper",
"main": "dist/cropper.js",
"version": "0.3.1",
"authors": [
"long.zhang <long.zhang@ele.me>"
],
"license": "MIT",
"ignore": [
"demo",
"test"
]
}
================================================
FILE: demo/angular-demo.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Angular Demo</title>
<link href="cropper-demo.css" rel="stylesheet" />
<link href="../dist/cropper.css" rel="stylesheet"/>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.28/angular.js"></script>
<script type="text/javascript" src="../dist/cropper.js"></script>
</head>
<body>
<form method="post" action="#" enctype="multipart/form-data" ng-app="cropperDemo" ng-controller="demoController">
<p>
<button class="btn-upload btn-lg">Select</button>
<input type="file" name="avatar" cropper-source="avatar" cropper-file-types="jpg,jpeg,png" />
Support formats: JPG, PNG
</p>
<div class="preview-container">
<div class="image-container target" cropper="avatar" cropper-context="cropContext">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="noavatar" />
</div>
<div class="large-wrapper">
<div class="image-container large" cropper-preview="avatar">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="noavatar" />
</div>
<p>Large</p>
</div>
<div>
<div class="image-container medium" cropper-preview="avatar">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="noavatar" />
</div>
<p>Medium</p>
<div class="image-container small" cropper-preview="avatar">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="noavatar" />
</div>
<p>Small</p>
</div>
</div>
<input type="hidden" name="x" value="{{cropContext.left}}"/>
<input type="hidden" name="y" value="{{cropContext.top}}"/>
<input type="hidden" name="w" value="{{cropContext.width}}"/>
<input type="hidden" name="h" value="{{cropContext.height}}"/>
</form>
<script>
angular.module('cropperDemo', ['cropper'])
.controller('demoController', function ($scope) {
$scope.cropContext = {};
});
</script>
</body>
</html>
================================================
FILE: demo/cropper-demo.css
================================================
form {
margin-left: 18px;
}
form > p {
padding-top: 22px;
line-height: 40px;
font-size: 14px;
position: relative;
color: #666;
margin-bottom: 30px;
overflow: hidden;
}
form > p .btn-upload {
width: 120px;
padding: 12px 20px;
font-size: 14px;
border: 1px solid #1e89e0;
background-color: transparent;
color: #1e89e0;
margin-right: 20px;
}
form > p input {
position: absolute;
font-size: 480px;
top: 22px;
left: -30px;
height: 42px;
width: 150px;
opacity: 0;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
cursor: pointer;
}
form .btn-submit {
font-size: 14px;
padding: 12px 20px;
width: 120px;
margin-right: 20px;
}
.preview-container {
overflow: hidden;
color: #999;
margin-bottom: 20px;
}
.preview-container .noavatar {
background-color: #efeeef;
}
.preview-container > div {
float: left;
height: 320px;
}
.large-wrapper {
border-left: 1px solid #eee;
padding-left: 30px;
margin-right: 30px;
}
.image-container {
position: relative;
overflow: hidden;
border: 1px solid #bbb;
margin-bottom: 5px;
}
.image-container .image-wrapper {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
overflow: hidden;
}
.image-container img {
position: absolute;
}
.image-container.target {
width: 320px;
height: 320px;
margin-right: 30px;
}
.image-container.target img {
width: 320px;
height: 320px;
}
.image-container.large {
width: 210px;
height: 210px;
}
.image-container.large img {
width: 210px;
height: 210px;
}
.image-container.large .noavatar {
background-position: -320px 0;
}
.image-container.medium {
width: 48px;
height: 48px;
}
.image-container.medium img {
width: 48px;
height: 48px;
}
.image-container.medium .noavatar {
background-position: -324px -210px;
}
.image-container.small {
width: 20px;
height: 20px;
margin-top: 30px;
}
.image-container.small img {
width: 20px;
height: 20px;
}
.image-container.small .noavatar {
background-position: -373px -210px;
}
================================================
FILE: demo/standalone-noar.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Standalone demo</title>
<link href="cropper-demo.css" rel="stylesheet" />
<link href="../dist/cropper.css" rel="stylesheet"/>
<script type="text/javascript" src="../dist/cropper.js"></script>
</head>
<body>
<form method="post" action="#" enctype="multipart/form-data">
<p>
<button class="btn-upload btn-lg">Select</button>
<input type="file" name="avatar" id="cropper-input"/>
Support formats: JPG, PNG
</p>
<div class="preview-container">
<div class="image-container target" id="cropper-target">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="noavatar" />
</div>
<div class="large-wrapper">
<div class="image-container large" id="preview-large">
<div class="image-wrapper">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="noavatar" />
</div>
</div>
<p>Large</p>
</div>
<div>
<div class="image-container medium" id="preview-medium">
<div class="image-wrapper">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="noavatar" />
</div>
</div>
<p>Medium</p>
<div class="image-container small" id="preview-small">
<div class="image-wrapper">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="noavatar" />
</div>
</div>
<p>Small</p>
</div>
</div>
<input type="hidden" name="x" value="{{cropContext.left}}"/>
<input type="hidden" name="y" value="{{cropContext.top}}"/>
<input type="hidden" name="w" value="{{cropContext.width}}"/>
<input type="hidden" name="h" value="{{cropContext.height}}"/>
</form>
<script>
var cropper = new Cropper({
aspectRatio: 'auto',
element: document.getElementById('cropper-target'),
previews: [
document.getElementById('preview-large'),
document.getElementById('preview-medium'),
document.getElementById('preview-small')
],
onCroppedRectChange: function(rect) {
console.log(rect);
}
});
var input = document.getElementById('cropper-input');
input.onchange = function() {
if (typeof FileReader !== 'undefined') {
var reader = new FileReader();
reader.onload = function (event) {
cropper.setImage(event.target.result);
};
if (input.files && input.files[0]) {
reader.readAsDataURL(input.files[0]);
}
} else { // IE10-
input.select();
input.blur();
var src = document.selection.createRange().text;
cropper.setImage(src);
}
};
</script>
</body>
</html>
================================================
FILE: demo/standalone.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Standalone demo</title>
<link href="cropper-demo.css" rel="stylesheet" />
<link href="../dist/cropper.css" rel="stylesheet"/>
<script type="text/javascript" src="../dist/cropper.js"></script>
</head>
<body>
<form method="post" action="#" enctype="multipart/form-data">
<p>
<button class="btn-upload btn-lg">Select</button>
<input type="file" name="avatar" id="cropper-input"/>
Support formats: JPG, PNG
</p>
<div class="preview-container">
<div class="image-container target" id="cropper-target">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="noavatar" />
</div>
<div class="large-wrapper">
<div class="image-container large" id="preview-large">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="noavatar" />
</div>
<p>Large</p>
</div>
<div>
<div class="image-container medium" id="preview-medium">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="noavatar" />
</div>
<p>Medium</p>
<div class="image-container small" id="preview-small">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="noavatar" />
</div>
<p>Small</p>
</div>
</div>
<input type="hidden" name="x" value="{{cropContext.left}}"/>
<input type="hidden" name="y" value="{{cropContext.top}}"/>
<input type="hidden" name="w" value="{{cropContext.width}}"/>
<input type="hidden" name="h" value="{{cropContext.height}}"/>
</form>
<script>
var cropper = new Cropper({
element: document.getElementById('cropper-target'),
previews: [
document.getElementById('preview-large'),
document.getElementById('preview-medium'),
document.getElementById('preview-small')
],
onCroppedRectChange: function(rect) {
console.log(rect);
}
});
var input = document.getElementById('cropper-input');
input.onchange = function() {
if (typeof FileReader !== 'undefined') {
var reader = new FileReader();
reader.onload = function (event) {
cropper.setImage(event.target.result);
};
if (input.files && input.files[0]) {
reader.readAsDataURL(input.files[0]);
}
} else { // IE10-
input.select();
input.blur();
var src = document.selection.createRange().text;
cropper.setImage(src);
}
};
</script>
</body>
</html>
================================================
FILE: dist/cropper.css
================================================
.resizer {
position: absolute;
box-sizing: border-box;
border: 1px dashed gray;
background-color: transparent;
cursor: move;
}
.resizer .resize-handle {
position: absolute;
background-color: #333;
opacity: 0.5;
filter: alpha(opacity=50);
font-size: 1px;
height: 7px;
width: 7px;
}
.resizer .resize-handle.ord-n {
cursor: n-resize;
left: 50%;
margin-left: -4px;
margin-top: -1px;
top: 0;
}
.resizer .resize-handle.ord-s {
cursor: s-resize;
bottom: 0;
left: 50%;
margin-bottom: -1px;
margin-left: -4px;
}
.resizer .resize-handle.ord-e {
cursor: e-resize;
margin-right: -1px;
margin-top: -4px;
right: 0;
top: 50%;
}
.resizer .resize-handle.ord-w {
cursor: w-resize;
left: 0;
margin-left: -1px;
margin-top: -4px;
top: 50%;
}
.resizer .resize-handle.ord-nw {
cursor: nw-resize;
left: 0;
margin-left: -1px;
margin-top: -1px;
top: 0;
}
.resizer .resize-handle.ord-ne {
cursor: ne-resize;
margin-right: -1px;
margin-top: -1px;
right: 0;
top: 0;
}
.resizer .resize-handle.ord-se {
cursor: se-resize;
bottom: 0;
margin-bottom: -1px;
margin-right: -1px;
right: 0;
}
.resizer .resize-handle.ord-sw {
cursor: sw-resize;
bottom: 0;
left: 0;
margin-bottom: -1px;
margin-left: -1px;
}
.resizer .resize-bar.ord-n, .resizer .resize-bar.ord-s {
position: absolute;
height: 7px;
width: 100%;
}
.resizer .resize-bar.ord-e, .resizer .resize-bar.ord-w {
position: absolute;
height: 100%;
width: 7px;
}
.resizer .resize-bar.ord-n {
cursor: n-resize;
margin-top: -1px;
}
.resizer .resize-bar.ord-s {
cursor: s-resize;
bottom: 0;
margin-bottom: -1px;
}
.resizer .resize-bar.ord-e {
cursor: e-resize;
margin-right: -1px;
right: 0;
}
.resizer .resize-bar.ord-w {
cursor: w-resize;
margin-left: -1px;
}
.resizer .inner-rect {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: white;
opacity: 0;
filter: alpha(opacity=0);
}
.cropper {
position: absolute;
box-sizing: border-box;
}
.cropper .mask {
width: 100%;
height: 100%;
opacity: 0.4;
filter: alpha(opacity=40);
display: block;
background-color: black;
}
.cropper .resizer .wrapper {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
overflow: hidden;
}
.cropper .resizer .wrapper img {
position: absolute;
-webkit-user-select: none;
-webkit-user-drag: none;
}
================================================
FILE: dist/cropper.js
================================================
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
var buildDom = require('./build-dom');
var draggable = require('./draggable');
var configMap = {
'n': { top: true, height: -1 },
'w': { left: true, width: -1 },
'e': { width: 1 },
's': { height: 1 },
'nw': { left: true, top: true, width: -1, height: -1 },
'ne': { top: true, width: 1, height: -1 },
'sw': { left: true, width: -1, height: 1 },
'se': { width: 1, height: 1 }
};
var getPosition = function (element) {
var selfRect = element.getBoundingClientRect();
var parentRect = element.offsetParent.getBoundingClientRect();
return {
left: selfRect.left - parentRect.left,
top: selfRect.top - parentRect.top
};
};
var Resizer = function(options) {
for (var prop in options) {
if (options.hasOwnProperty(prop)) this[prop] = options[prop];
}
};
Resizer.prototype.doOnStateChange = function(state) {
};
Resizer.prototype.makeDraggable = function(dom) {
var self = this;
var dragState = {};
var containment;
draggable(dom, {
start: function (event) {
var parentNode = dom.parentNode;
containment = {
left: 0,
top: 0,
width: parentNode.clientWidth,
height: parentNode.clientHeight,
right: parentNode.clientWidth,
bottom: parentNode.clientHeight
};
dragState.startLeft = event.clientX;
dragState.startTop = event.clientY;
var position = getPosition(dom);
dragState.resizerStartLeft = position.left;
dragState.resizerStartTop = position.top;
dragState.resizerStartWidth = dom.offsetWidth;
dragState.resizerStartHeight = dom.offsetHeight;
},
drag: function (event) {
var offsetLeft = event.clientX - dragState.startLeft;
var offsetTop = event.clientY - dragState.startTop;
var left = dragState.resizerStartLeft + offsetLeft;
var top = dragState.resizerStartTop + offsetTop;
if (left < containment.left) {
left = containment.left;
}
if (top < containment.top) {
top = containment.top;
}
if (left + dragState.resizerStartWidth > containment.right) {
left = containment.right - dragState.resizerStartWidth;
}
if (top + dragState.resizerStartHeight > containment.bottom) {
top = containment.bottom - dragState.resizerStartHeight;
}
dom.style.left = left + 'px';
dom.style.top = top + 'px';
self.doOnStateChange();
},
end: function () {
dragState = {};
if (self.doOnDragEnd) {
self.doOnDragEnd();
}
}
});
};
Resizer.prototype.bindResizeEvent = function(dom) {
var self = this;
var resizeState = {};
var aspectRatio = self.aspectRatio;
if (typeof aspectRatio !== 'number') {
aspectRatio = undefined;
}
var makeResizable = function (bar) {
var type = bar.className.split(' ')[0];
var transformMap = configMap[type.substr(4)];
var containment;
draggable(bar, {
start: function (event) {
var parentNode = dom.parentNode;
containment = {
left: 0,
top: 0,
width: parentNode.clientWidth,
height: parentNode.clientHeight,
right: parentNode.clientWidth,
bottom: parentNode.clientHeight
};
resizeState.startWidth = dom.clientWidth;
resizeState.startHeight = dom.clientHeight;
resizeState.startLeft = event.clientX;
resizeState.startTop = event.clientY;
var position = getPosition(dom);
resizeState.resizerStartLeft = position.left;
resizeState.resizerStartTop = position.top;
},
drag: function (event) {
var widthRatio = transformMap.width;
var heightRatio = transformMap.height;
var offsetLeft = event.clientX - resizeState.startLeft;
var offsetTop = event.clientY - resizeState.startTop;
var width, height, minWidth = 50, maxWidth = 10000, minHeight = 50, maxHeight = 10000;
if (widthRatio !== undefined) {
width = resizeState.startWidth + widthRatio * offsetLeft;
if (width < minWidth) {
width = minWidth;
}
if (maxWidth && width > maxWidth) {
width = maxWidth;
}
}
if (heightRatio !== undefined) {
height = resizeState.startHeight + heightRatio * offsetTop;
if (height < minHeight) {
height = minHeight;
}
if (maxHeight && height > maxHeight) {
height = maxHeight;
}
}
if (aspectRatio !== undefined) {
if (type === 'ord-n' || type === 'ord-s') {
width = height * aspectRatio;
} else if (type === 'ord-w' || type === 'ord-e') {
height = width / aspectRatio;
} else {
if (width / height < aspectRatio) {
height = width / aspectRatio;
} else {
width = height * aspectRatio;
}
}
}
var position = {
left: resizeState.resizerStartLeft,
top: resizeState.resizerStartTop
};
if (transformMap.left !== undefined) {
position.left = resizeState.resizerStartLeft + (width - resizeState.startWidth) * widthRatio;
}
if (transformMap.top !== undefined) {
position.top = resizeState.resizerStartTop + (height - resizeState.startHeight) * heightRatio;
}
//=== containment start
if (width + position.left > containment.right) {
width = containment.right - position.left;
}
if (position.left < containment.left) {
width -= containment.left - position.left;
position.left = containment.left;
}
if (height + position.top > containment.bottom) {
height = containment.bottom - position.top;
}
if (position.top < containment.top) {
height -= containment.top - position.top;
position.top = containment.top;
}
//=== containment end
if (aspectRatio !== undefined) {
if (width / height < aspectRatio) {
height = width / aspectRatio;
} else {
width = height * aspectRatio;
}
}
if (transformMap.left !== undefined) {
position.left = resizeState.resizerStartLeft + (width - resizeState.startWidth) * widthRatio;
}
if (transformMap.top !== undefined) {
position.top = resizeState.resizerStartTop + (height - resizeState.startHeight) * heightRatio;
}
dom.style.width = width + 'px';
dom.style.height = height + 'px';
if (position.left !== undefined) {
dom.style.left = position.left + 'px';
}
if (position.top !== undefined) {
dom.style.top = position.top + 'px';
}
self.doOnStateChange();
},
end: function () {
if (self.doOnDragEnd) {
self.doOnDragEnd();
}
}
});
};
var bars = dom.querySelectorAll('.resize-bar');
var handles = dom.querySelectorAll('.resize-handle');
var i, j;
for (i = 0, j = bars.length; i < j; i++) {
makeResizable(bars[i]);
}
for (i = 0, j = handles.length; i < j; i++) {
makeResizable(handles[i]);
}
};
Resizer.prototype.render = function(container) {
var self = this;
var dom = buildDom({
tag: 'div',
className: 'resizer',
content: [
{ tag: 'div', className: 'ord-n resize-bar' },
{ tag: 'div', className: 'ord-s resize-bar' },
{ tag: 'div', className: 'ord-w resize-bar' },
{ tag: 'div', className: 'ord-e resize-bar' },
{ tag: 'div', className: 'ord-nw resize-handle' },
{ tag: 'div', className: 'ord-n resize-handle' },
{ tag: 'div', className: 'ord-ne resize-handle' },
{ tag: 'div', className: 'ord-w resize-handle' },
{ tag: 'div', className: 'ord-e resize-handle' },
{ tag: 'div', className: 'ord-sw resize-handle' },
{ tag: 'div', className: 'ord-s resize-handle' },
{ tag: 'div', className: 'ord-se resize-handle' }
]
});
self.dom = dom;
self.bindResizeEvent(dom);
self.makeDraggable(dom);
if (container) {
container.appendChild(dom);
}
return dom;
};
module.exports = Resizer;
},{"./build-dom":3,"./draggable":5}],2:[function(require,module,exports){
var Cropper = require('./cropper');
var cropperInstances = {};
Cropper.getInstance = function(id) {
return cropperInstances[id];
};
angular.module('cropper', [])
.factory('Cropper', function() {
return Cropper;
})
.directive('cropper', function() {
return {
restrict: 'A',
scope: {
cropperContext: '=',
cropperAspectRatio: '@'
},
link: function(scope, element, attrs) {
var id = attrs.cropper;
if (!id) throw new Error('cropper id is required');
var cropperAspectRatio = scope.cropperAspectRatio;
if (cropperAspectRatio) {
if (/^\d*(\.)?\d+$/g.test(cropperAspectRatio)) {
cropperAspectRatio = parseFloat(cropperAspectRatio);
}
} else {
cropperAspectRatio = 1;
}
var cropper = Cropper({ element: element[0], aspectRatio: cropperAspectRatio });
cropperInstances[id] = cropper;
var cropperContext = scope.cropperContext;
cropper.onCroppedRectChange = function(rect) {
if (cropperContext) {
cropperContext.left = rect.left;
cropperContext.top = rect.top;
cropperContext.width = rect.width;
cropperContext.height = rect.height;
}
try { scope.$apply(); } catch(e) {}
};
scope.$on('$destroy', function() {
cropperInstances[id] = null;
delete cropperInstances[id];
});
}
};
}).directive('cropperPreview', function(){
return {
restrict: 'A',
link: function(scope, element, attrs) {
var id = attrs.cropperPreview;
if (!id) throw new Error('cropper id is required');
var cropper = cropperInstances[id];
if (cropper) {
cropper.addPreview(element[0]);
}
}
}
}).directive('cropperSource', function() {
return {
restrict: 'A',
link: function ($scope, $el, attrs) {
var id = attrs.cropperSource;
if (!id) throw new Error('cropper id is required');
var fileValidateRegex = /\.(jpg|png|gif|jpeg)$/i;
var fileTypes = attrs.cropperFileTypes;
if (fileTypes) {
var types = fileTypes.split(',');
if (types.length > 0) {
fileValidateRegex = new RegExp('\.(' + types.join('|') + ')$', 'i');
}
}
$el.on('change', function () {
var input = this;
var cropper = cropperInstances[id];
var fileName = input.value;
if (!fileValidateRegex.test(fileName)) {
cropper.setImage();
return;
}
if (typeof FileReader !== 'undefined') {
var reader = new FileReader();
reader.onload = function (event) {
cropper.setImage(event.target.result);
};
if (input.files && input.files[0]) {
reader.readAsDataURL(input.files[0]);
}
} else {
input.select();
input.blur();
var src = document.selection.createRange().text;
cropper.setImage(src);
}
});
}
};
});
},{"./cropper":4}],3:[function(require,module,exports){
var buildDOM = function(config, refs) {
if (!config) return null;
var dom, childElement;
if (config.tag) {
dom = document.createElement(config.tag);
for (var prop in config) {
if (config.hasOwnProperty(prop)) {
if (prop === 'content' || prop === 'tag') continue;
if (prop === 'key' && refs) {
var key = config[prop];
if (key) {
refs[key] = dom;
}
}
dom[prop] = config[prop];
}
}
var content = config.content;
if (content instanceof Array) {
for (var i = 0, j = content.length; i < j; i++) {
var child = content[i];
childElement = buildDOM(child, refs);
dom.appendChild(childElement);
}
} else if (typeof content === 'string') {
childElement = document.createTextNode(content);
dom.appendChild(childElement);
}
}
return dom;
};
module.exports = buildDOM;
},{}],4:[function(require,module,exports){
var Resizer = require('./Resizer');
var buildDom = require('./build-dom');
var blankImage = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
var preLoadElement;
var ieVersion = Number(document.documentMode);
var getImageSize = function(src, callback) {
if (ieVersion < 10) {
if (!preLoadElement) {
preLoadElement = document.createElement('div');
preLoadElement.style.position = 'absolute';
preLoadElement.style.width = '1px';
preLoadElement.style.height = '1px';
preLoadElement.style.left = '-9999px';
preLoadElement.style.top = '-9999px';
preLoadElement.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=image)';
document.body.insertBefore(preLoadElement, document.body.firstChild);
}
preLoadElement.filters.item('DXImageTransform.Microsoft.AlphaImageLoader').src = src;
var size = {
width: preLoadElement.offsetWidth,
height: preLoadElement.offsetHeight
};
if (typeof callback === 'function') {
callback(size);
}
} else {
var image = new Image();
image.onload = function() {
var size = {
width: image.width,
height: image.height
};
if (typeof callback === 'function') {
callback(size);
}
};
image.src = src;
}
};
var Cropper = function(options) {
var cropper = this;
if (!(this instanceof Cropper)) {
cropper = new Cropper();
}
cropper.aspectRatio = 1;
for (var prop in options) {
if (options.hasOwnProperty(prop)) cropper[prop] = options[prop];
}
if (cropper.element) {
cropper.render(cropper.element);
}
return cropper;
};
Cropper.prototype.resetResizer = function() {
var resizer = this.resizer;
var cropperRect = this.cropperRect;
var aspectRatio = this.aspectRatio;
if (typeof aspectRatio !== 'number') {
aspectRatio = 1;
}
var width = 100;
var height = 100 / aspectRatio;
var resizerDom = resizer.dom;
resizerDom.style.width = width + 'px';
resizerDom.style.height = height + 'px';
if (cropperRect) {
resizerDom.style.left = (cropperRect.width - width) / 2 + 'px';
resizerDom.style.top = (cropperRect.height - height) / 2 + 'px';
} else {
resizerDom.style.left = resizerDom.style.top = '';
}
resizer.doOnStateChange();
resizer.doOnDragEnd();
};
Cropper.prototype.setImage = function(src) {
var element = this.element;
var sourceImage = element.querySelector('img');
var resizeImage = this.refs.image;
var self = this;
if (src === undefined || src === null) {
resizeImage.src = sourceImage.src = blankImage;
resizeImage.style.width = resizeImage.style.height = resizeImage.style.left = resizeImage.style.top = '';
sourceImage.style.width = sourceImage.style.height = sourceImage.style.left = sourceImage.style.top = '';
self.updatePreview(blankImage);
self.dom.style.display = 'none';
self.resetResizer();
self.dom.style.left = self.dom.style.top = '';
self.dom.style.width = element.offsetWidth + 'px';
self.dom.style.height = element.offsetHeight + 'px';
self.croppedRect = {
width: 0,
height: 0,
left: 0,
top: 0
};
self.onCroppedRectChange && self.onCroppedRectChange(self.croppedRect);
return;
}
getImageSize(src, function(size) {
if (ieVersion < 10) {
resizeImage.src = sourceImage.src = blankImage;
resizeImage.style.filter = sourceImage.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale)';
sourceImage.filters.item('DXImageTransform.Microsoft.AlphaImageLoader').src = src;
resizeImage.filters.item('DXImageTransform.Microsoft.AlphaImageLoader').src = src;
}
self.imageSize = size;
var elementWidth = element.offsetWidth;
var elementHeight = element.offsetHeight;
var dom = self.dom;
var cropperRect = {};
if (size.width / size.height > elementWidth / elementHeight) {
cropperRect.width = elementWidth;
cropperRect.height = elementWidth * size.height / size.width;
cropperRect.top = (elementHeight - cropperRect.height) / 2;
cropperRect.left = 0;
} else {
cropperRect.height = elementHeight;
cropperRect.width = elementHeight * size.width / size.height;
cropperRect.top = 0;
cropperRect.left = (elementWidth - cropperRect.width) / 2;
}
self.cropperRect = cropperRect;
for (var style in cropperRect) {
if (cropperRect.hasOwnProperty(style)) {
dom.style[style] = sourceImage.style[style] = resizeImage.style[style] = cropperRect[style] + 'px';
}
}
if (!ieVersion || ieVersion > 9) {
resizeImage.src = sourceImage.src = src;
}
self.dom.style.display = '';
self.resetResizer();
self.updatePreview(src);
});
};
Cropper.prototype.addPreview = function(preview) {
var previews = this.previews;
if (!previews) {
previews = this.previews = [];
}
previews.push(preview);
};
Cropper.prototype.render = function(container) {
var resizer = new Resizer({ aspectRatio: this.aspectRatio });
var refs = {};
var dom = buildDom({
tag: 'div',
className: 'cropper',
content: [{
tag: 'div',
className: 'mask'
}]
}, refs);
var resizerDom = resizer.render(dom);
var img = buildDom({
tag: 'div',
className: 'wrapper',
content: [{
tag: 'img',
key: 'image',
src: blankImage
}]
}, refs);
var self = this;
self.refs = refs;
resizer.doOnStateChange = function() {
var left = parseInt(resizerDom.style.left, 10) || 0;
var top = parseInt(resizerDom.style.top, 10) || 0;
var image = refs.image;
image.style.left = -left + 'px';
image.style.top = -top + 'px';
self.updatePreview();
};
resizer.doOnDragEnd = function() {
var left = parseInt(resizerDom.style.left, 10) || 0;
var top = parseInt(resizerDom.style.top, 10) || 0;
var resizerWidth = resizerDom.offsetWidth;
var resizerHeight = resizerDom.offsetHeight;
var imageSize = self.imageSize;
var cropperRect = self.cropperRect;
if (cropperRect) {
var scale = cropperRect.width / imageSize.width;
self.croppedRect = {
width: Math.floor(resizerWidth / scale),
height: Math.floor(resizerHeight / scale),
left: Math.floor(left / scale),
top: Math.floor(top / scale)
};
self.onCroppedRectChange && self.onCroppedRectChange(self.croppedRect);
}
};
self.resizer = resizer;
self.dom = dom;
resizerDom.insertBefore(img, resizerDom.firstChild);
container.appendChild(dom);
self.dom.style.display = 'none';
};
Cropper.prototype.updatePreview = function(src) {
var imageSize = this.imageSize;
var cropperRect = this.cropperRect;
if (!imageSize || !cropperRect) return;
var previews = this.previews || [];
var resizerDom = this.resizer.dom;
var resizerLeft = parseInt(resizerDom.style.left, 10) || 0;
var resizerTop = parseInt(resizerDom.style.top, 10) || 0;
var resizerWidth = resizerDom.offsetWidth;
var resizerHeight = resizerDom.offsetHeight;
for (var i = 0, j = previews.length; i < j; i++) {
var previewElement = previews[i];
var previewImage = previewElement.querySelector('img');
var previewWrapper = previewElement.querySelector('div');
if (!previewImage) continue;
if (src === blankImage) {
previewImage.style.width = previewImage.style.height = previewImage.style.left = previewImage.style.top = '';
previewImage.src = blankImage;
} else {
if (ieVersion < 10) {
if (src) {
previewImage.src = blankImage;
previewImage.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale)';
previewImage.filters.item('DXImageTransform.Microsoft.AlphaImageLoader').src = src;
previewImage.style.width = cropperRect.width + 'px';
previewImage.style.height = cropperRect.height + 'px';
}
} else if (src) {
previewImage.src = src;
}
var elementWidth = previewElement.offsetWidth;
var elementHeight = previewElement.offsetHeight;
var scale = elementWidth / resizerWidth;
if (previewWrapper) {
var elementRatio = elementWidth / elementHeight;
var resizerRatio = resizerWidth / resizerHeight;
if (elementRatio < resizerRatio) {
previewWrapper.style.width = elementWidth + 'px';
previewWrapper.style.height = resizerHeight * elementWidth / resizerWidth + 'px';
previewWrapper.style.top = (elementHeight - previewWrapper.clientHeight) / 2 + 'px';
previewWrapper.style.left = '';
} else {
var visibleWidth = resizerWidth * elementHeight / resizerHeight;
scale = visibleWidth / resizerWidth;
previewWrapper.style.height = elementHeight + 'px';
previewWrapper.style.width = visibleWidth + 'px';
previewWrapper.style.left = (elementWidth - previewWrapper.clientWidth) / 2 + 'px';
previewWrapper.style.top = '';
}
}
previewImage.style.width = scale * cropperRect.width + 'px';
previewImage.style.height = scale * cropperRect.height + 'px';
previewImage.style.left = -resizerLeft * scale + 'px';
previewImage.style.top = -resizerTop * scale + 'px';
}
}
};
module.exports = Cropper;
},{"./Resizer":1,"./build-dom":3}],5:[function(require,module,exports){
var bind = function(element, event, fn) {
if (element.attachEvent) {
element.attachEvent('on' + event, fn);
} else {
element.addEventListener(event, fn, false);
}
};
var unbind = function(element, event, fn) {
if (element.detachEvent) {
element.detachEvent('on' + event, fn);
} else {
element.removeEventListener(event, fn);
}
};
var isDragging = false;
var isIE8 = Number(document.documentMode) < 9;
var fixEvent = function(event) {
var scrollTop = Math.max(window.scrollY || 0, document.documentElement.scrollTop || 0);
var scrollLeft = Math.max(window.scrollX || 0, document.documentElement.scrollLeft || 0);
event.target = event.srcElement;
event.pageX = scrollLeft + event.clientX;
event.pageY = scrollTop + event.clientY;
};
module.exports = function(element, options) {
var moveFn = function(event) {
if (isIE8) {
fixEvent(event);
}
if (options.drag) {
options.drag(event);
}
};
var upFn = function(event) {
if (isIE8) {
fixEvent(event);
}
unbind(document, 'mousemove', moveFn);
unbind(document, 'mouseup', upFn);
document.onselectstart = null;
document.ondragstart = null;
isDragging = false;
if (options.end) {
options.end(event);
}
};
bind(element, 'mousedown', function(event) {
if (isIE8) {
fixEvent(event);
}
if (isDragging) return;
document.onselectstart = function() { return false; };
document.ondragstart = function() { return false; };
bind(document, 'mousemove', moveFn);
bind(document, 'mouseup', upFn);
isDragging = true;
if (options.start) {
options.start(event);
}
});
};
},{}],6:[function(require,module,exports){
window.Cropper = require('./cropper');
},{"./cropper":4}],7:[function(require,module,exports){
if (typeof angular !== 'undefined') {
require('./angular');
} else {
require('./export');
}
},{"./angular":2,"./export":6}]},{},[7]);
================================================
FILE: doc/README.MD
================================================
## 介绍
Image Cropper可以为图片显示一个裁剪框,裁剪框允许用户调整大小和位置,常用来做用户自定义头像的裁剪功能。
目前Image Cropper的实现是无依赖的,浏览器支持到IE8+。Image Cropper可以和Angular一块使用,也可以独立使用。
## 安装
如果你使用bower,可以在bower.json中添加这么一段配置:
"image-cropper": "git@github.com:ElemeFE/image-cropper.git#~0.2.0"
如果你使用npm,可以直接这么安装:
npm install image-cropper.js --save
## 引入文件
Image Cropper的使用依赖两个文件:
- dist/cropper.css
- dist/cropper.js
最简单的办法是直接在HTML中引用这两个文件,不过每个项目的情况不同,可以根据情况来决定如何引入这两个文件。
## 使用说明
Image Cropper是因为一个裁剪头像并上传的需求诞生的。
一个裁剪头像的流程大概是这样:
- 用户选择一张本地图片。
- 用户使用鼠标拖拽裁剪头像,并实时预览不同大小的裁剪效果。
- 把用户的裁剪范围和图片提交到服务器端。
从提交到服务器端的角度来看,我们关注的点只有两个:
- 用户选择的本地图片
- 用户的裁剪结果(Rect: 相对于图片的left、top、width、height)
### 在Angular中使用
Image Cropper在同一个页面上可以指定多个Instance,该属性定义一个字符串,作为Image Cropper的一个实例的ID。
Image Cropper的ID是必需的,在下面的例子里面,我们使用avatar作为例子中的Image Cropper的实例的ID。
Image Cropper为Angular提供了三个属性Directive,通过这三个Directive的配合,即可实现图像裁剪和预览的效果。
- cropper:添加到要显示裁剪框的元素上,该directive的属性值是cropper的ID。
- cropper-preview:添加到要显示预览的元素上。
- cropper-source:添加到input type=file上。
注意:下面例子中的data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7是一个空的gif文件,用来在用户未选择图片的情况下显示该图片。
#### cropper
cropper需要添加到要显示裁剪框的元素上,directive的属性值为Image Cropper的实例的ID,例如:
<div class="image-container target" cropper="avatar">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="noavatar" />
</div>
#### cropper-aspect-ratio
cropper默认的款高比为1,如果需要使用其他宽高比或者不限制宽高比,则可以设置此属性。比如不限制宽高比,则可以这么设置:
<div class="image-container target" cropper="avatar" cropper-aspect-ratio="auto">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="noavatar" />
</div>
cropper-aspect-ratio如果不是数字,则为不限制宽高比。
##### cropper-context
用户的裁剪结果需要导出到Controller中的一个变量里面,我们可以使用cropper-context来指定这个变量,比如Controller中的变量名是cropContext,则这么定义:
<div class="image-container source" cropper="avatar" cropper-context="cropContext">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="noavatar" />
</div>
#### cropper-source
cropper-source需要添加到一个input[type=file]上,directive的属性值为Image Cropper的实例的ID,例如:
<input type="file" name="avatar" cropper-source="avatar" />
在input[type=file]的值发生变化的时候,cropper和cropper-preview会去使用用户选择的图片。
##### cropper-file-types
Image Cropper默认支持的类型为gif、png、jpg,如果需要定义只支持者某几种格式,比如只支持jpg、png,可以这么定义:
<input type="file" name="avatar" cropper-source="avatar" cropper-file-types="jpg,jpeg,png" />
#### cropper-preview
在用户裁剪图像的过程中,需要动态的预览不同大小的头像的效果,在显示预览图像的元素上添加这个directive。这个directive的属性值为Image Cropper的实例的ID,例如:
<div class="image-container small" cropper-preview="avatar">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="noavatar" />
</div>
注意,如果设置了aspectRatio为非数值,即不限制裁剪图片的宽高比,cropper-preview的HTML应该是这种结构的:
<div class="image-container small" id="preview-small">
<div class="image-wrapper">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="noavatar" />
</div>
</div>
### 独立使用
Image Cropper是可以不和Angular集成,独立使用的。如果你的页面中没有Angular,则在window上可以直接使用Cropper。
#### 创建Cropper实例
独立使用Cropper,需要指定这么几个属性:
- element:显示裁剪框的Element。
- aspectRatio:裁剪框的宽高比,默认值为1。如果设置非数字类型,则为不限制款高比。
- previews:数组类型,可以指定多个显示图片预览的元素。
- onCroppedRectChange:在裁剪结果变更之后会触发这个回调。
一个例子如下:
var cropper = new Cropper({
element: document.getElementById('cropper-target'),
previews: [
document.getElementById('preview-large'),
document.getElementById('preview-medium'),
document.getElementById('preview-small')
],
onCroppedRectChange: function(rect) {
console.log(rect);
}
});
#### 更改Cropper使用的图片
Image Cropper为Angular提供了一个cropper-source来在input[type=file]变更之后更改Cropper的图片,在独立使用的时候,需要手动去做这件事。
变更Cropper的图片使用setImage方法来完成,假设input[type=file]的id为cropper-input,示例代码如下:
var input = document.getElementById('cropper-input');
input.onchange = function() {
if (typeof FileReader !== 'undefined') {
var reader = new FileReader();
reader.onload = function (event) {
cropper.setImage(event.target.result);
};
if (input.files && input.files[0]) {
reader.readAsDataURL(input.files[0]);
}
} else { // IE10-
input.select();
input.blur();
var src = document.selection.createRange().text;
cropper.setImage(src);
}
};
## License
MIT
================================================
FILE: package.json
================================================
{
"name": "image-cropper.js",
"version": "0.3.1",
"description": "A image cropper for cropping user avatar, no dependencies",
"repository": {
"type": "git",
"url": "https://github.com/ElemeFE/image-cropper.git"
},
"keywords": [
"crop",
"cropper",
"image"
],
"main": "src/index.js",
"author": "long.zhang",
"license": "MIT",
"devDependencies": {
"browserify": "^9.0.8",
"watchify": "^3.3.0"
},
"scripts": {
"build": "browserify src/index.js -o dist/cropper.js",
"watch": "watchify src/index.js -o dist/cropper.js -dv",
"build-plain": "browserify src/export.js -o dist/cropper-plain.js"
}
}
================================================
FILE: src/angular.js
================================================
var Cropper = require('./cropper');
var cropperInstances = {};
Cropper.getInstance = function(id) {
return cropperInstances[id];
};
angular.module('cropper', [])
.factory('Cropper', function() {
return Cropper;
})
.directive('cropper', function() {
return {
restrict: 'A',
scope: {
cropperContext: '=',
cropperAspectRatio: '@'
},
link: function(scope, element, attrs) {
var id = attrs.cropper;
if (!id) throw new Error('cropper id is required');
var cropperAspectRatio = scope.cropperAspectRatio;
if (cropperAspectRatio) {
if (/^\d*(\.)?\d+$/g.test(cropperAspectRatio)) {
cropperAspectRatio = parseFloat(cropperAspectRatio);
}
} else {
cropperAspectRatio = 1;
}
var cropper = Cropper({ element: element[0], aspectRatio: cropperAspectRatio });
cropperInstances[id] = cropper;
var cropperContext = scope.cropperContext;
cropper.onCroppedRectChange = function(rect) {
if (cropperContext) {
cropperContext.left = rect.left;
cropperContext.top = rect.top;
cropperContext.width = rect.width;
cropperContext.height = rect.height;
}
try { scope.$apply(); } catch(e) {}
};
scope.$on('$destroy', function() {
cropperInstances[id] = null;
delete cropperInstances[id];
});
}
};
}).directive('cropperPreview', function(){
return {
restrict: 'A',
link: function(scope, element, attrs) {
var id = attrs.cropperPreview;
if (!id) throw new Error('cropper id is required');
var cropper = cropperInstances[id];
if (cropper) {
cropper.addPreview(element[0]);
}
}
}
}).directive('cropperSource', function() {
return {
restrict: 'A',
link: function ($scope, $el, attrs) {
var id = attrs.cropperSource;
if (!id) throw new Error('cropper id is required');
var fileValidateRegex = /\.(jpg|png|gif|jpeg)$/i;
var fileTypes = attrs.cropperFileTypes;
if (fileTypes) {
var types = fileTypes.split(',');
if (types.length > 0) {
fileValidateRegex = new RegExp('\.(' + types.join('|') + ')$', 'i');
}
}
$el.on('change', function () {
var input = this;
var cropper = cropperInstances[id];
var fileName = input.value;
if (!fileValidateRegex.test(fileName)) {
cropper.setImage();
return;
}
if (typeof FileReader !== 'undefined') {
var reader = new FileReader();
reader.onload = function (event) {
cropper.setImage(event.target.result);
};
if (input.files && input.files[0]) {
reader.readAsDataURL(input.files[0]);
}
} else {
input.select();
input.blur();
var src = document.selection.createRange().text;
cropper.setImage(src);
}
});
}
};
});
================================================
FILE: src/build-dom.js
================================================
var buildDOM = function(config, refs) {
if (!config) return null;
var dom, childElement;
if (config.tag) {
dom = document.createElement(config.tag);
for (var prop in config) {
if (config.hasOwnProperty(prop)) {
if (prop === 'content' || prop === 'tag') continue;
if (prop === 'key' && refs) {
var key = config[prop];
if (key) {
refs[key] = dom;
}
}
dom[prop] = config[prop];
}
}
var content = config.content;
if (content instanceof Array) {
for (var i = 0, j = content.length; i < j; i++) {
var child = content[i];
childElement = buildDOM(child, refs);
dom.appendChild(childElement);
}
} else if (typeof content === 'string') {
childElement = document.createTextNode(content);
dom.appendChild(childElement);
}
}
return dom;
};
module.exports = buildDOM;
================================================
FILE: src/cropper.js
================================================
var Resizer = require('./Resizer');
var buildDom = require('./build-dom');
var blankImage = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
var preLoadElement;
var ieVersion = Number(document.documentMode);
var getImageSize = function(src, callback) {
if (ieVersion < 10) {
if (!preLoadElement) {
preLoadElement = document.createElement('div');
preLoadElement.style.position = 'absolute';
preLoadElement.style.width = '1px';
preLoadElement.style.height = '1px';
preLoadElement.style.left = '-9999px';
preLoadElement.style.top = '-9999px';
preLoadElement.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=image)';
document.body.insertBefore(preLoadElement, document.body.firstChild);
}
preLoadElement.filters.item('DXImageTransform.Microsoft.AlphaImageLoader').src = src;
var size = {
width: preLoadElement.offsetWidth,
height: preLoadElement.offsetHeight
};
if (typeof callback === 'function') {
callback(size);
}
} else {
var image = new Image();
image.onload = function() {
var size = {
width: image.width,
height: image.height
};
if (typeof callback === 'function') {
callback(size);
}
};
image.src = src;
}
};
var Cropper = function(options) {
var cropper = this;
if (!(this instanceof Cropper)) {
cropper = new Cropper();
}
cropper.aspectRatio = 1;
for (var prop in options) {
if (options.hasOwnProperty(prop)) cropper[prop] = options[prop];
}
if (cropper.element) {
cropper.render(cropper.element);
}
return cropper;
};
Cropper.prototype.resetResizer = function() {
var resizer = this.resizer;
var cropperRect = this.cropperRect;
var aspectRatio = this.aspectRatio;
if (typeof aspectRatio !== 'number') {
aspectRatio = 1;
}
var width = 100;
var height = 100 / aspectRatio;
var resizerDom = resizer.dom;
resizerDom.style.width = width + 'px';
resizerDom.style.height = height + 'px';
if (cropperRect) {
resizerDom.style.left = (cropperRect.width - width) / 2 + 'px';
resizerDom.style.top = (cropperRect.height - height) / 2 + 'px';
} else {
resizerDom.style.left = resizerDom.style.top = '';
}
resizer.doOnStateChange();
resizer.doOnDragEnd();
};
Cropper.prototype.setImage = function(src) {
var element = this.element;
var sourceImage = element.querySelector('img');
var resizeImage = this.refs.image;
var self = this;
if (src === undefined || src === null) {
resizeImage.src = sourceImage.src = blankImage;
resizeImage.style.width = resizeImage.style.height = resizeImage.style.left = resizeImage.style.top = '';
sourceImage.style.width = sourceImage.style.height = sourceImage.style.left = sourceImage.style.top = '';
self.updatePreview(blankImage);
self.dom.style.display = 'none';
self.resetResizer();
self.dom.style.left = self.dom.style.top = '';
self.dom.style.width = element.offsetWidth + 'px';
self.dom.style.height = element.offsetHeight + 'px';
self.croppedRect = {
width: 0,
height: 0,
left: 0,
top: 0
};
self.onCroppedRectChange && self.onCroppedRectChange(self.croppedRect);
return;
}
getImageSize(src, function(size) {
if (ieVersion < 10) {
resizeImage.src = sourceImage.src = blankImage;
resizeImage.style.filter = sourceImage.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale)';
sourceImage.filters.item('DXImageTransform.Microsoft.AlphaImageLoader').src = src;
resizeImage.filters.item('DXImageTransform.Microsoft.AlphaImageLoader').src = src;
}
self.imageSize = size;
var elementWidth = element.offsetWidth;
var elementHeight = element.offsetHeight;
var dom = self.dom;
var cropperRect = {};
if (size.width / size.height > elementWidth / elementHeight) {
cropperRect.width = elementWidth;
cropperRect.height = elementWidth * size.height / size.width;
cropperRect.top = (elementHeight - cropperRect.height) / 2;
cropperRect.left = 0;
} else {
cropperRect.height = elementHeight;
cropperRect.width = elementHeight * size.width / size.height;
cropperRect.top = 0;
cropperRect.left = (elementWidth - cropperRect.width) / 2;
}
self.cropperRect = cropperRect;
for (var style in cropperRect) {
if (cropperRect.hasOwnProperty(style)) {
dom.style[style] = sourceImage.style[style] = resizeImage.style[style] = cropperRect[style] + 'px';
}
}
if (!ieVersion || ieVersion > 9) {
resizeImage.src = sourceImage.src = src;
}
self.dom.style.display = '';
self.resetResizer();
self.updatePreview(src);
});
};
Cropper.prototype.addPreview = function(preview) {
var previews = this.previews;
if (!previews) {
previews = this.previews = [];
}
previews.push(preview);
};
Cropper.prototype.render = function(container) {
var resizer = new Resizer({ aspectRatio: this.aspectRatio });
var refs = {};
var dom = buildDom({
tag: 'div',
className: 'cropper',
content: [{
tag: 'div',
className: 'mask'
}]
}, refs);
var resizerDom = resizer.render(dom);
var img = buildDom({
tag: 'div',
className: 'wrapper',
content: [{
tag: 'img',
key: 'image',
src: blankImage
}]
}, refs);
var self = this;
self.refs = refs;
resizer.doOnStateChange = function() {
var left = parseInt(resizerDom.style.left, 10) || 0;
var top = parseInt(resizerDom.style.top, 10) || 0;
var image = refs.image;
image.style.left = -left + 'px';
image.style.top = -top + 'px';
self.updatePreview();
};
resizer.doOnDragEnd = function() {
var left = parseInt(resizerDom.style.left, 10) || 0;
var top = parseInt(resizerDom.style.top, 10) || 0;
var resizerWidth = resizerDom.offsetWidth;
var resizerHeight = resizerDom.offsetHeight;
var imageSize = self.imageSize;
var cropperRect = self.cropperRect;
if (cropperRect) {
var scale = cropperRect.width / imageSize.width;
self.croppedRect = {
width: Math.floor(resizerWidth / scale),
height: Math.floor(resizerHeight / scale),
left: Math.floor(left / scale),
top: Math.floor(top / scale)
};
self.onCroppedRectChange && self.onCroppedRectChange(self.croppedRect);
}
};
self.resizer = resizer;
self.dom = dom;
resizerDom.insertBefore(img, resizerDom.firstChild);
container.appendChild(dom);
self.dom.style.display = 'none';
};
Cropper.prototype.updatePreview = function(src) {
var imageSize = this.imageSize;
var cropperRect = this.cropperRect;
if (!imageSize || !cropperRect) return;
var previews = this.previews || [];
var resizerDom = this.resizer.dom;
var resizerLeft = parseInt(resizerDom.style.left, 10) || 0;
var resizerTop = parseInt(resizerDom.style.top, 10) || 0;
var resizerWidth = resizerDom.offsetWidth;
var resizerHeight = resizerDom.offsetHeight;
for (var i = 0, j = previews.length; i < j; i++) {
var previewElement = previews[i];
var previewImage = previewElement.querySelector('img');
var previewWrapper = previewElement.querySelector('div');
if (!previewImage) continue;
if (src === blankImage) {
previewImage.style.width = previewImage.style.height = previewImage.style.left = previewImage.style.top = '';
previewImage.src = blankImage;
} else {
if (ieVersion < 10) {
if (src) {
previewImage.src = blankImage;
previewImage.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale)';
previewImage.filters.item('DXImageTransform.Microsoft.AlphaImageLoader').src = src;
previewImage.style.width = cropperRect.width + 'px';
previewImage.style.height = cropperRect.height + 'px';
}
} else if (src) {
previewImage.src = src;
}
var elementWidth = previewElement.offsetWidth;
var elementHeight = previewElement.offsetHeight;
var scale = elementWidth / resizerWidth;
if (previewWrapper) {
var elementRatio = elementWidth / elementHeight;
var resizerRatio = resizerWidth / resizerHeight;
if (elementRatio < resizerRatio) {
previewWrapper.style.width = elementWidth + 'px';
previewWrapper.style.height = resizerHeight * elementWidth / resizerWidth + 'px';
previewWrapper.style.top = (elementHeight - previewWrapper.clientHeight) / 2 + 'px';
previewWrapper.style.left = '';
} else {
var visibleWidth = resizerWidth * elementHeight / resizerHeight;
scale = visibleWidth / resizerWidth;
previewWrapper.style.height = elementHeight + 'px';
previewWrapper.style.width = visibleWidth + 'px';
previewWrapper.style.left = (elementWidth - previewWrapper.clientWidth) / 2 + 'px';
previewWrapper.style.top = '';
}
}
previewImage.style.width = scale * cropperRect.width + 'px';
previewImage.style.height = scale * cropperRect.height + 'px';
previewImage.style.left = -resizerLeft * scale + 'px';
previewImage.style.top = -resizerTop * scale + 'px';
}
}
};
module.exports = Cropper;
================================================
FILE: src/draggable.js
================================================
var bind = function(element, event, fn) {
if (element.attachEvent) {
element.attachEvent('on' + event, fn);
} else {
element.addEventListener(event, fn, false);
}
};
var unbind = function(element, event, fn) {
if (element.detachEvent) {
element.detachEvent('on' + event, fn);
} else {
element.removeEventListener(event, fn);
}
};
var isDragging = false;
var isIE8 = Number(document.documentMode) < 9;
var fixEvent = function(event) {
var scrollTop = Math.max(window.scrollY || 0, document.documentElement.scrollTop || 0);
var scrollLeft = Math.max(window.scrollX || 0, document.documentElement.scrollLeft || 0);
event.target = event.srcElement;
event.pageX = scrollLeft + event.clientX;
event.pageY = scrollTop + event.clientY;
};
module.exports = function(element, options) {
var moveFn = function(event) {
if (isIE8) {
fixEvent(event);
}
if (options.drag) {
options.drag(event);
}
};
var upFn = function(event) {
if (isIE8) {
fixEvent(event);
}
unbind(document, 'mousemove', moveFn);
unbind(document, 'mouseup', upFn);
document.onselectstart = null;
document.ondragstart = null;
isDragging = false;
if (options.end) {
options.end(event);
}
};
bind(element, 'mousedown', function(event) {
if (isIE8) {
fixEvent(event);
}
if (isDragging) return;
document.onselectstart = function() { return false; };
document.ondragstart = function() { return false; };
bind(document, 'mousemove', moveFn);
bind(document, 'mouseup', upFn);
isDragging = true;
if (options.start) {
options.start(event);
}
});
};
================================================
FILE: src/export.js
================================================
window.Cropper = require('./cropper');
================================================
FILE: src/index.js
================================================
if (typeof angular !== 'undefined') {
require('./angular');
} else {
require('./export');
}
================================================
FILE: src/resizer.js
================================================
var buildDom = require('./build-dom');
var draggable = require('./draggable');
var configMap = {
'n': { top: true, height: -1 },
'w': { left: true, width: -1 },
'e': { width: 1 },
's': { height: 1 },
'nw': { left: true, top: true, width: -1, height: -1 },
'ne': { top: true, width: 1, height: -1 },
'sw': { left: true, width: -1, height: 1 },
'se': { width: 1, height: 1 }
};
var getPosition = function (element) {
var selfRect = element.getBoundingClientRect();
var parentRect = element.offsetParent.getBoundingClientRect();
return {
left: selfRect.left - parentRect.left,
top: selfRect.top - parentRect.top
};
};
var Resizer = function(options) {
for (var prop in options) {
if (options.hasOwnProperty(prop)) this[prop] = options[prop];
}
};
Resizer.prototype.doOnStateChange = function(state) {
};
Resizer.prototype.makeDraggable = function(dom) {
var self = this;
var dragState = {};
var containment;
draggable(dom, {
start: function (event) {
var parentNode = dom.parentNode;
containment = {
left: 0,
top: 0,
width: parentNode.clientWidth,
height: parentNode.clientHeight,
right: parentNode.clientWidth,
bottom: parentNode.clientHeight
};
dragState.startLeft = event.clientX;
dragState.startTop = event.clientY;
var position = getPosition(dom);
dragState.resizerStartLeft = position.left;
dragState.resizerStartTop = position.top;
dragState.resizerStartWidth = dom.offsetWidth;
dragState.resizerStartHeight = dom.offsetHeight;
},
drag: function (event) {
var offsetLeft = event.clientX - dragState.startLeft;
var offsetTop = event.clientY - dragState.startTop;
var left = dragState.resizerStartLeft + offsetLeft;
var top = dragState.resizerStartTop + offsetTop;
if (left < containment.left) {
left = containment.left;
}
if (top < containment.top) {
top = containment.top;
}
if (left + dragState.resizerStartWidth > containment.right) {
left = containment.right - dragState.resizerStartWidth;
}
if (top + dragState.resizerStartHeight > containment.bottom) {
top = containment.bottom - dragState.resizerStartHeight;
}
dom.style.left = left + 'px';
dom.style.top = top + 'px';
self.doOnStateChange();
},
end: function () {
dragState = {};
if (self.doOnDragEnd) {
self.doOnDragEnd();
}
}
});
};
Resizer.prototype.bindResizeEvent = function(dom) {
var self = this;
var resizeState = {};
var aspectRatio = self.aspectRatio;
if (typeof aspectRatio !== 'number') {
aspectRatio = undefined;
}
var makeResizable = function (bar) {
var type = bar.className.split(' ')[0];
var transformMap = configMap[type.substr(4)];
var containment;
draggable(bar, {
start: function (event) {
var parentNode = dom.parentNode;
containment = {
left: 0,
top: 0,
width: parentNode.clientWidth,
height: parentNode.clientHeight,
right: parentNode.clientWidth,
bottom: parentNode.clientHeight
};
resizeState.startWidth = dom.clientWidth;
resizeState.startHeight = dom.clientHeight;
resizeState.startLeft = event.clientX;
resizeState.startTop = event.clientY;
var position = getPosition(dom);
resizeState.resizerStartLeft = position.left;
resizeState.resizerStartTop = position.top;
},
drag: function (event) {
var widthRatio = transformMap.width;
var heightRatio = transformMap.height;
var offsetLeft = event.clientX - resizeState.startLeft;
var offsetTop = event.clientY - resizeState.startTop;
var width, height, minWidth = 50, maxWidth = 10000, minHeight = 50, maxHeight = 10000;
if (widthRatio !== undefined) {
width = resizeState.startWidth + widthRatio * offsetLeft;
if (width < minWidth) {
width = minWidth;
}
if (maxWidth && width > maxWidth) {
width = maxWidth;
}
}
if (heightRatio !== undefined) {
height = resizeState.startHeight + heightRatio * offsetTop;
if (height < minHeight) {
height = minHeight;
}
if (maxHeight && height > maxHeight) {
height = maxHeight;
}
}
if (aspectRatio !== undefined) {
if (type === 'ord-n' || type === 'ord-s') {
width = height * aspectRatio;
} else if (type === 'ord-w' || type === 'ord-e') {
height = width / aspectRatio;
} else {
if (width / height < aspectRatio) {
height = width / aspectRatio;
} else {
width = height * aspectRatio;
}
}
}
var position = {
left: resizeState.resizerStartLeft,
top: resizeState.resizerStartTop
};
if (transformMap.left !== undefined) {
position.left = resizeState.resizerStartLeft + (width - resizeState.startWidth) * widthRatio;
}
if (transformMap.top !== undefined) {
position.top = resizeState.resizerStartTop + (height - resizeState.startHeight) * heightRatio;
}
//=== containment start
if (width + position.left > containment.right) {
width = containment.right - position.left;
}
if (position.left < containment.left) {
width -= containment.left - position.left;
position.left = containment.left;
}
if (height + position.top > containment.bottom) {
height = containment.bottom - position.top;
}
if (position.top < containment.top) {
height -= containment.top - position.top;
position.top = containment.top;
}
//=== containment end
if (aspectRatio !== undefined) {
if (width / height < aspectRatio) {
height = width / aspectRatio;
} else {
width = height * aspectRatio;
}
}
if (transformMap.left !== undefined) {
position.left = resizeState.resizerStartLeft + (width - resizeState.startWidth) * widthRatio;
}
if (transformMap.top !== undefined) {
position.top = resizeState.resizerStartTop + (height - resizeState.startHeight) * heightRatio;
}
dom.style.width = width + 'px';
dom.style.height = height + 'px';
if (position.left !== undefined) {
dom.style.left = position.left + 'px';
}
if (position.top !== undefined) {
dom.style.top = position.top + 'px';
}
self.doOnStateChange();
},
end: function () {
if (self.doOnDragEnd) {
self.doOnDragEnd();
}
}
});
};
var bars = dom.querySelectorAll('.resize-bar');
var handles = dom.querySelectorAll('.resize-handle');
var i, j;
for (i = 0, j = bars.length; i < j; i++) {
makeResizable(bars[i]);
}
for (i = 0, j = handles.length; i < j; i++) {
makeResizable(handles[i]);
}
};
Resizer.prototype.render = function(container) {
var self = this;
var dom = buildDom({
tag: 'div',
className: 'resizer',
content: [
{ tag: 'div', className: 'ord-n resize-bar' },
{ tag: 'div', className: 'ord-s resize-bar' },
{ tag: 'div', className: 'ord-w resize-bar' },
{ tag: 'div', className: 'ord-e resize-bar' },
{ tag: 'div', className: 'ord-nw resize-handle' },
{ tag: 'div', className: 'ord-n resize-handle' },
{ tag: 'div', className: 'ord-ne resize-handle' },
{ tag: 'div', className: 'ord-w resize-handle' },
{ tag: 'div', className: 'ord-e resize-handle' },
{ tag: 'div', className: 'ord-sw resize-handle' },
{ tag: 'div', className: 'ord-s resize-handle' },
{ tag: 'div', className: 'ord-se resize-handle' }
]
});
self.dom = dom;
self.bindResizeEvent(dom);
self.makeDraggable(dom);
if (container) {
container.appendChild(dom);
}
return dom;
};
module.exports = Resizer;
gitextract_w4s35rzg/
├── .gitignore
├── LICENSE
├── README.MD
├── bower.json
├── demo/
│ ├── angular-demo.html
│ ├── cropper-demo.css
│ ├── standalone-noar.html
│ └── standalone.html
├── dist/
│ ├── cropper.css
│ └── cropper.js
├── doc/
│ └── README.MD
├── package.json
└── src/
├── angular.js
├── build-dom.js
├── cropper.js
├── draggable.js
├── export.js
├── index.js
└── resizer.js
SYMBOL INDEX (1 symbols across 1 files)
FILE: dist/cropper.js
function s (line 1) | function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&re...
Condensed preview — 19 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (73K chars).
[
{
"path": ".gitignore",
"chars": 598,
"preview": "# Logs\nlogs\n*.log\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nl"
},
{
"path": "LICENSE",
"chars": 1073,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2015 饿了么前端\n\nPermission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "README.MD",
"chars": 478,
"preview": "# Image Cropper\nA image cropper for cropping user avatar, no dependencies. \n\nDocument & Demo: http://elemefe.github.io/i"
},
{
"path": "bower.json",
"chars": 199,
"preview": "{\n \"name\": \"image-cropper\",\n \"main\": \"dist/cropper.js\",\n \"version\": \"0.3.1\",\n \"authors\": [\n \"long.zhang <long.zha"
},
{
"path": "demo/angular-demo.html",
"chars": 2120,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Angular Demo</title>\n <link href=\"cropper-dem"
},
{
"path": "demo/cropper-demo.css",
"chars": 2052,
"preview": "form {\n margin-left: 18px;\n}\n\nform > p {\n padding-top: 22px;\n line-height: 40px;\n font-size: 14px;\n position: relat"
},
{
"path": "demo/standalone-noar.html",
"chars": 2772,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Standalone demo</title>\n <link href=\"cropper-"
},
{
"path": "demo/standalone.html",
"chars": 2588,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Standalone demo</title>\n <link href=\"cropper-"
},
{
"path": "dist/cropper.css",
"chars": 2425,
"preview": ".resizer {\n position: absolute;\n box-sizing: border-box;\n border: 1px dashed gray;\n background-color: transparent;\n "
},
{
"path": "dist/cropper.js",
"chars": 24382,
"preview": "(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==\"function\"&&require;if(!u&&a)return a(o,!0)"
},
{
"path": "doc/README.MD",
"chars": 4534,
"preview": "## 介绍\nImage Cropper可以为图片显示一个裁剪框,裁剪框允许用户调整大小和位置,常用来做用户自定义头像的裁剪功能。\n\n目前Image Cropper的实现是无依赖的,浏览器支持到IE8+。Image Cropper可以和Ang"
},
{
"path": "package.json",
"chars": 657,
"preview": "{\n \"name\": \"image-cropper.js\",\n \"version\": \"0.3.1\",\n \"description\": \"A image cropper for cropping user avatar, no dep"
},
{
"path": "src/angular.js",
"chars": 3004,
"preview": "var Cropper = require('./cropper');\n\nvar cropperInstances = {};\n\nCropper.getInstance = function(id) {\n return cropperIn"
},
{
"path": "src/build-dom.js",
"chars": 926,
"preview": "var buildDOM = function(config, refs) {\n if (!config) return null;\n var dom, childElement;\n if (config.tag) {\n dom"
},
{
"path": "src/cropper.js",
"chars": 9449,
"preview": "var Resizer = require('./Resizer');\nvar buildDom = require('./build-dom');\n\nvar blankImage = 'data:image/gif;base64,R0lG"
},
{
"path": "src/draggable.js",
"chars": 1682,
"preview": "var bind = function(element, event, fn) {\n if (element.attachEvent) {\n element.attachEvent('on' + event, fn);\n } el"
},
{
"path": "src/export.js",
"chars": 38,
"preview": "window.Cropper = require('./cropper');"
},
{
"path": "src/index.js",
"chars": 97,
"preview": "if (typeof angular !== 'undefined') {\n require('./angular');\n} else {\n require('./export');\n}\n\n"
},
{
"path": "src/resizer.js",
"chars": 8312,
"preview": "var buildDom = require('./build-dom');\nvar draggable = require('./draggable');\n\nvar configMap = {\n 'n': { top: true, h"
}
]
About this extraction
This page contains the full source code of the ElemeFE/image-cropper GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 19 files (65.8 KB), approximately 18.5k tokens, and a symbol index with 1 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.