UNPKG

react-croppy

Version:

A pure image cropper for React

419 lines (360 loc) 13.9 kB
'use strict'; exports.__esModule = true; var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _Rect = require('./Rect'); var _Rect2 = _interopRequireDefault(_Rect); var _utils = require('../utils'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var Cropper = function (_Component) { _inherits(Cropper, _Component); function Cropper(props) { _classCallCheck(this, Cropper); var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(Cropper).call(this, props)); _this.getPosition = function (e) { var offset = (0, _utils.getOffset)(_this.node); var x = e.pageX - offset.left; var y = e.pageY - offset.top; return { x: x, y: y }; }; _this.posCollidesCrop = function (pos) { // does the provided mouse position collide with the crop box var x = pos.x; var y = pos.y; return x >= _this.state.x && x <= _this.state.x + _this.state.width && y >= _this.state.y && y <= _this.state.y + _this.state.height; }; _this.posCollidesResizeHandler = function (pos) { // does the provided mouse position collide with the resize handler var handlerSize = _this.props.handlerSize; var x = pos.x; var y = pos.y; var handlerX = _this.state.x + _this.state.width - handlerSize; var handlerY = _this.state.y + _this.state.height - handlerSize; return x >= handlerX && x <= handlerX + handlerSize && y >= handlerY && y <= handlerY + handlerSize; }; _this.cropIsActive = function () { // is there currently an active cropbox? return _this.state.width && _this.state.height; }; _this.getDelta = function (pos) { // diff between mouse position and left/top position of crop box return { x: pos.x - _this.state.x, y: pos.y - _this.state.y }; }; _this.onMouseDown = function (e) { e.preventDefault(); var pos = _this.getPosition(e); var isActive = _this.cropIsActive(); var collides = _this.posCollidesCrop(pos); if (!isActive || !collides) { // reset starting position _this.setState(_extends({}, pos, { width: 0, height: 0, resizing: true, startX: pos.x, startY: pos.y })); } else { var delta = _this.getDelta(pos); var _this$state = _this.state; var width = _this$state.width; var height = _this$state.height; if (_this.posCollidesResizeHandler(pos)) { _this.setState({ resizing: true, // calc distance between left bottom corner and mouse pos deltaHandler: { x: width - delta.x, y: height - delta.y } }); } else { _this.setState({ dragging: true, delta: delta }); } } }; _this.onResize = function (_ref) { var width = _ref.width; var height = _ref.height; var _this$props = _this.props; var aspectRatio = _this$props.aspectRatio; var minCropWidth = _this$props.minCropWidth; var minCropHeight = _this$props.minCropHeight; if (minCropWidth) { width = Math.max(minCropWidth, _this.getRatio() * width); } if (minCropHeight) { height = Math.max(minCropHeight, _this.getRatio() * height); } if (aspectRatio) { height = width / aspectRatio; } return { width: width, height: height }; }; _this.onMouseMove = function (e) { if (!_this.state.dragging && !_this.state.resizing) { return; } e.preventDefault(); var _this$getPosition = _this.getPosition(e); var x = _this$getPosition.x; var y = _this$getPosition.y; var ratio = _this.getRatio(); var _this$state2 = _this.state; var width = _this$state2.width; var height = _this$state2.height; var delta = _this$state2.delta; var domWidth = _this$state2.domWidth; var domHeight = _this$state2.domHeight; var newState = {}; if (_this.state.dragging) { newState = { x: (0, _utils.clip)(x - delta.x, 0, domWidth - width), y: (0, _utils.clip)(y - delta.y, 0, domHeight - height), width: width, height: height }; } else if (_this.state.resizing) { width = x - _this.state.x; height = y - _this.state.y; newState = _extends({ x: _this.state.x, y: _this.state.y }, _this.onResize({ width: (0, _utils.clip)(width + _this.state.deltaHandler.x, 1, domWidth - _this.state.x), height: (0, _utils.clip)(height + _this.state.deltaHandler.y, 1, domHeight - _this.state.y) })); } if (_this.props.onCrop) { _this.props.onCrop(_this.toNativeMetrics(newState)); } _this.setState(newState); }; _this.onMouseUp = function () { if (!_this.state.dragging && !_this.state.resizing) { return; } var _this$state3 = _this.state; var x = _this$state3.x; var y = _this$state3.y; var width = _this$state3.width; var height = _this$state3.height; var data = _extends({ nativeSize: _this.image.nativeSize }, _this.toNativeMetrics({ x: x, y: y, width: width, height: height })); _this.setState({ resizing: false, dragging: false }); if (width && height && _this.props.onCropEnd) { _this.props.onCropEnd(data); } }; _this.onWindowResize = function () { _this.computeDOMSizes(); }; _this.onImageLoad = function () { var imgNode = _this.getImageNode(); _this.node = imgNode; var domSize = _this.imageDomSize(); _this.image = _this.image || {}; _this.image.nativeSize = { width: imgNode.naturalWidth, height: imgNode.naturalHeight }; var update = { domWidth: domSize.width, domHeight: domSize.height }; if (_this.props.start) { var aspectRatio = _this.props.aspectRatio; var _this$props$start = _slicedToArray(_this.props.start, 4); var x = _this$props$start[0]; var y = _this$props$start[1]; var width = _this$props$start[2]; var height = _this$props$start[3]; var ratio = domSize.width / _this.image.nativeSize.width; if (aspectRatio) { height = width / aspectRatio; } (0, _utils.assign)(update, { x: x * ratio, y: y * ratio, width: width * ratio, height: height * ratio }); } _this.setState(update); }; _this.computeDOMSizes = function () { // size of the image cropper in the DOM var domSize = _this.imageDomSize(); var update = { domWidth: domSize.width, domHeight: domSize.height }; _this.setState(update); return update; }; _this.state = { x: 0, y: 0, width: 0, height: 0, domHeight: 0, domWidth: 0, resizing: false, dragging: false, deltaHandler: { x: 0, y: 0 }, pixelRatio: (0, _utils.getPixelRatio)() }; return _this; } _createClass(Cropper, [{ key: 'componentDidMount', value: function componentDidMount() { this.setupListeners(); this.computeDOMSizes(); } }, { key: 'componentWillUnmount', value: function componentWillUnmount() { this.teardownListeners(); } }, { key: 'setupListeners', value: function setupListeners() { window.addEventListener('mousemove', this.onMouseMove); window.addEventListener('mouseup', this.onMouseUp); window.addEventListener('resize', this.onWindowResize); } }, { key: 'teardownListeners', value: function teardownListeners() { window.removeEventListener('mousemove', this.onMouseMove); window.removeEventListener('mouseup', this.onMouseUp); window.removeEventListener('resize', this.onWindowResize); } }, { key: 'getRatio', value: function getRatio() { return this.image.nativeSize.width / this.state.domWidth; } }, { key: 'toNativeMetrics', value: function toNativeMetrics(_ref2) { var x = _ref2.x; var y = _ref2.y; var width = _ref2.width; var height = _ref2.height; // convert current in dom dimensions to sizes of the image object var ratio = this.getRatio(); var delta = this.state.deltaHandler; return { width: width * ratio, height: height * ratio, x: x * ratio, y: y * ratio }; } }, { key: 'getImageNode', value: function getImageNode() { return _reactDom2.default.findDOMNode(this.refs.image); } }, { key: 'imageDomSize', value: function imageDomSize() { var imgNode = this.getImageNode(); var cs = window.getComputedStyle(imgNode); var width = parseInt(cs.getPropertyValue('width').slice(0, -2), 10); var height = parseInt(cs.getPropertyValue('height').slice(0, -2), 10); return { width: width, height: height }; } }, { key: 'render', value: function render() { var containerWidth = this.state.domWidth; var containerHeight = this.state.domHeight; return _react2.default.createElement( 'div', { onDrag: this.onDrag, onMouseDown: this.onMouseDown, style: _extends({ position: 'relative', height: 0, paddingBottom: containerHeight / containerWidth * 100 + '%' }, this.props.style) }, this.props.children, _react2.default.createElement( 'div', { style: { position: 'absolute', top: 0, left: 0, zIndex: 1 } }, _react2.default.createElement(_Rect2.default, { canvasWidth: containerWidth, canvasHeight: containerHeight, width: this.state.width, height: this.state.height, x: this.state.x, y: this.state.y, borderColor: this.props.borderColor, handlerSize: this.props.handlerSize, pixelRatio: this.state.pixelRatio }) ), _react2.default.createElement('img', { ref: 'image', onLoad: this.onImageLoad, style: { position: 'absolute', top: 0, left: 0, zIndex: 0, maxWidth: '100%' }, src: this.props.src }) ); } }]); return Cropper; }(_react.Component); Cropper.propTypes = { src: _react.PropTypes.string.isRequired, minCropWidth: _react.PropTypes.number, maxCropWidth: _react.PropTypes.number, borderColor: _react.PropTypes.string, aspectRatio: _react.PropTypes.number, style: _react.PropTypes.object, start: _react.PropTypes.array, startChange: _react.PropTypes.bool, isLoading: _react.PropTypes.bool, onCrop: _react.PropTypes.func, onCropEnd: _react.PropTypes.func }; Cropper.defaultProps = { minCropWidth: 0, minCropHeight: 0, borderColor: '#FF4136', // red handlerSize: 20, start: null, style: {} }; exports.default = Cropper;