react-native-image-zoom-viewer
Version:
react native image viewer,大图浏览
551 lines (547 loc) • 25 kB
JavaScript
;
var __extends = (this && this.__extends) || (function () {
var 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 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) || 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;
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0)
t[p[i]] = s[p[i]];
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
var React = require("react");
var react_native_1 = require("react-native");
var react_native_image_pan_zoom_1 = require("react-native-image-pan-zoom");
var image_viewer_style_1 = require("./image-viewer.style");
var image_viewer_type_1 = require("./image-viewer.type");
var ImageViewer = /** @class */ (function (_super) {
__extends(ImageViewer, _super);
function ImageViewer() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.state = new image_viewer_type_1.State();
// 背景透明度渐变动画
_this.fadeAnim = new react_native_1.Animated.Value(0);
// 当前基准位置
_this.standardPositionX = 0;
// 整体位移,用来切换图片用
_this.positionXNumber = 0;
_this.positionX = new react_native_1.Animated.Value(0);
_this.width = 0;
_this.height = 0;
_this.styles = image_viewer_style_1.default(0, 0, 'transparent');
// 是否执行过 layout. fix 安卓不断触发 onLayout 的 bug
_this.hasLayout = false;
// 记录已加载的图片 index
_this.loadedIndex = new Map();
_this.handleLongPressWithIndex = new Map();
_this.imageRefs = [];
/**
* reset Image scale and position
*/
_this.resetImageByIndex = function (index) {
_this.imageRefs[index] && _this.imageRefs[index].reset();
};
/**
* 预加载图片
*/
_this.preloadImage = function (index) {
if (index < _this.state.imageSizes.length) {
_this.loadImage(index + 1);
}
};
/**
* 触发溢出水平滚动
*/
_this.handleHorizontalOuterRangeOffset = function (offsetX) {
if (offsetX === void 0) { offsetX = 0; }
_this.positionXNumber = _this.standardPositionX + offsetX;
_this.positionX.setValue(_this.positionXNumber);
var offsetXRTL = !react_native_1.I18nManager.isRTL ? offsetX : -offsetX;
if (offsetXRTL < 0) {
if (_this.state.currentShowIndex || 0 < _this.props.imageUrls.length - 1) {
_this.loadImage((_this.state.currentShowIndex || 0) + 1);
}
}
else if (offsetXRTL > 0) {
if (_this.state.currentShowIndex || 0 > 0) {
_this.loadImage((_this.state.currentShowIndex || 0) - 1);
}
}
};
/**
* 手势结束,但是没有取消浏览大图
*/
_this.handleResponderRelease = function (vx) {
if (vx === void 0) { vx = 0; }
var vxRTL = react_native_1.I18nManager.isRTL ? -vx : vx;
var isLeftMove = react_native_1.I18nManager.isRTL
? _this.positionXNumber - _this.standardPositionX < -(_this.props.flipThreshold || 0)
: _this.positionXNumber - _this.standardPositionX > (_this.props.flipThreshold || 0);
var isRightMove = react_native_1.I18nManager.isRTL
? _this.positionXNumber - _this.standardPositionX > (_this.props.flipThreshold || 0)
: _this.positionXNumber - _this.standardPositionX < -(_this.props.flipThreshold || 0);
if (vxRTL > 0.7) {
// 上一张
_this.goBack.call(_this);
// 这里可能没有触发溢出滚动,为了防止图片不被加载,调用加载图片
if (_this.state.currentShowIndex || 0 > 0) {
_this.loadImage((_this.state.currentShowIndex || 0) - 1);
}
return;
}
else if (vxRTL < -0.7) {
// 下一张
_this.goNext.call(_this);
if (_this.state.currentShowIndex || 0 < _this.props.imageUrls.length - 1) {
_this.loadImage((_this.state.currentShowIndex || 0) + 1);
}
return;
}
if (isLeftMove) {
// 上一张
_this.goBack.call(_this);
}
else if (isRightMove) {
// 下一张
_this.goNext.call(_this);
return;
}
else {
// 回到之前的位置
_this.resetPosition.call(_this);
return;
}
};
/**
* 到上一张
*/
_this.goBack = function () {
if (_this.state.currentShowIndex === 0) {
// 回到之前的位置
_this.resetPosition.call(_this);
return;
}
_this.positionXNumber = !react_native_1.I18nManager.isRTL
? _this.standardPositionX + _this.width
: _this.standardPositionX - _this.width;
_this.standardPositionX = _this.positionXNumber;
react_native_1.Animated.timing(_this.positionX, {
toValue: _this.positionXNumber,
duration: _this.props.pageAnimateTime
}).start();
var nextIndex = (_this.state.currentShowIndex || 0) - 1;
_this.setState({
currentShowIndex: nextIndex
}, function () {
if (_this.props.onChange) {
_this.props.onChange(_this.state.currentShowIndex);
}
});
};
/**
* 到下一张
*/
_this.goNext = function () {
if (_this.state.currentShowIndex === _this.props.imageUrls.length - 1) {
// 回到之前的位置
_this.resetPosition.call(_this);
return;
}
_this.positionXNumber = !react_native_1.I18nManager.isRTL
? _this.standardPositionX - _this.width
: _this.standardPositionX + _this.width;
_this.standardPositionX = _this.positionXNumber;
react_native_1.Animated.timing(_this.positionX, {
toValue: _this.positionXNumber,
duration: _this.props.pageAnimateTime
}).start();
var nextIndex = (_this.state.currentShowIndex || 0) + 1;
_this.setState({
currentShowIndex: nextIndex
}, function () {
if (_this.props.onChange) {
_this.props.onChange(_this.state.currentShowIndex);
}
});
};
/**
* 长按
*/
_this.handleLongPress = function (image) {
if (_this.props.saveToLocalByLongPress) {
// 出现保存到本地的操作框
_this.setState({ isShowMenu: true });
}
if (_this.props.onLongPress) {
_this.props.onLongPress(image);
}
};
/**
* 单击
*/
_this.handleClick = function () {
if (_this.props.onClick) {
_this.props.onClick(_this.handleCancel, _this.state.currentShowIndex);
}
};
/**
* 双击
*/
_this.handleDoubleClick = function () {
if (_this.props.onDoubleClick) {
_this.props.onDoubleClick(_this.handleCancel);
}
};
/**
* 退出
*/
_this.handleCancel = function () {
_this.hasLayout = false;
if (_this.props.onCancel) {
_this.props.onCancel();
}
};
/**
* 完成布局
*/
_this.handleLayout = function (event) {
if (event.nativeEvent.layout.width !== _this.width) {
_this.hasLayout = true;
_this.width = event.nativeEvent.layout.width;
_this.height = event.nativeEvent.layout.height;
_this.styles = image_viewer_style_1.default(_this.width, _this.height, _this.props.backgroundColor || 'transparent');
// 强制刷新
_this.forceUpdate();
_this.jumpToCurrentImage();
}
};
/**
* 保存当前图片到本地相册
*/
_this.saveToLocal = function () {
if (!_this.props.onSave) {
react_native_1.CameraRoll.saveToCameraRoll(_this.props.imageUrls[_this.state.currentShowIndex || 0].url);
_this.props.onSaveToCamera(_this.state.currentShowIndex);
}
else {
_this.props.onSave(_this.props.imageUrls[_this.state.currentShowIndex || 0].url);
}
_this.setState({ isShowMenu: false });
};
_this.handleLeaveMenu = function () {
_this.setState({ isShowMenu: false });
};
_this.handleSwipeDown = function () {
if (_this.props.onSwipeDown) {
_this.props.onSwipeDown();
}
_this.handleCancel();
};
return _this;
}
ImageViewer.prototype.componentWillMount = function () {
this.init(this.props);
};
ImageViewer.prototype.componentWillReceiveProps = function (nextProps) {
var _this = this;
if (nextProps.index !== this.state.currentShowIndex) {
this.setState({
currentShowIndex: nextProps.index
}, function () {
// 立刻预加载要看的图
_this.loadImage(nextProps.index || 0);
_this.jumpToCurrentImage();
// 显示动画
react_native_1.Animated.timing(_this.fadeAnim, {
toValue: 1,
duration: 200
}).start();
});
}
};
/**
* props 有变化时执行
*/
ImageViewer.prototype.init = function (nextProps) {
var _this = this;
if (nextProps.imageUrls.length === 0) {
// 隐藏时候清空
this.fadeAnim.setValue(0);
return this.setState(new image_viewer_type_1.State());
}
// 给 imageSizes 塞入空数组
var imageSizes = [];
nextProps.imageUrls.forEach(function (imageUrl) {
imageSizes.push({
width: imageUrl.width || 0,
height: imageUrl.height || 0,
status: 'loading'
});
});
this.setState({
currentShowIndex: nextProps.index,
imageSizes: imageSizes
}, function () {
// 立刻预加载要看的图
_this.loadImage(nextProps.index || 0);
_this.jumpToCurrentImage();
// 显示动画
react_native_1.Animated.timing(_this.fadeAnim, {
toValue: 1,
duration: 200
}).start();
});
};
/**
* 调到当前看图位置
*/
ImageViewer.prototype.jumpToCurrentImage = function () {
// 跳到当前图的位置
this.positionXNumber = this.width * (this.state.currentShowIndex || 0) * (react_native_1.I18nManager.isRTL ? 1 : -1);
this.standardPositionX = this.positionXNumber;
this.positionX.setValue(this.positionXNumber);
};
/**
* 加载图片,主要是获取图片长与宽
*/
ImageViewer.prototype.loadImage = function (index) {
var _this = this;
if (!this.state.imageSizes[index]) {
return;
}
if (this.loadedIndex.has(index)) {
return;
}
this.loadedIndex.set(index, true);
var image = this.props.imageUrls[index];
var imageStatus = __assign({}, this.state.imageSizes[index]);
// 保存 imageSize
var saveImageSize = function () {
// 如果已经 success 了,就不做处理
if (_this.state.imageSizes[index] && _this.state.imageSizes[index].status !== 'loading') {
return;
}
var imageSizes = _this.state.imageSizes.slice();
imageSizes[index] = imageStatus;
_this.setState({ imageSizes: imageSizes });
};
if (this.state.imageSizes[index].status === 'success') {
// 已经加载过就不会加载了
return;
}
// 如果已经有宽高了,直接设置为 success
if (this.state.imageSizes[index].width > 0 && this.state.imageSizes[index].height > 0) {
imageStatus.status = 'success';
saveImageSize();
return;
}
// 是否加载完毕了图片大小
var sizeLoaded = false;
// 是否加载完毕了图片
var imageLoaded = false;
// Tagged success if url is started with file:, or not set yet(for custom source.uri).
if (!image.url || image.url.startsWith("file:")) {
imageLoaded = true;
}
// 如果已知源图片宽高,直接设置为 success
if (image.width && image.height) {
if (this.props.enablePreload && imageLoaded === false) {
react_native_1.Image.prefetch(image.url);
}
imageStatus.width = image.width;
imageStatus.height = image.height;
imageStatus.status = 'success';
saveImageSize();
return;
}
react_native_1.Image.getSize(image.url, function (width, height) {
imageStatus.width = width;
imageStatus.height = height;
imageStatus.status = 'success';
saveImageSize();
}, function () {
try {
var data = react_native_1.Image.resolveAssetSource(image.props.source);
imageStatus.width = data.width;
imageStatus.height = data.height;
imageStatus.status = 'success';
saveImageSize();
}
catch (newError) {
// Give up..
imageStatus.status = 'fail';
}
});
};
/**
* 回到原位
*/
ImageViewer.prototype.resetPosition = function () {
this.positionXNumber = this.standardPositionX;
react_native_1.Animated.timing(this.positionX, {
toValue: this.standardPositionX,
duration: 150
}).start();
};
/**
* 获得整体内容
*/
ImageViewer.prototype.getContent = function () {
var _this = this;
// 获得屏幕宽高
var screenWidth = this.width;
var screenHeight = this.height;
var ImageElements = this.props.imageUrls.map(function (image, index) {
if ((_this.state.currentShowIndex || 0) > index + 1 || (_this.state.currentShowIndex || 0) < index - 1) {
return <react_native_1.View key={index} style={{ width: screenWidth, height: screenHeight }}/>;
}
if (!_this.handleLongPressWithIndex.has(index)) {
_this.handleLongPressWithIndex.set(index, _this.handleLongPress.bind(_this, image));
}
var width = _this.state.imageSizes[index] && _this.state.imageSizes[index].width;
var height = _this.state.imageSizes[index] && _this.state.imageSizes[index].height;
var imageInfo = _this.state.imageSizes[index];
if (!imageInfo || !imageInfo.status) {
return <react_native_1.View key={index} style={{ width: screenWidth, height: screenHeight }}/>;
}
// 如果宽大于屏幕宽度,整体缩放到宽度是屏幕宽度
if (width > screenWidth) {
var widthPixel = screenWidth / width;
width *= widthPixel;
height *= widthPixel;
}
// 如果此时高度还大于屏幕高度,整体缩放到高度是屏幕高度
if (height > screenHeight) {
var HeightPixel = screenHeight / height;
width *= HeightPixel;
height *= HeightPixel;
}
var Wrapper = function (_a) {
var children = _a.children, others = __rest(_a, ["children"]);
return (<react_native_image_pan_zoom_1.default cropWidth={_this.width} cropHeight={_this.height} maxOverflow={_this.props.maxOverflow} horizontalOuterRangeOffset={_this.handleHorizontalOuterRangeOffset} responderRelease={_this.handleResponderRelease} onLongPress={_this.handleLongPressWithIndex.get(index)} onClick={_this.handleClick} onDoubleClick={_this.handleDoubleClick} enableSwipeDown={_this.props.enableSwipeDown} onSwipeDown={_this.handleSwipeDown} pinchToZoom={_this.props.enableImageZoom} enableDoubleClickZoom={_this.props.enableImageZoom} doubleClickInterval={_this.props.doubleClickInterval} {...others}>
{children}
</react_native_image_pan_zoom_1.default>);
};
switch (imageInfo.status) {
case 'loading':
return (<Wrapper key={index} style={__assign({}, _this.styles.modalContainer, _this.styles.loadingContainer)} imageWidth={screenWidth} imageHeight={screenHeight}>
<react_native_1.View style={_this.styles.loadingContainer}>{_this.props.loadingRender()}</react_native_1.View>
</Wrapper>);
case 'success':
if (!image.props) {
image.props = {};
}
if (!image.props.style) {
image.props.style = {};
}
image.props.style = __assign({}, _this.styles.imageStyle, image.props.style, { width: width,
height: height });
if (typeof image.props.source === 'number') {
// source = require(..), doing nothing
}
else {
if (!image.props.source) {
image.props.source = {};
}
image.props.source = __assign({ uri: image.url }, image.props.source);
}
if (_this.props.enablePreload) {
_this.preloadImage(_this.state.currentShowIndex || 0);
}
return (<react_native_image_pan_zoom_1.default key={index} ref={function (el) { return (_this.imageRefs[index] = el); }} cropWidth={_this.width} cropHeight={_this.height} maxOverflow={_this.props.maxOverflow} horizontalOuterRangeOffset={_this.handleHorizontalOuterRangeOffset} responderRelease={_this.handleResponderRelease} onLongPress={_this.handleLongPressWithIndex.get(index)} onClick={_this.handleClick} onDoubleClick={_this.handleDoubleClick} imageWidth={width} imageHeight={height} enableSwipeDown={_this.props.enableSwipeDown} onSwipeDown={_this.handleSwipeDown} pinchToZoom={_this.props.enableImageZoom} enableDoubleClickZoom={_this.props.enableImageZoom} doubleClickInterval={_this.props.doubleClickInterval}>
{_this.props.renderImage(image.props)}
</react_native_image_pan_zoom_1.default>);
case 'fail':
return (<Wrapper key={index} style={_this.styles.modalContainer} imageWidth={_this.props.failImageSource ? _this.props.failImageSource.width : screenWidth} imageHeight={_this.props.failImageSource ? _this.props.failImageSource.height : screenHeight}>
{_this.props.failImageSource &&
_this.props.renderImage({
source: {
uri: _this.props.failImageSource.url
},
style: {
width: _this.props.failImageSource.width,
height: _this.props.failImageSource.height
}
})}
</Wrapper>);
}
});
return (<react_native_1.Animated.View style={{ zIndex: 9 }}>
<react_native_1.Animated.View style={__assign({}, this.styles.container, { opacity: this.fadeAnim })}>
{this.props.renderHeader(this.state.currentShowIndex)}
<react_native_1.View style={this.styles.arrowLeftContainer}>
<react_native_1.TouchableWithoutFeedback onPress={this.goBack}>
<react_native_1.View>{this.props.renderArrowLeft()}</react_native_1.View>
</react_native_1.TouchableWithoutFeedback>
</react_native_1.View>
<react_native_1.View style={this.styles.arrowRightContainer}>
<react_native_1.TouchableWithoutFeedback onPress={this.goNext}>
<react_native_1.View>{this.props.renderArrowRight()}</react_native_1.View>
</react_native_1.TouchableWithoutFeedback>
</react_native_1.View>
<react_native_1.Animated.View style={__assign({}, this.styles.moveBox, { transform: [{ translateX: this.positionX }], width: this.width * this.props.imageUrls.length })}>
{ImageElements}
</react_native_1.Animated.View>
{this.props.renderIndicator((this.state.currentShowIndex || 0) + 1, this.props.imageUrls.length)}
{this.props.imageUrls[this.state.currentShowIndex || 0] &&
this.props.imageUrls[this.state.currentShowIndex || 0].originSizeKb &&
this.props.imageUrls[this.state.currentShowIndex || 0].originUrl && (<react_native_1.View style={this.styles.watchOrigin}>
<react_native_1.TouchableOpacity style={this.styles.watchOriginTouchable}>
<react_native_1.Text style={this.styles.watchOriginText}>查看原图(2M)</react_native_1.Text>
</react_native_1.TouchableOpacity>
</react_native_1.View>)}
<react_native_1.View style={[{ bottom: 0, position: 'absolute', zIndex: 9 }, this.props.footerContainerStyle]}>
{this.props.renderFooter(this.state.currentShowIndex)}
</react_native_1.View>
</react_native_1.Animated.View>
</react_native_1.Animated.View>);
};
ImageViewer.prototype.getMenu = function () {
if (!this.state.isShowMenu) {
return null;
}
return (<react_native_1.View style={this.styles.menuContainer}>
<react_native_1.View style={this.styles.menuShadow}/>
<react_native_1.View style={this.styles.menuContent}>
<react_native_1.TouchableHighlight underlayColor="#F2F2F2" onPress={this.saveToLocal} style={this.styles.operateContainer}>
<react_native_1.Text style={this.styles.operateText}>{this.props.menuContext.saveToLocal}</react_native_1.Text>
</react_native_1.TouchableHighlight>
<react_native_1.TouchableHighlight underlayColor="#F2F2F2" onPress={this.handleLeaveMenu} style={this.styles.operateContainer}>
<react_native_1.Text style={this.styles.operateText}>{this.props.menuContext.cancel}</react_native_1.Text>
</react_native_1.TouchableHighlight>
</react_native_1.View>
</react_native_1.View>);
};
ImageViewer.prototype.render = function () {
var childs = null;
childs = (<react_native_1.View>
{this.getContent()}
{this.getMenu()}
</react_native_1.View>);
return (<react_native_1.View onLayout={this.handleLayout} style={__assign({ flex: 1, overflow: 'hidden' }, this.props.style)}>
{childs}
</react_native_1.View>);
};
ImageViewer.defaultProps = new image_viewer_type_1.Props();
return ImageViewer;
}(React.Component));
exports.default = ImageViewer;
//# sourceMappingURL=image-viewer.component.js.map