mirror of
https://github.com/infeng/react-viewer.git
synced 2025-12-08 17:36:40 +00:00
Merge branch 'master' into master
This commit is contained in:
commit
4c58cabe54
48
README.md
48
README.md
@ -46,20 +46,44 @@ class App extends React.Component<any, any> {
|
||||
|
||||
## Props
|
||||
|
||||
| props | type | default | description | required |
|
||||
|--------------|--------------|---------|-----------------------------|----------|
|
||||
| visible | string | false | Viewer visible | true |
|
||||
| onClose | function | - | Specify a function that will be called when Visible close | true |
|
||||
| images | [ImageDecorator](#imagedecorator)[] | [] | image source array | true |
|
||||
| activeIndex | number | 0 | active image index | false |
|
||||
| zIndex | number | 1000 | Viewer css z-index | false |
|
||||
| container | HTMLElement | null | set parent node(inline mode) | false |
|
||||
| drag | boolean | true | whether to drag image | false |
|
||||
| attribute | boolean | true | whether to show image attribute | false |
|
||||
| zoomable | boolean | true | whether to show 'zoom' button | false |
|
||||
| rotatable | boolean | true | whether to show 'rotate' button | false |
|
||||
| scalable | boolean | true | whether to show 'scale' button | false |
|
||||
| onMaskClick | (e) => void | - | callback function when mask is clicked | false |
|
||||
| downloadable | boolean | false | whether to show 'download' | false |
|
||||
| noClose | boolean | false | to not render close button | false |
|
||||
| noNavbar | boolean | false | to not render the navbar | false |
|
||||
| noToolbar | boolean | false | to not render the toolbar | false |
|
||||
| noImgDetails | boolean | false | to not render image detail (WxH) | false |
|
||||
| noFooter | boolean | false | to not render the entire footer | false |
|
||||
| changeable | boolean | true | wheather to show change button | false |
|
||||
| customToolbar | (defaultToolbarConfigs: [ToolbarConfig](#toolbarconfig)[]) => ToolbarConfig[] | - | customer toolbar | false |
|
||||
|
||||
### ImageDecorator
|
||||
|
||||
| props | type | default | description | required |
|
||||
|-------------|--------------|---------|-----------------------------|----------|
|
||||
| visible | string | false | Viewer visible | true |
|
||||
| onClose | function | - | Specify a function that will be called when Visible close | true |
|
||||
| images | {src: string, alt: string}[] | [] | image source array | true |
|
||||
| activeIndex | number | 0 | active image index | false |
|
||||
| zIndex | number | 1000 | Viewer css z-index | false |
|
||||
| container | HTMLElement | null | set parent node(inline mode) | false |
|
||||
| drag | boolean | true | whether to drag image | false |
|
||||
| attribute | boolean | true | whether to show image attribute | false |
|
||||
| zoomable | boolean | true | whether to show 'zoom' buttom | false |
|
||||
| rotatable | boolean | true | whether to show 'rotate' button | false |
|
||||
| scalable | boolean | true | whether to show 'scale' button | false |
|
||||
| onMaskClick | (e) => void | - | callback function when mask is clicked |
|
||||
| src | string | - | image source | true |
|
||||
| alt | string | - | image description | false |
|
||||
| downloadUrl | string | - | image downlaod url | false |
|
||||
|
||||
### ToolbarConfig
|
||||
|
||||
| props | type | default | description | required |
|
||||
|-------------|--------------|---------|-----------------------------|----------|
|
||||
| key | string | - | tool key | true |
|
||||
| render | React.ReactNode | - | tool render | false |
|
||||
| onClick | function | - | callback function when action is clicked | false |
|
||||
|
||||
## Keyboard support
|
||||
|
||||
|
||||
@ -47,15 +47,19 @@ class App extends React.Component<any, Partial<State>> {
|
||||
let images = [{
|
||||
src: img,
|
||||
alt: 'lake',
|
||||
downloadUrl: '',
|
||||
}, {
|
||||
src: img2,
|
||||
alt: 'mountain',
|
||||
downloadUrl: '',
|
||||
}, {
|
||||
src: img3,
|
||||
alt: '',
|
||||
downloadUrl: '',
|
||||
}, {
|
||||
src: img4,
|
||||
alt: '',
|
||||
downloadUrl: '',
|
||||
}];
|
||||
|
||||
let inline = this.state.mode === 'inline';
|
||||
@ -125,8 +129,17 @@ class App extends React.Component<any, Partial<State>> {
|
||||
onClose={() => { this.setState({ visible: false }); } }
|
||||
images={images}
|
||||
activeIndex={this.state.activeIndex}
|
||||
attribute={false}
|
||||
container={inline ? this.container : null}
|
||||
downloadable
|
||||
customToolbar={(toolbars) => {
|
||||
return toolbars.concat([{
|
||||
key: 'test',
|
||||
render: <div>C</div>,
|
||||
onClick: (activeImage) => {
|
||||
console.log(activeImage);
|
||||
},
|
||||
}]);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="footer">
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
<!DOCTYPE html> <html> <head> <title>react-viewer</title> <meta charset="utf-8"> <link href="/react-viewer/index.css?55d05ece8bc68335253e" rel="stylesheet"></head> <body> <div id="root"></div> <script type="text/javascript" src="/react-viewer/index.js?55d05ece8bc68335253e"></script></body> </html>
|
||||
<!DOCTYPE html> <html> <head> <title>react-viewer</title> <meta charset="utf-8"> <link href="/react-viewer/index.css?e520ae9314a8c80503cc" rel="stylesheet"></head> <body> <div id="root"></div> <script type="text/javascript" src="/react-viewer/index.js?e520ae9314a8c80503cc"></script></body> </html>
|
||||
File diff suppressed because one or more lines are too long
2
package-lock.json
generated
2
package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-viewer",
|
||||
"version": "2.3.4",
|
||||
"version": "2.4.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-viewer",
|
||||
"version": "2.3.4",
|
||||
"version": "2.4.0",
|
||||
"description": "react image viewer",
|
||||
"main": "dist/index",
|
||||
"scripts": {
|
||||
|
||||
@ -11,6 +11,7 @@ export enum ActionType {
|
||||
close = 8,
|
||||
scaleX = 9,
|
||||
scaleY = 10,
|
||||
download = 11,
|
||||
}
|
||||
|
||||
export interface IconProps {
|
||||
|
||||
@ -51,12 +51,10 @@ export default class Viewer extends React.Component<ViewerProps, any> {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.props.visible) {
|
||||
if (this.props.visible && this.props.onClose) {
|
||||
this.props.onClose();
|
||||
this.removeViewer();
|
||||
} else {
|
||||
this.removeViewer();
|
||||
}
|
||||
this.removeViewer();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: ViewerProps) {
|
||||
|
||||
@ -38,12 +38,6 @@ export default class ViewerCanvas extends React.Component<ViewerCanvasProps, Vie
|
||||
mouseX: 0,
|
||||
mouseY: 0,
|
||||
};
|
||||
|
||||
this.handleMouseScroll = this.handleMouseScroll.bind(this);
|
||||
this.handleMouseDown = this.handleMouseDown.bind(this);
|
||||
this.bindEvent = this.bindEvent.bind(this);
|
||||
this.handleMouseMove = this.handleMouseMove.bind(this);
|
||||
this.handleMouseUp = this.handleMouseUp.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -61,7 +55,7 @@ export default class ViewerCanvas extends React.Component<ViewerCanvasProps, Vie
|
||||
this.handleMouseDown(e);
|
||||
}
|
||||
|
||||
handleMouseDown(e) {
|
||||
handleMouseDown = (e) => {
|
||||
if (!this.props.visible || !this.props.drag) {
|
||||
return;
|
||||
}
|
||||
@ -74,7 +68,7 @@ export default class ViewerCanvas extends React.Component<ViewerCanvasProps, Vie
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseMove(e) {
|
||||
handleMouseMove = (e) => {
|
||||
if (this.state.isMouseDown) {
|
||||
let diffX = e.clientX - this.state.mouseX;
|
||||
let diffY = e.clientY - this.state.mouseY;
|
||||
@ -86,13 +80,14 @@ export default class ViewerCanvas extends React.Component<ViewerCanvasProps, Vie
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseUp(e) {
|
||||
handleMouseUp = (e) => {
|
||||
this.setState({
|
||||
isMouseDown: false,
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseScroll(e) {
|
||||
handleMouseScroll = (e) => {
|
||||
e.preventDefault();
|
||||
let direct: 0 | 1 | -1 = 0;
|
||||
if (e.wheelDelta) {
|
||||
direct = e.wheelDelta > 0 ? 1 : -1;
|
||||
@ -102,11 +97,16 @@ export default class ViewerCanvas extends React.Component<ViewerCanvasProps, Vie
|
||||
if (direct !== 0) {
|
||||
let x = e.clientX;
|
||||
let y = e.clientY;
|
||||
if (this.props.container) {
|
||||
const containerRect = this.props.container.getBoundingClientRect();
|
||||
x -= containerRect.left;
|
||||
y -= containerRect.top;
|
||||
}
|
||||
this.props.onZoom(x, y, direct, .05);
|
||||
}
|
||||
}
|
||||
|
||||
bindEvent(remove?: boolean) {
|
||||
bindEvent = (remove?: boolean) => {
|
||||
let funcName = 'addEventListener';
|
||||
if (remove) {
|
||||
funcName = 'removeEventListener';
|
||||
|
||||
@ -2,8 +2,8 @@ import * as React from 'react';
|
||||
import './style/index.less';
|
||||
import ViewerCanvas from './ViewerCanvas';
|
||||
import ViewerNav from './ViewerNav';
|
||||
import ViewerToolbar from './ViewerToolbar';
|
||||
import ViewerProps, { ImageDecorator } from './ViewerProps';
|
||||
import ViewerToolbar, { defaultToolbars } from './ViewerToolbar';
|
||||
import ViewerProps, { ImageDecorator, ToolbarConfig } from './ViewerProps';
|
||||
import Icon, { ActionType } from './Icon';
|
||||
|
||||
function noop() {}
|
||||
@ -40,6 +40,8 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
|
||||
rotatable: true,
|
||||
scalable: true,
|
||||
onMaskClick: noop,
|
||||
changeable: true,
|
||||
customToolbar: (toolbars) => toolbars,
|
||||
};
|
||||
|
||||
private prefixCls: string;
|
||||
@ -69,17 +71,6 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
|
||||
loading: false,
|
||||
};
|
||||
|
||||
this.handleChangeImg = this.handleChangeImg.bind(this);
|
||||
this.handleChangeImgState = this.handleChangeImgState.bind(this);
|
||||
this.handleAction = this.handleAction.bind(this);
|
||||
this.handleResize = this.handleResize.bind(this);
|
||||
this.handleZoom = this.handleZoom.bind(this);
|
||||
this.handleRotate = this.handleRotate.bind(this);
|
||||
this.handleKeydown = this.handleKeydown.bind(this);
|
||||
this.handleScaleX = this.handleScaleX.bind(this);
|
||||
this.handleScaleY = this.handleScaleY.bind(this);
|
||||
this.getImageCenterXY = this.getImageCenterXY.bind(this);
|
||||
|
||||
this.setContainerWidthHeight();
|
||||
this.footerHeight = 84;
|
||||
}
|
||||
@ -93,7 +84,7 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
|
||||
}
|
||||
}
|
||||
|
||||
handleClose(e) {
|
||||
handleClose = (e) => {
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
@ -119,20 +110,24 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
(this.refs['viewerCore'] as HTMLDivElement).addEventListener('transitionend', this.handleTransitionEnd, false);
|
||||
(this.refs['viewerCore'] as HTMLDivElement).addEventListener(
|
||||
'transitionend',
|
||||
this.handleTransitionEnd,
|
||||
false
|
||||
);
|
||||
this.startVisible(this.state.activeIndex);
|
||||
}
|
||||
|
||||
getImgWidthHeight(imgWidth, imgHeight) {
|
||||
let width = 0;
|
||||
let height = 0;
|
||||
let maxWidth = this.containerWidth * .8;
|
||||
let maxHeight = (this.containerHeight - this.footerHeight) * .8;
|
||||
let maxWidth = this.containerWidth * 0.8;
|
||||
let maxHeight = (this.containerHeight - this.footerHeight) * 0.8;
|
||||
width = Math.min(maxWidth, imgWidth);
|
||||
height = (width / imgWidth) * imgHeight;
|
||||
height = width / imgWidth * imgHeight;
|
||||
if (height > maxHeight) {
|
||||
height = maxHeight;
|
||||
width = (height / imgHeight) * imgWidth;
|
||||
width = height / imgHeight * imgWidth;
|
||||
}
|
||||
return [width, height];
|
||||
}
|
||||
@ -151,13 +146,13 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
|
||||
width: 0,
|
||||
height: 0,
|
||||
left: 0,
|
||||
top: 0,
|
||||
top: 0,
|
||||
rotate: 0,
|
||||
scaleX: 1,
|
||||
scaleY: 1,
|
||||
loading: true,
|
||||
});
|
||||
}else {
|
||||
} else {
|
||||
this.setState({
|
||||
activeIndex: activeIndex,
|
||||
loading: true,
|
||||
@ -176,16 +171,16 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
|
||||
let imgCenterXY = this.getImageCenterXY();
|
||||
this.handleZoom(imgCenterXY.x, imgCenterXY.y, 1, 1);
|
||||
}, 50);
|
||||
}else {
|
||||
const [ width, height ] = this.getImgWidthHeight(imgWidth, imgHeight);
|
||||
let left = ( this.containerWidth - width ) / 2;
|
||||
} else {
|
||||
const [width, height] = this.getImgWidthHeight(imgWidth, imgHeight);
|
||||
let left = (this.containerWidth - width) / 2;
|
||||
let top = (this.containerHeight - height - this.footerHeight) / 2;
|
||||
this.setState({
|
||||
activeIndex: activeIndex,
|
||||
width: width,
|
||||
height: height,
|
||||
left: left,
|
||||
top: top,
|
||||
top: top,
|
||||
imageWidth: imgWidth,
|
||||
imageHeight: imgHeight,
|
||||
loading: false,
|
||||
@ -205,7 +200,7 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
|
||||
};
|
||||
}
|
||||
|
||||
handleChangeImg(newIndex: number) {
|
||||
handleChangeImg = (newIndex: number) => {
|
||||
// let imgCenterXY2 = this.getImageCenterXY();
|
||||
// this.handleZoom(imgCenterXY2.x, imgCenterXY2.y, -1, 1);
|
||||
// setTimeout(() => {
|
||||
@ -214,7 +209,7 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
|
||||
this.loadImg(newIndex);
|
||||
}
|
||||
|
||||
handleChangeImgState(width, height, top, left) {
|
||||
handleChangeImgState = (width, height, top, left) => {
|
||||
this.setState({
|
||||
width: width,
|
||||
height: height,
|
||||
@ -223,7 +218,7 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
|
||||
});
|
||||
}
|
||||
|
||||
handleAction(type: ActionType) {
|
||||
handleDefaultAction = (type: ActionType) => {
|
||||
switch (type) {
|
||||
case ActionType.prev:
|
||||
if (this.state.activeIndex - 1 >= 0) {
|
||||
@ -237,11 +232,11 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
|
||||
break;
|
||||
case ActionType.zoomIn:
|
||||
let imgCenterXY = this.getImageCenterXY();
|
||||
this.handleZoom(imgCenterXY.x, imgCenterXY.y, 1, .05);
|
||||
this.handleZoom(imgCenterXY.x, imgCenterXY.y, 1, 0.05);
|
||||
break;
|
||||
case ActionType.zoomOut:
|
||||
let imgCenterXY2 = this.getImageCenterXY();
|
||||
this.handleZoom(imgCenterXY2.x, imgCenterXY2.y, -1, .05);
|
||||
this.handleZoom(imgCenterXY2.x, imgCenterXY2.y, -1, 0.05);
|
||||
break;
|
||||
case ActionType.rotateLeft:
|
||||
this.handleRotate();
|
||||
@ -258,24 +253,43 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
|
||||
case ActionType.scaleY:
|
||||
this.handleScaleY(-1);
|
||||
break;
|
||||
case ActionType.download:
|
||||
this.handleDownload();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handleScaleX(newScale: 1 | -1) {
|
||||
handleAction = (config: ToolbarConfig) => {
|
||||
this.handleDefaultAction(config.actionType);
|
||||
|
||||
if (config.onClick) {
|
||||
const activeImage = this.getActiveImage();
|
||||
config.onClick(activeImage);
|
||||
}
|
||||
}
|
||||
|
||||
handleDownload = () => {
|
||||
const activeImage = this.getActiveImage();
|
||||
if (activeImage.downloadUrl) {
|
||||
location.href = activeImage.downloadUrl;
|
||||
}
|
||||
};
|
||||
|
||||
handleScaleX = (newScale: 1 | -1) => {
|
||||
this.setState({
|
||||
scaleX: this.state.scaleX * newScale,
|
||||
});
|
||||
}
|
||||
|
||||
handleScaleY(newScale: 1 | -1) {
|
||||
handleScaleY = (newScale: 1 | -1) => {
|
||||
this.setState({
|
||||
scaleY: this.state.scaleY * newScale,
|
||||
});
|
||||
}
|
||||
|
||||
handleZoom(targetX, targetY, direct, scale) {
|
||||
handleZoom = (targetX, targetY, direct, scale) => {
|
||||
let imgCenterXY = this.getImageCenterXY();
|
||||
let diffX = targetX - imgCenterXY.x;
|
||||
let diffY = targetY - imgCenterXY.y;
|
||||
@ -288,14 +302,17 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
|
||||
let scaleX = 0;
|
||||
let scaleY = 0;
|
||||
if (this.state.width === 0) {
|
||||
const [ imgWidth, imgHeight ] = this.getImgWidthHeight(this.state.imageWidth, this.state.imageHeight);
|
||||
const [imgWidth, imgHeight] = this.getImgWidthHeight(
|
||||
this.state.imageWidth,
|
||||
this.state.imageHeight
|
||||
);
|
||||
reset = true;
|
||||
left = (this.containerWidth - imgWidth) / 2;
|
||||
top = (this.containerHeight - this.footerHeight - imgHeight) / 2;
|
||||
width = this.state.width + imgWidth;
|
||||
height = this.state.height + imgHeight;
|
||||
scaleX = scaleY = 1;
|
||||
}else {
|
||||
} else {
|
||||
let directX = this.state.scaleX > 0 ? 1 : -1;
|
||||
let directY = this.state.scaleY > 0 ? 1 : -1;
|
||||
scaleX = this.state.scaleX + scale * direct * directX;
|
||||
@ -319,30 +336,30 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
|
||||
});
|
||||
}
|
||||
|
||||
getImageCenterXY() {
|
||||
getImageCenterXY = () => {
|
||||
return {
|
||||
x: (this.state.left + this.state.width / 2),
|
||||
y: (this.state.top + this.state.height / 2),
|
||||
x: this.state.left + this.state.width / 2,
|
||||
y: this.state.top + this.state.height / 2,
|
||||
};
|
||||
}
|
||||
|
||||
handleRotate(isRight: boolean = false) {
|
||||
handleRotate = (isRight: boolean = false) => {
|
||||
this.setState({
|
||||
rotate: this.state.rotate + 90 * (isRight ? 1 : -1),
|
||||
});
|
||||
}
|
||||
|
||||
handleResize() {
|
||||
handleResize = () => {
|
||||
this.setContainerWidthHeight();
|
||||
if (this.props.visible) {
|
||||
const [ width, height ] = this.getImgWidthHeight(this.state.imageWidth, this.state.imageHeight);
|
||||
let left = ( this.containerWidth - width ) / 2;
|
||||
const [width, height] = this.getImgWidthHeight(this.state.imageWidth, this.state.imageHeight);
|
||||
let left = (this.containerWidth - width) / 2;
|
||||
let top = (this.containerHeight - height - this.footerHeight) / 2;
|
||||
this.setState({
|
||||
width: width,
|
||||
height: height,
|
||||
left: left,
|
||||
top: top,
|
||||
top: top,
|
||||
rotate: 0,
|
||||
scaleX: 1,
|
||||
scaleY: 1,
|
||||
@ -350,7 +367,7 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
|
||||
}
|
||||
}
|
||||
|
||||
handleKeydown(e) {
|
||||
handleKeydown = (e) => {
|
||||
let keyCode = e.keyCode || e.which || e.charCode;
|
||||
let isFeatrue = false;
|
||||
switch (keyCode) {
|
||||
@ -362,29 +379,29 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
|
||||
// key: ←
|
||||
case 37:
|
||||
if (e.ctrlKey) {
|
||||
this.handleAction(ActionType.rotateLeft);
|
||||
}else {
|
||||
this.handleAction(ActionType.prev);
|
||||
this.handleDefaultAction(ActionType.rotateLeft);
|
||||
} else {
|
||||
this.handleDefaultAction(ActionType.prev);
|
||||
}
|
||||
isFeatrue = true;
|
||||
break;
|
||||
// key: →
|
||||
case 39:
|
||||
if (e.ctrlKey) {
|
||||
this.handleAction(ActionType.rotateRight);
|
||||
}else {
|
||||
this.handleAction(ActionType.next);
|
||||
this.handleDefaultAction(ActionType.rotateRight);
|
||||
} else {
|
||||
this.handleDefaultAction(ActionType.next);
|
||||
}
|
||||
isFeatrue = true;
|
||||
break;
|
||||
// key: ↑
|
||||
case 38:
|
||||
this.handleAction(ActionType.zoomIn);
|
||||
this.handleDefaultAction(ActionType.zoomIn);
|
||||
isFeatrue = true;
|
||||
break;
|
||||
// key: ↓
|
||||
case 40:
|
||||
this.handleAction(ActionType.zoomOut);
|
||||
this.handleDefaultAction(ActionType.zoomOut);
|
||||
isFeatrue = true;
|
||||
break;
|
||||
// key: Ctrl + 1
|
||||
@ -402,14 +419,14 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
|
||||
}
|
||||
}
|
||||
|
||||
handleTransitionEnd = (e) => {
|
||||
handleTransitionEnd = e => {
|
||||
if (!this.state.transitionEnd || this.state.visibleStart) {
|
||||
this.setState({
|
||||
visibleStart: false,
|
||||
transitionEnd: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bindEvent(remove: boolean = false) {
|
||||
let funcName = 'addEventListener';
|
||||
@ -421,7 +438,11 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
|
||||
|
||||
componentWillUnmount() {
|
||||
this.bindEvent(true);
|
||||
(this.refs['viewerCore'] as HTMLDivElement).removeEventListener('transitionend', this.handleTransitionEnd, false);
|
||||
(this.refs['viewerCore'] as HTMLDivElement).removeEventListener(
|
||||
'transitionend',
|
||||
this.handleTransitionEnd,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: ViewerProps) {
|
||||
@ -435,7 +456,7 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
|
||||
this.containerWidth / 2,
|
||||
(this.containerHeight - this.footerHeight) / 2,
|
||||
-1,
|
||||
(this.state.scaleX > 0 ? 1 : -1) * this.state.scaleX - 0.11,
|
||||
(this.state.scaleX > 0 ? 1 : -1) * this.state.scaleX - 0.11
|
||||
);
|
||||
setTimeout(() => {
|
||||
document.body.style.overflow = '';
|
||||
@ -457,7 +478,22 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
|
||||
|
||||
handleCanvasMouseDown = e => {
|
||||
this.props.onMaskClick(e);
|
||||
}
|
||||
};
|
||||
|
||||
getActiveImage = () => {
|
||||
let activeImg: ImageDecorator = {
|
||||
src: '',
|
||||
alt: '',
|
||||
downloadUrl: '',
|
||||
};
|
||||
|
||||
let images = this.props.images || [];
|
||||
if (images.length > 0 && this.state.activeIndex >= 0) {
|
||||
activeImg = images[this.state.activeIndex];
|
||||
}
|
||||
|
||||
return activeImg;
|
||||
};
|
||||
|
||||
render() {
|
||||
let activeImg: ImageDecorator = {
|
||||
@ -482,10 +518,7 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
|
||||
viewerStryle.display = 'block';
|
||||
}
|
||||
if (this.state.visible && this.state.transitionEnd) {
|
||||
let images = this.props.images || [];
|
||||
if (images.length > 0 && this.state.activeIndex >= 0) {
|
||||
activeImg = images[this.state.activeIndex];
|
||||
}
|
||||
activeImg = this.getActiveImage();
|
||||
}
|
||||
|
||||
let className = `${this.prefixCls} ${this.prefixCls}-transition`;
|
||||
@ -494,59 +527,66 @@ export default class ViewerCore extends React.Component<ViewerProps, ViewerCoreS
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref="viewerCore"
|
||||
className={className}
|
||||
style={viewerStryle}
|
||||
>
|
||||
<div className={`${this.prefixCls}-mask`} style={{zIndex: zIndex}}></div>
|
||||
<div
|
||||
className={`${this.prefixCls}-close ${this.prefixCls}-btn`}
|
||||
onClick={this.handleClose.bind(this)}
|
||||
style={{zIndex: zIndex + 10}}
|
||||
>
|
||||
<Icon type={ActionType.close}/>
|
||||
</div>
|
||||
<div ref="viewerCore" className={className} style={viewerStryle}>
|
||||
<div className={`${this.prefixCls}-mask`} style={{ zIndex: zIndex }} />
|
||||
{this.props.noClose || (
|
||||
<div
|
||||
className={`${this.prefixCls}-close ${this.prefixCls}-btn`}
|
||||
onClick={this.handleClose}
|
||||
style={{ zIndex: zIndex + 10 }}
|
||||
>
|
||||
<Icon type={ActionType.close} />
|
||||
</div>
|
||||
)}
|
||||
<ViewerCanvas
|
||||
prefixCls={this.prefixCls}
|
||||
imgSrc={activeImg.src}
|
||||
visible={this.props.visible}
|
||||
width={this.state.width}
|
||||
height={this.state.height}
|
||||
top={this.state.top}
|
||||
left={this.state.left}
|
||||
rotate={this.state.rotate}
|
||||
onChangeImgState={this.handleChangeImgState}
|
||||
onResize={this.handleResize}
|
||||
onZoom={this.handleZoom}
|
||||
zIndex={zIndex + 5}
|
||||
scaleX={this.state.scaleX}
|
||||
scaleY={this.state.scaleY}
|
||||
loading={this.state.loading}
|
||||
drag={this.props.drag}
|
||||
container={this.props.container}
|
||||
onCanvasMouseDown={this.handleCanvasMouseDown}
|
||||
prefixCls={this.prefixCls}
|
||||
imgSrc={activeImg.src}
|
||||
visible={this.props.visible}
|
||||
width={this.state.width}
|
||||
height={this.state.height}
|
||||
top={this.state.top}
|
||||
left={this.state.left}
|
||||
rotate={this.state.rotate}
|
||||
onChangeImgState={this.handleChangeImgState}
|
||||
onResize={this.handleResize}
|
||||
onZoom={this.handleZoom}
|
||||
zIndex={zIndex + 5}
|
||||
scaleX={this.state.scaleX}
|
||||
scaleY={this.state.scaleY}
|
||||
loading={this.state.loading}
|
||||
drag={this.props.drag}
|
||||
container={this.props.container}
|
||||
onCanvasMouseDown={this.handleCanvasMouseDown}
|
||||
/>
|
||||
<div className={`${this.prefixCls}-footer`} style={{zIndex: zIndex + 5}}>
|
||||
<ViewerToolbar
|
||||
prefixCls={this.prefixCls}
|
||||
onAction={this.handleAction}
|
||||
alt={activeImg.alt}
|
||||
width={this.state.imageWidth}
|
||||
height={this.state.imageHeight}
|
||||
attribute={this.props.attribute}
|
||||
zoomable={this.props.zoomable}
|
||||
rotatable={this.props.rotatable}
|
||||
scalable={this.props.scalable}
|
||||
changeable={true}
|
||||
/>
|
||||
<ViewerNav
|
||||
prefixCls={this.prefixCls}
|
||||
images={this.props.images}
|
||||
activeIndex={this.state.activeIndex}
|
||||
onChangeImg={this.handleChangeImg}
|
||||
/>
|
||||
</div>
|
||||
{this.props.noFooter || (
|
||||
<div className={`${this.prefixCls}-footer`} style={{ zIndex: zIndex + 5 }}>
|
||||
{this.props.noToolbar || (
|
||||
<ViewerToolbar
|
||||
prefixCls={this.prefixCls}
|
||||
onAction={this.handleAction}
|
||||
alt={activeImg.alt}
|
||||
width={this.state.imageWidth}
|
||||
height={this.state.imageHeight}
|
||||
attribute={this.props.attribute}
|
||||
zoomable={this.props.zoomable}
|
||||
rotatable={this.props.rotatable}
|
||||
scalable={this.props.scalable}
|
||||
changeable={this.props.changeable}
|
||||
downloadable={this.props.downloadable}
|
||||
noImgDetails={this.props.noImgDetails}
|
||||
toolbars={this.props.customToolbar(defaultToolbars)}
|
||||
/>
|
||||
)}
|
||||
{this.props.noNavbar || (
|
||||
<ViewerNav
|
||||
prefixCls={this.prefixCls}
|
||||
images={this.props.images}
|
||||
activeIndex={this.state.activeIndex}
|
||||
onChangeImg={this.handleChangeImg}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ export default class ViewerNav extends React.Component<ViewerNavProps, any> {
|
||||
activeIndex: 0,
|
||||
};
|
||||
|
||||
handleChangeImg(newIndex) {
|
||||
handleChangeImg = (newIndex) => {
|
||||
if (this.props.activeIndex === newIndex) {
|
||||
return;
|
||||
}
|
||||
@ -33,7 +33,7 @@ export default class ViewerNav extends React.Component<ViewerNavProps, any> {
|
||||
<li
|
||||
key={index}
|
||||
className={index === this.props.activeIndex ? 'active' : ''}
|
||||
onClick={this.handleChangeImg.bind(this, index)}
|
||||
onClick={() => { this.handleChangeImg(index); }}
|
||||
>
|
||||
<img src={item.src} alt={item.alt} />
|
||||
</li>
|
||||
|
||||
@ -1,6 +1,14 @@
|
||||
export interface ImageDecorator {
|
||||
src: string;
|
||||
alt?: string;
|
||||
downloadUrl?: string;
|
||||
}
|
||||
|
||||
export interface ToolbarConfig {
|
||||
key: string;
|
||||
actionType?: number;
|
||||
render?: React.ReactNode;
|
||||
onClick?: (activeImage: ImageDecorator) => void;
|
||||
}
|
||||
|
||||
interface ViewerProps {
|
||||
@ -28,6 +36,29 @@ interface ViewerProps {
|
||||
scalable?: boolean;
|
||||
/** callback function when mask is clicked */
|
||||
onMaskClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
||||
/** 是否显示下载按钮 */
|
||||
downloadable?: boolean;
|
||||
|
||||
// no render close button
|
||||
noClose?: boolean;
|
||||
|
||||
// no render image details
|
||||
noImgDetails?: boolean;
|
||||
|
||||
// no render navbar
|
||||
noNavbar?: boolean;
|
||||
|
||||
// no render toolbar
|
||||
noToolbar?: boolean;
|
||||
|
||||
// no render footer
|
||||
noFooter?: boolean;
|
||||
|
||||
// wheather to show change button
|
||||
changeable?: boolean;
|
||||
|
||||
// custom toolbar
|
||||
customToolbar?: (toolbars: ToolbarConfig[]) => ToolbarConfig[];
|
||||
}
|
||||
|
||||
export default ViewerProps;
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import Icon, { ActionType } from './Icon';
|
||||
import { ToolbarConfig } from './ViewerProps';
|
||||
|
||||
export interface ViewerToolbarProps {
|
||||
prefixCls: string;
|
||||
onAction: (type: ActionType) => void;
|
||||
onAction: (config: ToolbarConfig) => void;
|
||||
alt: string;
|
||||
width: number;
|
||||
height: number;
|
||||
@ -12,6 +13,58 @@ export interface ViewerToolbarProps {
|
||||
rotatable: boolean;
|
||||
scalable: boolean;
|
||||
changeable: boolean;
|
||||
downloadable: boolean;
|
||||
noImgDetails: boolean;
|
||||
toolbars: ToolbarConfig[];
|
||||
}
|
||||
|
||||
export const defaultToolbars: ToolbarConfig[] = [
|
||||
{
|
||||
key: 'zoomIn',
|
||||
actionType: ActionType.zoomIn,
|
||||
},
|
||||
{
|
||||
key: 'zoomOut',
|
||||
actionType: ActionType.zoomOut,
|
||||
},
|
||||
{
|
||||
key: 'prev',
|
||||
actionType: ActionType.prev,
|
||||
},
|
||||
{
|
||||
key: 'reset',
|
||||
actionType: ActionType.reset,
|
||||
},
|
||||
{
|
||||
key: 'next',
|
||||
actionType: ActionType.next,
|
||||
},
|
||||
{
|
||||
key: 'rotateLeft',
|
||||
actionType: ActionType.rotateLeft,
|
||||
},
|
||||
{
|
||||
key: 'rotateRight',
|
||||
actionType: ActionType.rotateRight,
|
||||
},
|
||||
{
|
||||
key: 'scaleX',
|
||||
actionType: ActionType.scaleX,
|
||||
},
|
||||
{
|
||||
key: 'scaleY',
|
||||
actionType: ActionType.scaleY,
|
||||
},
|
||||
{
|
||||
key: 'download',
|
||||
actionType: ActionType.download,
|
||||
},
|
||||
];
|
||||
|
||||
function deleteToolbarFromKey(toolbars: ToolbarConfig[], keys: string[]) {
|
||||
const targetToolbar = toolbars.filter(item => keys.indexOf(item.key) < 0);
|
||||
|
||||
return targetToolbar;
|
||||
}
|
||||
|
||||
export default class ViewerToolbar extends React.Component<ViewerToolbarProps, any> {
|
||||
@ -20,89 +73,61 @@ export default class ViewerToolbar extends React.Component<ViewerToolbarProps, a
|
||||
super();
|
||||
}
|
||||
|
||||
handleAction(type: ActionType) {
|
||||
this.props.onAction(type);
|
||||
handleAction(config: ToolbarConfig) {
|
||||
this.props.onAction(config);
|
||||
}
|
||||
|
||||
renderAction = (config: ToolbarConfig) => {
|
||||
let content = null;
|
||||
// default toolbar
|
||||
if (typeof ActionType[config.actionType] !== 'undefined') {
|
||||
content = <Icon type={config.actionType}/>;
|
||||
}
|
||||
// extra toolbar
|
||||
if (config.render) {
|
||||
content = config.render;
|
||||
}
|
||||
return (
|
||||
<li
|
||||
key={config.key}
|
||||
className={`${this.props.prefixCls}-btn`}
|
||||
onClick={() => {this.handleAction(config);}}
|
||||
>
|
||||
{content}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
let attributeNode = this.props.attribute ? (
|
||||
<p className={`${this.props.prefixCls}-attribute`}>
|
||||
{`${this.props.alt}(${this.props.width} x ${this.props.height})`}
|
||||
{this.props.alt && `${this.props.alt}`}
|
||||
{this.props.noImgDetails || `(${this.props.width} x ${this.props.height})`}
|
||||
</p>
|
||||
) : null;
|
||||
let featureNodeArr = [];
|
||||
if (this.props.zoomable) {
|
||||
featureNodeArr = featureNodeArr.concat([
|
||||
<li
|
||||
key="zoomIn"
|
||||
className={`${this.props.prefixCls}-btn`}
|
||||
onClick={() => {this.handleAction(ActionType.zoomIn);}}>
|
||||
<Icon type={ActionType.zoomIn}/>
|
||||
</li>,
|
||||
<li
|
||||
key="zoomOut"
|
||||
className={`${this.props.prefixCls}-btn`}
|
||||
onClick={() => {this.handleAction(ActionType.zoomOut);}}>
|
||||
<Icon type={ActionType.zoomOut}/>
|
||||
</li>,
|
||||
]);
|
||||
let toolbars = this.props.toolbars;
|
||||
if (!this.props.zoomable) {
|
||||
toolbars = deleteToolbarFromKey(toolbars, ['zoomIn', 'zoomOut']);
|
||||
}
|
||||
if (this.props.changeable) {
|
||||
featureNodeArr = featureNodeArr.concat([
|
||||
<li
|
||||
key="prev"
|
||||
className={`${this.props.prefixCls}-btn`} onClick={() => {this.handleAction(ActionType.prev);}}>
|
||||
<Icon type={ActionType.prev}/>
|
||||
</li>,
|
||||
<li
|
||||
key="reset"
|
||||
className={`${this.props.prefixCls}-btn`} onClick={() => {this.handleAction(ActionType.reset);}}>
|
||||
<Icon type={ActionType.reset}/>
|
||||
</li>,
|
||||
<li
|
||||
key="next"
|
||||
className={`${this.props.prefixCls}-btn`} onClick={() => {this.handleAction(ActionType.next);}}>
|
||||
<Icon type={ActionType.next}/>
|
||||
</li>,
|
||||
]);
|
||||
if (!this.props.changeable) {
|
||||
toolbars = deleteToolbarFromKey(toolbars, ['prev', 'next']);
|
||||
}
|
||||
if (this.props.rotatable) {
|
||||
featureNodeArr = featureNodeArr.concat([
|
||||
<li
|
||||
key="rotateLeft"
|
||||
className={`${this.props.prefixCls}-btn`}
|
||||
onClick={() => {this.handleAction(ActionType.rotateLeft);}}>
|
||||
<Icon type={ActionType.rotateLeft}/>
|
||||
</li>,
|
||||
<li
|
||||
key="rotateRight"
|
||||
className={`${this.props.prefixCls}-btn`}
|
||||
onClick={() => {this.handleAction(ActionType.rotateRight);}}>
|
||||
<Icon type={ActionType.rotateRight}/>
|
||||
</li>,
|
||||
]);
|
||||
if (!this.props.rotatable) {
|
||||
toolbars = deleteToolbarFromKey(toolbars, ['rotateLeft', 'rotateRight']);
|
||||
}
|
||||
if (this.props.scalable) {
|
||||
featureNodeArr = featureNodeArr.concat([
|
||||
<li
|
||||
key="scaleX"
|
||||
className={`${this.props.prefixCls}-btn`}
|
||||
onClick={() => {this.handleAction(ActionType.scaleX);}}>
|
||||
<Icon type={ActionType.scaleX}/>
|
||||
</li>,
|
||||
<li
|
||||
key="scaleY"
|
||||
className={`${this.props.prefixCls}-btn`}
|
||||
onClick={() => {this.handleAction(ActionType.scaleY);}}>
|
||||
<Icon type={ActionType.scaleY}/>
|
||||
</li>,
|
||||
]);
|
||||
if (!this.props.scalable) {
|
||||
toolbars = deleteToolbarFromKey(toolbars, ['scaleX', 'scaleY']);
|
||||
}
|
||||
if (!this.props.downloadable) {
|
||||
toolbars = deleteToolbarFromKey(toolbars, ['download']);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
{attributeNode}
|
||||
<ul className={`${this.props.prefixCls}-toolbar`}>
|
||||
{featureNodeArr}
|
||||
{toolbars.map(item => {
|
||||
return this.renderAction(item);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
||||
BIN
src/style/fonts/icomoon.eot
Normal file → Executable file
BIN
src/style/fonts/icomoon.eot
Normal file → Executable file
Binary file not shown.
1
src/style/fonts/icomoon.svg
Normal file → Executable file
1
src/style/fonts/icomoon.svg
Normal file → Executable file
@ -10,6 +10,7 @@
|
||||
<glyph unicode="" glyph-name="rotate-left" d="M761.862-64c113.726 206.032 132.888 520.306-313.862 509.824v-253.824l-384 384 384 384v-248.372c534.962 13.942 594.57-472.214 313.862-775.628z" />
|
||||
<glyph unicode="" glyph-name="rotate-right" d="M576 711.628v248.372l384-384-384-384v253.824c-446.75 10.482-427.588-303.792-313.86-509.824-280.712 303.414-221.1 789.57 313.86 775.628z" />
|
||||
<glyph unicode="" glyph-name="reset" d="M1024 576h-384l143.53 143.53c-72.53 72.526-168.96 112.47-271.53 112.47s-199-39.944-271.53-112.47c-72.526-72.53-112.47-168.96-112.47-271.53s39.944-199 112.47-271.53c72.53-72.526 168.96-112.47 271.53-112.47s199 39.944 271.528 112.472c6.056 6.054 11.86 12.292 17.456 18.668l96.32-84.282c-93.846-107.166-231.664-174.858-385.304-174.858-282.77 0-512 229.23-512 512s229.23 512 512 512c141.386 0 269.368-57.326 362.016-149.984l149.984 149.984v-384z" />
|
||||
<glyph unicode="" glyph-name="download" d="M736 512l-256-256-256 256h160v384h192v-384zM480 256h-480v-256h960v256h-480zM896 128h-128v64h128v-64z" />
|
||||
<glyph unicode="" glyph-name="zoom-in" d="M992 576h-352v352c0 17.672-14.328 32-32 32h-192c-17.672 0-32-14.328-32-32v-352h-352c-17.672 0-32-14.328-32-32v-192c0-17.672 14.328-32 32-32h352v-352c0-17.672 14.328-32 32-32h192c17.672 0 32 14.328 32 32v352h352c17.672 0 32 14.328 32 32v192c0 17.672-14.328 32-32 32z" />
|
||||
<glyph unicode="" glyph-name="zoom-out" d="M0 544v-192c0-17.672 14.328-32 32-32h960c17.672 0 32 14.328 32 32v192c0 17.672-14.328 32-32 32h-960c-17.672 0-32-14.328-32-32z" />
|
||||
<glyph unicode="" glyph-name="close" d="M1014.662 137.34c-0.004 0.004-0.008 0.008-0.012 0.010l-310.644 310.65 310.644 310.65c0.004 0.004 0.008 0.006 0.012 0.010 3.344 3.346 5.762 7.254 7.312 11.416 4.246 11.376 1.824 24.682-7.324 33.83l-146.746 146.746c-9.148 9.146-22.45 11.566-33.828 7.32-4.16-1.55-8.070-3.968-11.418-7.31 0-0.004-0.004-0.006-0.008-0.010l-310.648-310.652-310.648 310.65c-0.004 0.004-0.006 0.006-0.010 0.010-3.346 3.342-7.254 5.76-11.414 7.31-11.38 4.248-24.682 1.826-33.83-7.32l-146.748-146.748c-9.148-9.148-11.568-22.452-7.322-33.828 1.552-4.16 3.97-8.072 7.312-11.416 0.004-0.002 0.006-0.006 0.010-0.010l310.65-310.648-310.65-310.652c-0.002-0.004-0.006-0.006-0.008-0.010-3.342-3.346-5.76-7.254-7.314-11.414-4.248-11.376-1.826-24.682 7.322-33.83l146.748-146.746c9.15-9.148 22.452-11.568 33.83-7.322 4.16 1.552 8.070 3.97 11.416 7.312 0.002 0.004 0.006 0.006 0.010 0.010l310.648 310.65 310.648-310.65c0.004-0.002 0.008-0.006 0.012-0.008 3.348-3.344 7.254-5.762 11.414-7.314 11.378-4.246 24.684-1.826 33.828 7.322l146.746 146.748c9.148 9.148 11.57 22.454 7.324 33.83-1.552 4.16-3.97 8.068-7.314 11.414z" />
|
||||
|
||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.6 KiB |
BIN
src/style/fonts/icomoon.ttf
Normal file → Executable file
BIN
src/style/fonts/icomoon.ttf
Normal file → Executable file
Binary file not shown.
BIN
src/style/fonts/icomoon.woff
Normal file → Executable file
BIN
src/style/fonts/icomoon.woff
Normal file → Executable file
Binary file not shown.
@ -52,6 +52,7 @@
|
||||
|
||||
&-btn {
|
||||
background-color: @btn-background-color;
|
||||
color: white;
|
||||
}
|
||||
|
||||
&-btn:hover{
|
||||
@ -132,7 +133,7 @@
|
||||
border-radius: @toolbarHeight;
|
||||
margin-right: 3px;
|
||||
cursor: pointer;
|
||||
line-height: 29px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
&-toolbar li:hover {
|
||||
@ -254,7 +255,11 @@
|
||||
|
||||
&-scaleY:before {
|
||||
content: '\ea5f';
|
||||
}
|
||||
}
|
||||
|
||||
&-download:before {
|
||||
content: '\e9c7';
|
||||
}
|
||||
}
|
||||
|
||||
.spin {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user