react-native-image-pan-zoom
Version:
react native image pan zoom
634 lines • 33.7 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
Object.defineProperty(exports, "__esModule", { value: true });
var React = require("react");
var react_native_1 = require("react-native");
var image_zoom_style_1 = require("./image-zoom.style");
var image_zoom_type_1 = require("./image-zoom.type");
var ImageViewer = /** @class */ (function (_super) {
__extends(ImageViewer, _super);
function ImageViewer() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.state = new image_zoom_type_1.ImageZoomState();
// 上次/当前/动画 x 位移
_this.lastPositionX = null;
_this.positionX = 0;
_this.animatedPositionX = new react_native_1.Animated.Value(0);
// 上次/当前/动画 y 位移
_this.lastPositionY = null;
_this.positionY = 0;
_this.animatedPositionY = new react_native_1.Animated.Value(0);
// 缩放大小
_this.scale = 1;
_this.animatedScale = new react_native_1.Animated.Value(1);
_this.zoomLastDistance = null;
_this.zoomCurrentDistance = 0;
// 上次手按下去的时间
_this.lastTouchStartTime = 0;
// 滑动过程中,整体横向过界偏移量
_this.horizontalWholeOuterCounter = 0;
// 滑动过程中,swipeDown 偏移量
_this.swipeDownOffset = 0;
// 滑动过程中,x y的总位移
_this.horizontalWholeCounter = 0;
_this.verticalWholeCounter = 0;
// 两手距离中心点位置
_this.centerDiffX = 0;
_this.centerDiffY = 0;
// 上一次点击的时间
_this.lastClickTime = 0;
// 双击时的位置
_this.doubleClickX = 0;
_this.doubleClickY = 0;
// 是否双击了
_this.isDoubleClick = false;
// 是否是长按
_this.isLongPress = false;
// 是否在左右滑
_this.isHorizontalWrap = false;
// 图片手势处理
_this.imagePanResponder = react_native_1.PanResponder.create({
// 要求成为响应者:
onStartShouldSetPanResponder: _this.props.onStartShouldSetPanResponder,
onPanResponderTerminationRequest: _this.props.onPanResponderTerminationRequest,
onMoveShouldSetPanResponder: _this.props.onMoveShouldSetPanResponder,
onPanResponderGrant: function (evt) {
// 开始手势操作
_this.lastPositionX = null;
_this.lastPositionY = null;
_this.zoomLastDistance = null;
_this.horizontalWholeCounter = 0;
_this.verticalWholeCounter = 0;
_this.lastTouchStartTime = new Date().getTime();
_this.isDoubleClick = false;
_this.isLongPress = false;
_this.isHorizontalWrap = false;
// 任何手势开始,都清空单击计时器
if (_this.singleClickTimeout) {
clearTimeout(_this.singleClickTimeout);
}
if (evt.nativeEvent.changedTouches.length > 1) {
var centerX = (evt.nativeEvent.changedTouches[0].pageX + evt.nativeEvent.changedTouches[1].pageX) / 2;
_this.centerDiffX = centerX - _this.props.cropWidth / 2;
var centerY = (evt.nativeEvent.changedTouches[0].pageY + evt.nativeEvent.changedTouches[1].pageY) / 2;
_this.centerDiffY = centerY - _this.props.cropHeight / 2;
}
// 计算长按
if (_this.longPressTimeout) {
clearTimeout(_this.longPressTimeout);
}
var _a = evt.nativeEvent, locationX = _a.locationX, locationY = _a.locationY, pageX = _a.pageX, pageY = _a.pageY;
_this.longPressTimeout = setTimeout(function () {
_this.isLongPress = true;
if (_this.props.onLongPress) {
_this.props.onLongPress({ locationX: locationX, locationY: locationY, pageX: pageX, pageY: pageY });
}
}, _this.props.longPressTime);
if (evt.nativeEvent.changedTouches.length <= 1) {
// 一个手指的情况
if (new Date().getTime() - _this.lastClickTime < (_this.props.doubleClickInterval || 0)) {
// 认为触发了双击
_this.lastClickTime = 0;
// 因为可能触发放大,因此记录双击时的坐标位置
_this.doubleClickX = evt.nativeEvent.changedTouches[0].pageX;
_this.doubleClickY = evt.nativeEvent.changedTouches[0].pageY;
if (_this.props.onDoubleClick) {
_this.props.onDoubleClick({
locationX: evt.nativeEvent.changedTouches[0].locationX,
locationY: evt.nativeEvent.changedTouches[0].locationY,
pageX: _this.doubleClickX,
pageY: _this.doubleClickY,
});
}
// 取消长按
clearTimeout(_this.longPressTimeout);
// 缩放
_this.isDoubleClick = true;
if (_this.props.enableDoubleClickZoom) {
if (_this.scale > 1 || _this.scale < 1) {
// 回归原位
_this.scale = 1;
_this.positionX = 0;
_this.positionY = 0;
}
else {
// 开始在位移地点缩放
// 记录之前缩放比例
// 此时 this.scale 一定为 1
var beforeScale = _this.scale;
// 开始缩放
_this.scale = 2;
// 缩放 diff
var diffScale = _this.scale - beforeScale;
// 找到两手中心点距离页面中心的位移
// 移动位置
_this.positionX = ((_this.props.cropWidth / 2 - _this.doubleClickX) * diffScale) / _this.scale;
_this.positionY = ((_this.props.cropHeight / 2 - _this.doubleClickY) * diffScale) / _this.scale;
}
_this.imageDidMove('centerOn');
react_native_1.Animated.parallel([
react_native_1.Animated.timing(_this.animatedScale, {
toValue: _this.scale,
duration: 100,
useNativeDriver: !!_this.props.useNativeDriver,
}),
react_native_1.Animated.timing(_this.animatedPositionX, {
toValue: _this.positionX,
duration: 100,
useNativeDriver: !!_this.props.useNativeDriver,
}),
react_native_1.Animated.timing(_this.animatedPositionY, {
toValue: _this.positionY,
duration: 100,
useNativeDriver: !!_this.props.useNativeDriver,
}),
]).start();
}
}
else {
_this.lastClickTime = new Date().getTime();
}
}
},
onPanResponderMove: function (evt, gestureState) {
if (_this.isDoubleClick) {
// 有时双击会被当做位移,这里屏蔽掉
return;
}
if (evt.nativeEvent.changedTouches.length <= 1) {
// x 位移
var diffX = gestureState.dx - (_this.lastPositionX || 0);
if (_this.lastPositionX === null) {
diffX = 0;
}
// y 位移
var diffY = gestureState.dy - (_this.lastPositionY || 0);
if (_this.lastPositionY === null) {
diffY = 0;
}
// 保留这一次位移作为下次的上一次位移
_this.lastPositionX = gestureState.dx;
_this.lastPositionY = gestureState.dy;
_this.horizontalWholeCounter += diffX;
_this.verticalWholeCounter += diffY;
if (Math.abs(_this.horizontalWholeCounter) > 5 || Math.abs(_this.verticalWholeCounter) > 5) {
// 如果位移超出手指范围,取消长按监听
clearTimeout(_this.longPressTimeout);
}
if (_this.props.panToMove) {
// 处理左右滑,如果正在 swipeDown,左右滑失效
if (_this.swipeDownOffset === 0) {
if (Math.abs(diffX) > Math.abs(diffY)) {
_this.isHorizontalWrap = true;
}
// diffX > 0 表示手往右滑,图往左移动,反之同理
// horizontalWholeOuterCounter > 0 表示溢出在左侧,反之在右侧,绝对值越大溢出越多
if (_this.props.imageWidth * _this.scale > _this.props.cropWidth) {
// 如果图片宽度大图盒子宽度, 可以横向拖拽
// 没有溢出偏移量或者这次位移完全收回了偏移量才能拖拽
if (_this.horizontalWholeOuterCounter > 0) {
// 溢出在右侧
if (diffX < 0) {
// 从右侧收紧
if (_this.horizontalWholeOuterCounter > Math.abs(diffX)) {
// 偏移量还没有用完
_this.horizontalWholeOuterCounter += diffX;
diffX = 0;
}
else {
// 溢出量置为0,偏移量减去剩余溢出量,并且可以被拖动
diffX += _this.horizontalWholeOuterCounter;
_this.horizontalWholeOuterCounter = 0;
if (_this.props.horizontalOuterRangeOffset) {
_this.props.horizontalOuterRangeOffset(0);
}
}
}
else {
// 向右侧扩增
_this.horizontalWholeOuterCounter += diffX;
}
}
else if (_this.horizontalWholeOuterCounter < 0) {
// 溢出在左侧
if (diffX > 0) {
// 从左侧收紧
if (Math.abs(_this.horizontalWholeOuterCounter) > diffX) {
// 偏移量还没有用完
_this.horizontalWholeOuterCounter += diffX;
diffX = 0;
}
else {
// 溢出量置为0,偏移量减去剩余溢出量,并且可以被拖动
diffX += _this.horizontalWholeOuterCounter;
_this.horizontalWholeOuterCounter = 0;
if (_this.props.horizontalOuterRangeOffset) {
_this.props.horizontalOuterRangeOffset(0);
}
}
}
else {
// 向左侧扩增
_this.horizontalWholeOuterCounter += diffX;
}
}
else {
// 溢出偏移量为0,正常移动
}
// 产生位移
_this.positionX += diffX / _this.scale;
// 但是横向不能出现黑边
// 横向能容忍的绝对值
var horizontalMax = (_this.props.imageWidth * _this.scale - _this.props.cropWidth) / 2 / _this.scale;
if (_this.positionX < -horizontalMax) {
// 超越了左边临界点,还在继续向左移动
_this.positionX = -horizontalMax;
// 让其产生细微位移,偏离轨道
_this.horizontalWholeOuterCounter += -1 / 1e10;
}
else if (_this.positionX > horizontalMax) {
// 超越了右侧临界点,还在继续向右移动
_this.positionX = horizontalMax;
// 让其产生细微位移,偏离轨道
_this.horizontalWholeOuterCounter += 1 / 1e10;
}
_this.animatedPositionX.setValue(_this.positionX);
}
else {
// 不能横向拖拽,全部算做溢出偏移量
_this.horizontalWholeOuterCounter += diffX;
}
// 溢出量不会超过设定界限
if (_this.horizontalWholeOuterCounter > (_this.props.maxOverflow || 0)) {
_this.horizontalWholeOuterCounter = _this.props.maxOverflow || 0;
}
else if (_this.horizontalWholeOuterCounter < -(_this.props.maxOverflow || 0)) {
_this.horizontalWholeOuterCounter = -(_this.props.maxOverflow || 0);
}
if (_this.horizontalWholeOuterCounter !== 0) {
// 如果溢出偏移量不是0,执行溢出回调
if (_this.props.horizontalOuterRangeOffset) {
_this.props.horizontalOuterRangeOffset(_this.horizontalWholeOuterCounter);
}
}
}
// 如果图片高度大于盒子高度, 可以纵向弹性拖拽
if (_this.props.imageHeight * _this.scale > _this.props.cropHeight) {
_this.positionY += diffY / _this.scale;
_this.animatedPositionY.setValue(_this.positionY);
// 如果图片上边缘脱离屏幕上边缘,则进入 swipeDown 动作
// if (
// (this.props.imageHeight / 2 - this.positionY) * this.scale <
// this.props.cropHeight / 2
// ) {
// if (this.props.enableSwipeDown) {
// this.swipeDownOffset += diffY
// // 只要滑动溢出量不小于 0,就可以拖动
// if (this.swipeDownOffset > 0) {
// this.positionY += diffY / this.scale
// this.animatedPositionY.setValue(this.positionY)
// // 越到下方,缩放越小
// this.scale = this.scale - diffY / 1000
// this.animatedScale.setValue(this.scale)
// }
// }
// }
}
else {
// swipeDown 不允许在已经有横向偏移量时触发
if (_this.props.enableSwipeDown && !_this.isHorizontalWrap) {
// 图片高度小于盒子高度,只能向下拖拽,而且一定是 swipeDown 动作
_this.swipeDownOffset += diffY;
// 只要滑动溢出量不小于 0,就可以拖动
if (_this.swipeDownOffset > 0) {
_this.positionY += diffY / _this.scale;
_this.animatedPositionY.setValue(_this.positionY);
// 越到下方,缩放越小
_this.scale = _this.scale - diffY / 1000;
_this.animatedScale.setValue(_this.scale);
}
}
}
}
}
else {
// 多个手指的情况
// 取消长按状态
if (_this.longPressTimeout) {
clearTimeout(_this.longPressTimeout);
}
if (_this.props.pinchToZoom) {
// 找最小的 x 和最大的 x
var minX = void 0;
var maxX = void 0;
if (evt.nativeEvent.changedTouches[0].locationX > evt.nativeEvent.changedTouches[1].locationX) {
minX = evt.nativeEvent.changedTouches[1].pageX;
maxX = evt.nativeEvent.changedTouches[0].pageX;
}
else {
minX = evt.nativeEvent.changedTouches[0].pageX;
maxX = evt.nativeEvent.changedTouches[1].pageX;
}
var minY = void 0;
var maxY = void 0;
if (evt.nativeEvent.changedTouches[0].locationY > evt.nativeEvent.changedTouches[1].locationY) {
minY = evt.nativeEvent.changedTouches[1].pageY;
maxY = evt.nativeEvent.changedTouches[0].pageY;
}
else {
minY = evt.nativeEvent.changedTouches[0].pageY;
maxY = evt.nativeEvent.changedTouches[1].pageY;
}
var widthDistance = maxX - minX;
var heightDistance = maxY - minY;
var diagonalDistance = Math.sqrt(widthDistance * widthDistance + heightDistance * heightDistance);
_this.zoomCurrentDistance = Number(diagonalDistance.toFixed(1));
if (_this.zoomLastDistance !== null) {
var distanceDiff = (_this.zoomCurrentDistance - _this.zoomLastDistance) / 200;
var zoom = _this.scale + distanceDiff;
if (zoom < (_this.props.minScale || 0)) {
zoom = _this.props.minScale || 0;
}
if (zoom > (_this.props.maxScale || 0)) {
zoom = _this.props.maxScale || 0;
}
// 记录之前缩放比例
var beforeScale = _this.scale;
// 开始缩放
_this.scale = zoom;
_this.animatedScale.setValue(_this.scale);
// 图片要慢慢往两个手指的中心点移动
// 缩放 diff
var diffScale = _this.scale - beforeScale;
// 找到两手中心点距离页面中心的位移
// 移动位置
_this.positionX -= (_this.centerDiffX * diffScale) / _this.scale;
_this.positionY -= (_this.centerDiffY * diffScale) / _this.scale;
_this.animatedPositionX.setValue(_this.positionX);
_this.animatedPositionY.setValue(_this.positionY);
}
_this.zoomLastDistance = _this.zoomCurrentDistance;
}
}
_this.imageDidMove('onPanResponderMove');
},
onPanResponderRelease: function (evt, gestureState) {
// 取消长按
if (_this.longPressTimeout) {
clearTimeout(_this.longPressTimeout);
}
// 双击结束,结束尾判断
if (_this.isDoubleClick) {
return;
}
// 长按结束,结束尾判断
if (_this.isLongPress) {
return;
}
// 如果是单个手指、距离上次按住大于预设秒、滑动距离小于预设值, 则可能是单击(如果后续双击间隔内没有开始手势)
// const stayTime = new Date().getTime() - this.lastTouchStartTime!
var moveDistance = Math.sqrt(gestureState.dx * gestureState.dx + gestureState.dy * gestureState.dy);
var _a = evt.nativeEvent, locationX = _a.locationX, locationY = _a.locationY, pageX = _a.pageX, pageY = _a.pageY;
if (evt.nativeEvent.changedTouches.length === 1 && moveDistance < (_this.props.clickDistance || 0)) {
_this.singleClickTimeout = setTimeout(function () {
if (_this.props.onClick) {
_this.props.onClick({ locationX: locationX, locationY: locationY, pageX: pageX, pageY: pageY });
}
}, _this.props.doubleClickInterval);
}
else {
// 多手势结束,或者滑动结束
if (_this.props.responderRelease) {
_this.props.responderRelease(gestureState.vx, _this.scale);
}
_this.panResponderReleaseResolve();
}
},
onPanResponderTerminate: function () {
//
},
});
_this.resetScale = function () {
_this.positionX = 0;
_this.positionY = 0;
_this.scale = 1;
_this.animatedScale.setValue(1);
};
_this.panResponderReleaseResolve = function () {
// 判断是否是 swipeDown
if (_this.props.enableSwipeDown && _this.props.swipeDownThreshold) {
if (_this.swipeDownOffset > _this.props.swipeDownThreshold) {
if (_this.props.onSwipeDown) {
_this.props.onSwipeDown();
}
// Stop reset.
return;
}
}
if (_this.props.enableCenterFocus && _this.scale < 1) {
// 如果缩放小于1,强制重置为 1
_this.scale = 1;
react_native_1.Animated.timing(_this.animatedScale, {
toValue: _this.scale,
duration: 100,
useNativeDriver: !!_this.props.useNativeDriver,
}).start();
}
if (_this.props.imageWidth * _this.scale <= _this.props.cropWidth) {
// 如果图片宽度小于盒子宽度,横向位置重置
_this.positionX = 0;
react_native_1.Animated.timing(_this.animatedPositionX, {
toValue: _this.positionX,
duration: 100,
useNativeDriver: !!_this.props.useNativeDriver,
}).start();
}
if (_this.props.imageHeight * _this.scale <= _this.props.cropHeight) {
// 如果图片高度小于盒子高度,纵向位置重置
_this.positionY = 0;
react_native_1.Animated.timing(_this.animatedPositionY, {
toValue: _this.positionY,
duration: 100,
useNativeDriver: !!_this.props.useNativeDriver,
}).start();
}
// 横向肯定不会超出范围,由拖拽时控制
// 如果图片高度大于盒子高度,纵向不能出现黑边
if (_this.props.imageHeight * _this.scale > _this.props.cropHeight) {
// 纵向能容忍的绝对值
var verticalMax = (_this.props.imageHeight * _this.scale - _this.props.cropHeight) / 2 / _this.scale;
if (_this.positionY < -verticalMax) {
_this.positionY = -verticalMax;
}
else if (_this.positionY > verticalMax) {
_this.positionY = verticalMax;
}
react_native_1.Animated.timing(_this.animatedPositionY, {
toValue: _this.positionY,
duration: 100,
useNativeDriver: !!_this.props.useNativeDriver,
}).start();
}
if (_this.props.imageWidth * _this.scale > _this.props.cropWidth) {
// 纵向能容忍的绝对值
var horizontalMax = (_this.props.imageWidth * _this.scale - _this.props.cropWidth) / 2 / _this.scale;
if (_this.positionX < -horizontalMax) {
_this.positionX = -horizontalMax;
}
else if (_this.positionX > horizontalMax) {
_this.positionX = horizontalMax;
}
react_native_1.Animated.timing(_this.animatedPositionX, {
toValue: _this.positionX,
duration: 100,
useNativeDriver: !!_this.props.useNativeDriver,
}).start();
}
// 拖拽正常结束后,如果没有缩放,直接回到0,0点
if (_this.props.enableCenterFocus && _this.scale === 1) {
_this.positionX = 0;
_this.positionY = 0;
react_native_1.Animated.timing(_this.animatedPositionX, {
toValue: _this.positionX,
duration: 100,
useNativeDriver: !!_this.props.useNativeDriver,
}).start();
react_native_1.Animated.timing(_this.animatedPositionY, {
toValue: _this.positionY,
duration: 100,
useNativeDriver: !!_this.props.useNativeDriver,
}).start();
}
// 水平溢出量置空
_this.horizontalWholeOuterCounter = 0;
// swipeDown 溢出量置空
_this.swipeDownOffset = 0;
_this.imageDidMove('onPanResponderRelease');
};
return _this;
}
ImageViewer.prototype.componentDidMount = function () {
if (this.props.centerOn) {
this.centerOn(this.props.centerOn);
}
};
ImageViewer.prototype.componentDidUpdate = function (prevProps) {
// Either centerOn has never been called, or it is a repeat and we should ignore it
if ((this.props.centerOn && !prevProps.centerOn) ||
(this.props.centerOn && prevProps.centerOn && this.didCenterOnChange(prevProps.centerOn, this.props.centerOn))) {
this.centerOn(this.props.centerOn);
}
};
ImageViewer.prototype.imageDidMove = function (type) {
if (this.props.onMove) {
this.props.onMove({
type: type,
positionX: this.positionX,
positionY: this.positionY,
scale: this.scale,
zoomCurrentDistance: this.zoomCurrentDistance,
});
}
};
ImageViewer.prototype.didCenterOnChange = function (params, paramsNext) {
return params.x !== paramsNext.x || params.y !== paramsNext.y || params.scale !== paramsNext.scale;
};
ImageViewer.prototype.centerOn = function (params) {
var _this = this;
this.positionX = params.x;
this.positionY = params.y;
this.scale = params.scale;
var duration = params.duration || 300;
react_native_1.Animated.parallel([
react_native_1.Animated.timing(this.animatedScale, {
toValue: this.scale,
duration: duration,
useNativeDriver: !!this.props.useNativeDriver,
}),
react_native_1.Animated.timing(this.animatedPositionX, {
toValue: this.positionX,
duration: duration,
useNativeDriver: !!this.props.useNativeDriver,
}),
react_native_1.Animated.timing(this.animatedPositionY, {
toValue: this.positionY,
duration: duration,
useNativeDriver: !!this.props.useNativeDriver,
}),
]).start(function () {
_this.imageDidMove('centerOn');
});
};
/**
* 图片区域视图渲染完毕
*/
ImageViewer.prototype.handleLayout = function (event) {
if (this.props.layoutChange) {
this.props.layoutChange(event);
}
};
/**
* 重置大小和位置
*/
ImageViewer.prototype.reset = function () {
this.scale = 1;
this.animatedScale.setValue(this.scale);
this.positionX = 0;
this.animatedPositionX.setValue(this.positionX);
this.positionY = 0;
this.animatedPositionY.setValue(this.positionY);
};
ImageViewer.prototype.render = function () {
var animateConf = {
transform: [
{
scale: this.animatedScale,
},
{
translateX: this.animatedPositionX,
},
{
translateY: this.animatedPositionY,
},
],
};
var parentStyles = react_native_1.StyleSheet.flatten(this.props.style);
return (<react_native_1.View style={__assign(__assign(__assign({}, image_zoom_style_1.default.container), parentStyles), { width: this.props.cropWidth, height: this.props.cropHeight })} {...this.imagePanResponder.panHandlers}>
<react_native_1.Animated.View style={animateConf} renderToHardwareTextureAndroid={this.props.useHardwareTextureAndroid}>
<react_native_1.View onLayout={this.handleLayout.bind(this)} style={{
width: this.props.imageWidth,
height: this.props.imageHeight,
}}>
{this.props.children}
</react_native_1.View>
</react_native_1.Animated.View>
</react_native_1.View>);
};
ImageViewer.defaultProps = new image_zoom_type_1.ImageZoomProps();
return ImageViewer;
}(React.Component));
exports.default = ImageViewer;
//# sourceMappingURL=image-zoom.component.js.map