UNPKG

react-image-zoom-pan

Version:

Enables zooming and panning an image, both mobile and desktop.

731 lines (609 loc) 30.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _react = _interopRequireDefault(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _reselect = require("reselect"); var _warning = _interopRequireDefault(require("warning")); var _ZoomButtons = _interopRequireDefault(require("./ZoomButtons")); var _StateDebugView = _interopRequireDefault(require("./StateDebugView")); var _Utils = require("./Utils"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _extends() { _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; }; return _extends.apply(this, arguments); } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a 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); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var OVERZOOM_TOLERANCE = 0.05; var DOUBLE_TAP_THRESHOLD = 250; var ANIMATION_SPEED = 0.1; var isInitialized = function isInitialized(top, left, scale) { return scale !== undefined && left !== undefined && top !== undefined; }; var imageStyle = (0, _reselect.createSelector)(function (state) { return state.top; }, function (state) { return state.left; }, function (state) { return state.scale; }, function (top, left, scale) { var style = { cursor: 'pointer' }; return isInitialized(top, left, scale) ? _objectSpread({}, style, { transform: "translate3d(".concat(left, "px, ").concat(top, "px, 0) scale(").concat(scale, ")"), transformOrigin: '0 0' }) : style; }); var imageOverflow = (0, _reselect.createSelector)(function (state) { return state.top; }, function (state) { return state.left; }, function (state) { return state.scale; }, function (state) { return state.imageDimensions; }, function (state) { return state.containerDimensions; }, function (top, left, scale, imageDimensions, containerDimensions) { if (!isInitialized(top, left, scale)) { return ''; } return (0, _Utils.getImageOverflow)(top, left, scale, imageDimensions, containerDimensions); }); var browserPanActions = (0, _reselect.createSelector)(imageOverflow, function (imageOverflow) { //Determine the panning directions where there is no image overflow and let //the browser handle those directions (e.g., scroll viewport if possible). //Need to replace 'pan-left pan-right' with 'pan-x', etc. otherwise //it is rejected (o_O), therefore explicitly handle each combination. var browserPanX = !imageOverflow.left && !imageOverflow.right ? 'pan-x' //we can't pan the image horizontally, let the browser take it : !imageOverflow.left ? 'pan-left' : !imageOverflow.right ? 'pan-right' : ''; var browserPanY = !imageOverflow.top && !imageOverflow.bottom ? 'pan-y' : !imageOverflow.top ? 'pan-up' : !imageOverflow.bottom ? 'pan-down' : ''; return [browserPanX, browserPanY].join(' ').trim(); }); //Ensure the image is not over-panned, and not over- or under-scaled. //These constraints must be checked when image changes, and when container is resized. var PinchZoomPan = /*#__PURE__*/ function (_React$Component) { _inherits(PinchZoomPan, _React$Component); function PinchZoomPan() { var _getPrototypeOf2; var _this; _classCallCheck(this, PinchZoomPan); for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } _this = _possibleConstructorReturn(this, (_getPrototypeOf2 = _getPrototypeOf(PinchZoomPan)).call.apply(_getPrototypeOf2, [this].concat(args))); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "state", {}); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "lastPointerUpTimeStamp", void 0); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "lastPanPointerPosition", void 0); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "lastPinchLength", void 0); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "animation", void 0); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "imageRef", void 0); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "isImageLoaded", void 0); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "originalOverscrollBehaviorY", void 0); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "handleTouchStart", function (event) { _this.cancelAnimation(); var touches = event.touches; if (touches.length === 2) { _this.lastPinchLength = (0, _Utils.getPinchLength)(touches); _this.lastPanPointerPosition = null; } else if (touches.length === 1) { _this.lastPinchLength = null; _this.pointerDown(touches[0]); (0, _Utils.tryCancelEvent)(event); //suppress mouse events } }); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "handleTouchMove", function (event) { var touches = event.touches; if (touches.length === 2) { _this.pinchChange(touches); //suppress viewport scaling on iOS (0, _Utils.tryCancelEvent)(event); } else if (touches.length === 1) { var requestedPan = _this.pan(touches[0]); if (!_this.controlOverscrollViaCss) { //let the browser handling panning if we are at the edge of the image in //both pan directions, or if we are primarily panning in one direction //and are at the edge in that directino var overflow = imageOverflow(_this.state); var hasOverflowX = requestedPan.left && overflow.left > 0 || requestedPan.right && overflow.right > 0; var hasOverflowY = requestedPan.up && overflow.top > 0 || requestedPan.down && overflow.bottom > 0; if (!hasOverflowX && !hasOverflowY) { //no overflow in both directions return; } var panX = requestedPan.left || requestedPan.right; var panY = requestedPan.up || requestedPan.down; if (panY > 2 * panX && !hasOverflowY) { //primarily panning up or down and no overflow in the Y direction return; } if (panX > 2 * panY && !hasOverflowX) { //primarily panning left or right and no overflow in the X direction return; } (0, _Utils.tryCancelEvent)(event); } } }); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "handleTouchEnd", function (event) { _this.cancelAnimation(); if (event.touches.length === 0 && event.changedTouches.length === 1) { if (_this.lastPointerUpTimeStamp && _this.lastPointerUpTimeStamp + DOUBLE_TAP_THRESHOLD > event.timeStamp) { var pointerPosition = (0, _Utils.getRelativePosition)(event.changedTouches[0], _this.imageRef.parentNode); _this.doubleClick(pointerPosition); } _this.lastPointerUpTimeStamp = event.timeStamp; (0, _Utils.tryCancelEvent)(event); //suppress mouse events } //We allow transient +/-5% over-pinching. //Animate the bounce back to constraints if applicable. _this.maybeAdjustCurrentTransform(ANIMATION_SPEED); return; }); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "handleMouseDown", function (event) { _this.cancelAnimation(); _this.pointerDown(event); }); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "handleMouseMove", function (event) { if (!event.buttons) return null; _this.pan(event); }); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "handleMouseDoubleClick", function (event) { _this.cancelAnimation(); var pointerPosition = (0, _Utils.getRelativePosition)(event, _this.imageRef.parentNode); _this.doubleClick(pointerPosition); }); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "handleMouseWheel", function (event) { _this.cancelAnimation(); var point = (0, _Utils.getRelativePosition)(event, _this.imageRef.parentNode); if (event.deltaY > 0) { if (_this.state.scale > (0, _Utils.getMinScale)(_this.state, _this.props)) { _this.zoomOut(point); (0, _Utils.tryCancelEvent)(event); } } else if (event.deltaY < 0) { if (_this.state.scale < _this.props.maxScale) { _this.zoomIn(point); (0, _Utils.tryCancelEvent)(event); } } }); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "handleImageLoad", function (event) { _this.debug('handleImageLoad'); _this.isImageLoaded = true; _this.maybeHandleDimensionsChanged(); var onLoad = _react.default.Children.only(_this.props.children).props.onLoad; if (typeof onLoad === 'function') { onLoad(event); } }); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "handleZoomInClick", function () { _this.cancelAnimation(); _this.zoomIn(); }); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "handleZoomOutClick", function () { _this.cancelAnimation(); _this.zoomOut(); }); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "handleWindowResize", function () { return _this.maybeHandleDimensionsChanged(); }); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "handleRefImage", function (ref) { if (_this.imageRef) { _this.cancelAnimation(); _this.imageRef.removeEventListener('touchmove', _this.handleTouchMove); } _this.imageRef = ref; if (ref) { _this.imageRef.addEventListener('touchmove', _this.handleTouchMove, { passive: false }); } var _React$Children$only = _react.default.Children.only(_this.props.children), imageRefProp = _React$Children$only.ref; (0, _Utils.setRef)(imageRefProp, ref); }); return _this; } _createClass(PinchZoomPan, [{ key: "pointerDown", //actions value: function pointerDown(clientPosition) { this.lastPanPointerPosition = (0, _Utils.getRelativePosition)(clientPosition, this.imageRef.parentNode); } }, { key: "pan", value: function pan(pointerClientPosition) { if (!this.isTransformInitialized) { return; } if (!this.lastPanPointerPosition) { //if we were pinching and lifted a finger this.pointerDown(pointerClientPosition); return 0; } var pointerPosition = (0, _Utils.getRelativePosition)(pointerClientPosition, this.imageRef.parentNode); var translateX = pointerPosition.x - this.lastPanPointerPosition.x; var translateY = pointerPosition.y - this.lastPanPointerPosition.y; this.lastPanPointerPosition = pointerPosition; var top = this.state.top + translateY; var left = this.state.left + translateX; this.constrainAndApplyTransform(top, left, this.state.scale, 0, 0); return { up: translateY > 0 ? translateY : 0, down: translateY < 0 ? (0, _Utils.negate)(translateY) : 0, right: translateX < 0 ? (0, _Utils.negate)(translateX) : 0, left: translateX > 0 ? translateX : 0 }; } }, { key: "doubleClick", value: function doubleClick(pointerPosition) { if (String(this.props.doubleTapBehavior).toLowerCase() === 'zoom' && this.state.scale * (1 + OVERZOOM_TOLERANCE) < this.props.maxScale) { this.zoomIn(pointerPosition, ANIMATION_SPEED, 0.3); } else { //reset this.applyInitialTransform(ANIMATION_SPEED); } } }, { key: "pinchChange", value: function pinchChange(touches) { var length = (0, _Utils.getPinchLength)(touches); var midpoint = (0, _Utils.getPinchMidpoint)(touches); var scale = this.lastPinchLength ? this.state.scale * length / this.lastPinchLength //sometimes we get a touchchange before a touchstart when pinching : this.state.scale; this.zoom(scale, midpoint, OVERZOOM_TOLERANCE); this.lastPinchLength = length; } }, { key: "zoomIn", value: function zoomIn(midpoint) { var speed = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; var factor = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0.1; midpoint = midpoint || { x: this.state.containerDimensions.width / 2, y: this.state.containerDimensions.height / 2 }; this.zoom(this.state.scale * (1 + factor), midpoint, 0, speed); } }, { key: "zoomOut", value: function zoomOut(midpoint) { midpoint = midpoint || { x: this.state.containerDimensions.width / 2, y: this.state.containerDimensions.height / 2 }; this.zoom(this.state.scale * 0.9, midpoint, 0); } }, { key: "zoom", value: function zoom(requestedScale, containerRelativePoint, tolerance) { var speed = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0; if (!this.isTransformInitialized) { return; } var _this$state = this.state, scale = _this$state.scale, top = _this$state.top, left = _this$state.left; var imageRelativePoint = { top: containerRelativePoint.y - top, left: containerRelativePoint.x - left }; var nextScale = this.getConstrainedScale(requestedScale, tolerance); var incrementalScalePercentage = (nextScale - scale) / scale; var translateY = imageRelativePoint.top * incrementalScalePercentage; var translateX = imageRelativePoint.left * incrementalScalePercentage; var nextTop = top - translateY; var nextLeft = left - translateX; this.constrainAndApplyTransform(nextTop, nextLeft, nextScale, tolerance, speed); } //compare stored dimensions to actual dimensions; capture actual dimensions if different }, { key: "maybeHandleDimensionsChanged", value: function maybeHandleDimensionsChanged() { var _this2 = this; if (this.isImageReady) { var containerDimensions = (0, _Utils.getContainerDimensions)(this.imageRef); var imageDimensions = (0, _Utils.getDimensions)(this.imageRef); if (!(0, _Utils.isEqualDimensions)(containerDimensions, (0, _Utils.getDimensions)(this.state.containerDimensions)) || !(0, _Utils.isEqualDimensions)(imageDimensions, (0, _Utils.getDimensions)(this.state.imageDimensions))) { this.cancelAnimation(); //capture new dimensions this.setState({ containerDimensions: containerDimensions, imageDimensions: imageDimensions }, function () { //When image loads and image dimensions are first established, apply initial transform. //If dimensions change, constraints change; current transform may need to be adjusted. //Transforms depend on state, so wait until state is updated. if (!_this2.isTransformInitialized) { _this2.applyInitialTransform(); } else { _this2.maybeAdjustCurrentTransform(); } }); this.debug("Dimensions changed: Container: ".concat(containerDimensions.width, ", ").concat(containerDimensions.height, ", Image: ").concat(imageDimensions.width, ", ").concat(imageDimensions.height)); } } else { this.debug('Image not loaded'); } } //transformation methods //Zooming and panning cause transform to be requested. }, { key: "constrainAndApplyTransform", value: function constrainAndApplyTransform(requestedTop, requestedLeft, requestedScale, tolerance) { var speed = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0; var requestedTransform = { top: requestedTop, left: requestedLeft, scale: requestedScale }; this.debug("Requesting transform: left ".concat(requestedLeft, ", top ").concat(requestedTop, ", scale ").concat(requestedScale)); //Correct the transform if needed to prevent overpanning and overzooming var transform = this.getCorrectedTransform(requestedTransform, tolerance) || requestedTransform; this.debug("Applying transform: left ".concat(transform.left, ", top ").concat(transform.top, ", scale ").concat(transform.scale)); if ((0, _Utils.isEqualTransform)(transform, this.state)) { return false; } this.applyTransform(transform, speed); return true; } }, { key: "applyTransform", value: function applyTransform(_ref, speed) { var _this3 = this; var top = _ref.top, left = _ref.left, scale = _ref.scale; if (speed > 0) { var frame = function frame() { var translateY = top - _this3.state.top; var translateX = left - _this3.state.left; var translateScale = scale - _this3.state.scale; var nextTransform = { top: (0, _Utils.snapToTarget)(_this3.state.top + speed * translateY, top, 1), left: (0, _Utils.snapToTarget)(_this3.state.left + speed * translateX, left, 1), scale: (0, _Utils.snapToTarget)(_this3.state.scale + speed * translateScale, scale, 0.001) }; //animation runs until we reach the target if (!(0, _Utils.isEqualTransform)(nextTransform, _this3.state)) { _this3.setState(nextTransform, function () { return _this3.animation = requestAnimationFrame(frame); }); } }; this.animation = requestAnimationFrame(frame); } else { this.setState({ top: top, left: left, scale: scale }); } } //Returns constrained scale when requested scale is outside min/max with tolerance, otherwise returns requested scale }, { key: "getConstrainedScale", value: function getConstrainedScale(requestedScale, tolerance) { var lowerBoundFactor = 1.0 - tolerance; var upperBoundFactor = 1.0 + tolerance; return (0, _Utils.constrain)((0, _Utils.getMinScale)(this.state, this.props) * lowerBoundFactor, this.props.maxScale * upperBoundFactor, requestedScale); } //Returns constrained transform when requested transform is outside constraints with tolerance, otherwise returns null }, { key: "getCorrectedTransform", value: function getCorrectedTransform(requestedTransform, tolerance) { var scale = this.getConstrainedScale(requestedTransform.scale, tolerance); //get dimensions by which scaled image overflows container var negativeSpace = this.calculateNegativeSpace(scale); var overflow = { width: Math.max(0, (0, _Utils.negate)(negativeSpace.width)), height: Math.max(0, (0, _Utils.negate)(negativeSpace.height)) }; //if image overflows container, prevent moving by more than the overflow //example: overflow.height = 100, tolerance = 0.05 => top is constrained between -105 and +5 var _this$props = this.props, position = _this$props.position, initialTop = _this$props.initialTop, initialLeft = _this$props.initialLeft; var _this$state2 = this.state, imageDimensions = _this$state2.imageDimensions, containerDimensions = _this$state2.containerDimensions; var upperBoundFactor = 1.0 + tolerance; var top = overflow.height ? (0, _Utils.constrain)((0, _Utils.negate)(overflow.height) * upperBoundFactor, overflow.height * upperBoundFactor - overflow.height, requestedTransform.top) : position === 'center' ? (containerDimensions.height - imageDimensions.height * scale) / 2 : initialTop || 0; var left = overflow.width ? (0, _Utils.constrain)((0, _Utils.negate)(overflow.width) * upperBoundFactor, overflow.width * upperBoundFactor - overflow.width, requestedTransform.left) : position === 'center' ? (containerDimensions.width - imageDimensions.width * scale) / 2 : initialLeft || 0; var constrainedTransform = { top: top, left: left, scale: scale }; return (0, _Utils.isEqualTransform)(constrainedTransform, requestedTransform) ? null : constrainedTransform; } //Ensure current transform is within constraints }, { key: "maybeAdjustCurrentTransform", value: function maybeAdjustCurrentTransform() { var speed = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; var correctedTransform; if (correctedTransform = this.getCorrectedTransform(this.state, 0)) { this.applyTransform(correctedTransform, speed); } } }, { key: "applyInitialTransform", value: function applyInitialTransform() { var speed = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; var _this$state3 = this.state, imageDimensions = _this$state3.imageDimensions, containerDimensions = _this$state3.containerDimensions; var _this$props2 = this.props, position = _this$props2.position, initialScale = _this$props2.initialScale, maxScale = _this$props2.maxScale, initialTop = _this$props2.initialTop, initialLeft = _this$props2.initialLeft; var scale = String(initialScale).toLowerCase() === 'auto' ? (0, _Utils.getAutofitScale)(containerDimensions, imageDimensions) : initialScale; var minScale = (0, _Utils.getMinScale)(this.state, this.props); if (minScale > maxScale) { (0, _warning.default)(false, 'minScale cannot exceed maxScale.'); return; } if (scale < minScale || scale > maxScale) { (0, _warning.default)(false, 'initialScale must be between minScale and maxScale.'); return; } var initialPosition; if (position === 'center') { (0, _warning.default)(initialTop === undefined, 'initialTop prop should not be supplied with position=center. It was ignored.'); (0, _warning.default)(initialLeft === undefined, 'initialLeft prop should not be supplied with position=center. It was ignored.'); initialPosition = { top: (containerDimensions.width - imageDimensions.width * scale) / 2, left: (containerDimensions.height - imageDimensions.height * scale) / 2 }; } else { initialPosition = { top: initialTop || 0, left: initialLeft || 0 }; } this.constrainAndApplyTransform(initialPosition.top, initialPosition.left, scale, 0, speed); } //lifecycle methods }, { key: "render", value: function render() { var childElement = _react.default.Children.only(this.props.children); var _this$props3 = this.props, zoomButtons = _this$props3.zoomButtons, maxScale = _this$props3.maxScale, debug = _this$props3.debug; var scale = this.state.scale; var touchAction = this.controlOverscrollViaCss ? browserPanActions(this.state) || 'none' : undefined; var containerStyle = { width: '100%', height: '100%', overflow: 'hidden', touchAction: touchAction }; return _react.default.createElement("div", { style: containerStyle }, zoomButtons && this.isImageReady && this.isTransformInitialized && _react.default.createElement(_ZoomButtons.default, { scale: scale, minScale: (0, _Utils.getMinScale)(this.state, this.props), maxScale: maxScale, onZoomOutClick: this.handleZoomOutClick, onZoomInClick: this.handleZoomInClick }), debug && _react.default.createElement(_StateDebugView.default, _extends({}, this.state, { overflow: imageOverflow(this.state) })), _react.default.cloneElement(childElement, { onTouchStart: this.handleTouchStart, onTouchEnd: this.handleTouchEnd, onMouseDown: this.handleMouseDown, onMouseMove: this.handleMouseMove, onDoubleClick: this.handleMouseDoubleClick, onWheel: this.handleMouseWheel, onDragStart: _Utils.tryCancelEvent, onLoad: this.handleImageLoad, onContextMenu: _Utils.tryCancelEvent, ref: this.handleRefImage, style: imageStyle(this.state) })); } }, { key: "componentDidMount", value: function componentDidMount() { window.addEventListener("resize", this.handleWindowResize); this.maybeHandleDimensionsChanged(); } }, { key: "componentDidUpdate", value: function componentDidUpdate(prevProps, prevState) { this.maybeHandleDimensionsChanged(); } }, { key: "componentWillUnmount", value: function componentWillUnmount() { this.cancelAnimation(); this.imageRef.removeEventListener('touchmove', this.handleTouchMove); window.removeEventListener('resize', this.handleWindowResize); } }, { key: "calculateNegativeSpace", value: function calculateNegativeSpace(scale) { //get difference in dimension between container and scaled image var _this$state4 = this.state, containerDimensions = _this$state4.containerDimensions, imageDimensions = _this$state4.imageDimensions; var width = containerDimensions.width - scale * imageDimensions.width; var height = containerDimensions.height - scale * imageDimensions.height; return { width: width, height: height }; } }, { key: "cancelAnimation", value: function cancelAnimation() { if (this.animation) { cancelAnimationFrame(this.animation); } } }, { key: "debug", value: function debug(message) { if (this.props.debug) { console.log(message); } } }, { key: "isImageReady", get: function get() { return this.isImageLoaded || this.imageRef && this.imageRef.tagName !== 'IMG'; } }, { key: "isTransformInitialized", get: function get() { return isInitialized(this.state.top, this.state.left, this.state.scale); } }, { key: "controlOverscrollViaCss", get: function get() { return window.CSS && window.CSS.supports('touch-action', 'pan-up'); } }], [{ key: "getDerivedStateFromProps", value: function getDerivedStateFromProps(nextProps, prevState) { if (nextProps.initialTop !== prevState.initialTop || nextProps.initialLeft !== prevState.initialLeft || nextProps.initialScale !== prevState.initialScale || nextProps.position !== prevState.position) { return { position: nextProps.position, initialScale: nextProps.initialScale, initialTop: nextProps.initialTop, initialLeft: nextProps.initialLeft }; } else { return null; } } }]); return PinchZoomPan; }(_react.default.Component); exports.default = PinchZoomPan; PinchZoomPan.defaultProps = { initialScale: 'auto', minScale: 'auto', maxScale: 1, position: 'topLeft', zoomButtons: true, doubleTapBehavior: 'reset' }; PinchZoomPan.propTypes = { children: _propTypes.default.element.isRequired, initialScale: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]), minScale: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]), maxScale: _propTypes.default.number, position: _propTypes.default.oneOf(['topLeft', 'center']), zoomButtons: _propTypes.default.bool, doubleTapBehavior: _propTypes.default.oneOf(['reset', 'zoom']), initialTop: _propTypes.default.number, initialLeft: _propTypes.default.number };