diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..a3ba8c3 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,21 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Chrome against localhost, with sourcemaps", + "type": "chrome", + "request": "launch", + "url": "http://localhost:8001", + "sourceMaps": true, + "webRoot": "${workspaceRoot}" + }, + { + "name": "Attach to Chrome, with sourcemaps", + "type": "chrome", + "request": "attach", + "port": 9222, + "sourceMaps": true, + "webRoot": "${workspaceRoot}" + } + ] +} \ No newline at end of file diff --git a/demo/images/tibet-6.jpg b/demo/images/tibet-6.jpg new file mode 100644 index 0000000..126f480 Binary files /dev/null and b/demo/images/tibet-6.jpg differ diff --git a/demo/index.tsx b/demo/index.tsx index 37f9415..4fc9f36 100644 --- a/demo/index.tsx +++ b/demo/index.tsx @@ -1,15 +1,16 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import ViewerWrap from '../src/ViewerWrap'; -const imgSrc2 = require('./images/landscape2.jpg'); -const imgSrc = require('./images/landscape.jpg'); +const img2 = require('./images/landscape2.jpg'); +const img = require('./images/landscape.jpg'); +const img3 = require('./images/tibet-6.jpg'); class App extends React.Component { constructor() { super(); this.state = { - visible: true, + visible: false, }; } @@ -20,7 +21,7 @@ class App extends React.Component { { this.setState({ visible: false }); } } - images={[imgSrc, imgSrc2]} + images={[img2, img, img3]} /> ); diff --git a/package.json b/package.json index 28a777b..abc4531 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,6 @@ "@types/react": "^0.14.39", "@types/react-dom": "^0.14.17", "atool-build": "^0.8.1", - "classnames": "^2.2.5", "dora": "^0.4.3", "dora-plugin-browser-history": "^0.2.0", "dora-plugin-webpack": "^0.8.1", diff --git a/src/Viewer.tsx b/src/Viewer.tsx index 3d42294..9d757d0 100644 --- a/src/Viewer.tsx +++ b/src/Viewer.tsx @@ -1,11 +1,12 @@ import * as React from 'react'; import './style/index.less'; -import ViewerFooter from './ViewerFooter'; +import ViewerCore from './ViewerCore'; +import ViewerNav from './ViewerNav'; function noop() {} interface ViewerState { - activeIndex: number; + activeIndex?: number; } export default class Viewer extends React.Component { @@ -13,36 +14,64 @@ export default class Viewer extends React.Component { visible: false, onClose: noop, images: [], + activeIndex: 0, }; private prefixCls: string; - constructor() { - super(); + constructor(props) { + super(props); this.prefixCls = 'react-viewer'; this.state = { - activeIndex: 0, + activeIndex: this.props.activeIndex, }; + + this.handleChangeImg = this.handleChangeImg.bind(this); } handleClose(e) { this.props.onClose(); } + handleChangeImg(newIndex: number) { + let newState = this.state; + newState.activeIndex = newIndex; + this.setState(newState); + } + + componentWillReceiveProps(nextProps: ViewerProps) { + if (this.state.activeIndex !== nextProps.activeIndex) { + this.setState({ + activeIndex: nextProps.activeIndex, + }); + } + } + render() { + let activeImgSrc = ''; + if (this.props.images.length > 0) { + activeImgSrc = this.props.images[this.state.activeIndex]; + } + return (
-
-
- +
+ +
); } diff --git a/src/ViewerCore.tsx b/src/ViewerCore.tsx new file mode 100644 index 0000000..c8c35c7 --- /dev/null +++ b/src/ViewerCore.tsx @@ -0,0 +1,169 @@ +import * as React from 'react'; + +export interface ViewerCoreProps { + prefixCls: string; + imgSrc: string; + visible: boolean; +} + +interface ViewerCoreState { + width?: number; + top?: number; + left?: number; + isMouseDown?: boolean; + mouseX?: number; + mouseY?: number; +} + +export default class ViewerCore extends React.Component { + + constructor() { + super(); + + this.state = { + width: 0, + top: 15, + left: null, + isMouseDown: false, + 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); + this.handleResize = this.handleResize.bind(this); + } + + componentDidMount() { + this.bindEvent(); + this.resizeImg(this.props.imgSrc); + } + + resizeImg(imgSrc) { + let img = new Image(); + img.src = imgSrc; + img.onload = () => { + let width = 0; + let height = 0; + let imgWidth = img.width; + let imgHeight = img.height; + let aspectRatio = imgWidth / imgHeight; + if (aspectRatio > 1) { + width = Math.min(window.innerWidth * .9, imgWidth); + height = (width / imgWidth) * imgHeight; + }else { + height = Math.min((window.innerHeight - 52) * .8, imgHeight); + width = (height / imgHeight) * imgWidth; + } + let left = ( window.innerWidth - width ) / 2; + let top = (window.innerHeight - height) / 2; + this.setState({ + width: width, + left: left, + top: top, + }); + }; + } + + handleResize(e) { + this.resizeImg(this.props.imgSrc); + } + + handleMouseDown(e) { + e.preventDefault(); + e.stopPropagation(); + this.setState({ + isMouseDown: true, + mouseX: e.nativeEvent.pageX, + mouseY: e.nativeEvent.pageY, + }); + } + + handleMouseMove(e) { + if (this.state.isMouseDown) { + let diffX = e.x - this.state.mouseX; + let diffY = e.y - this.state.mouseY; + this.setState({ + top: this.state.top + diffY, + left: this.state.left + diffX, + mouseX: e.x, + mouseY: e.y, + }); + } + } + + handleMouseUp(e) { + this.setState({ + isMouseDown: false, + }); + } + + handleMouseScroll(e) { + let direct: 0 | 1 | -1 = 0; + if (e.wheelDelta) { + direct = e.wheelDelta > 0 ? 1 : -1; + }else if (e.detail) { + direct = e.detail > 0 ? 1 : -1; + } + if (direct !== 0) { + this.setState({ + width: this.state.width + direct * this.state.width * 0.05, + }); + } + } + + bindEvent(remove?: boolean) { + let funcName = 'addEventListener'; + if (remove) { + funcName = 'removeEventListener'; + } + document[funcName]('mousewheel', this.handleMouseScroll, false); + document[funcName]('click', this.handleMouseUp, false); + document[funcName]('mousemove', this.handleMouseMove, false); + window[funcName]('resize', this.handleResize, false); + } + + componentWillReceiveProps(nextProps: ViewerCoreProps) { + if (this.props.imgSrc !== nextProps.imgSrc) { + this.resizeImg(nextProps.imgSrc); + } + if (!this.props.visible && nextProps.visible) { + this.bindEvent(); + this.resizeImg(this.props.imgSrc); + } + if (this.props.visible && !nextProps.visible) { + this.bindEvent(true); + } + } + + render() { + let imgStyle: React.CSSProperties = { + width: `${this.state.width}px`, + marginTop: `${this.state.top}px`, + marginLeft: this.state.left ? `${this.state.left}px` : 'auto', + }; + + let imgClass = ''; + if (!this.state.isMouseDown) { + imgClass = `${this.props.prefixCls}-transition`; + } + + return ( +
+ +
+ ); + } +} diff --git a/src/ViewerFooter.tsx b/src/ViewerFooter.tsx deleted file mode 100644 index 0d0434a..0000000 --- a/src/ViewerFooter.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import * as React from 'react'; -import ViewerNav from './ViewerNav'; - -export interface ViewerFooterProps { - prefixCls: string; - images: any[]; - activeIndex: number; -} - -export default class ViewerFooter extends React.Component { - render() { - return ( -
- -
- ); - } -} diff --git a/src/ViewerNav.tsx b/src/ViewerNav.tsx index daa1db6..ad8563c 100644 --- a/src/ViewerNav.tsx +++ b/src/ViewerNav.tsx @@ -4,6 +4,7 @@ export interface ViewerNavProps { prefixCls: string; images: any[]; activeIndex: number; + onChangeImg: (index: number) => void; } export default class ViewerNav extends React.Component { @@ -11,12 +12,22 @@ export default class ViewerNav extends React.Component { activeIndex: 0, }; + handleChangeImg(newIndex) { + this.props.onChangeImg(newIndex); + } + render() { return (
    {this.props.images.map((item, index) => -
  • +
  • + +
  • ) }
diff --git a/src/ViewerProps.ts b/src/ViewerProps.ts index d2d43a9..58e9d0b 100644 --- a/src/ViewerProps.ts +++ b/src/ViewerProps.ts @@ -5,4 +5,6 @@ interface ViewerProps { onClose?: () => void; /** 需要进行浏览的图片地址集合 */ images?: any[]; + /** 当前图像index */ + activeIndex?: number; } diff --git a/src/style/index.less b/src/style/index.less index 379e6fb..f227a79 100644 --- a/src/style/index.less +++ b/src/style/index.less @@ -29,12 +29,22 @@ z-index: @zIndex; } - &-container { + &-core { position: absolute; top: 0; right: 0; left: 0; bottom: 0; + overflow: hidden; + } + + &-core > img { + display: block; + margin: 15px auto; + width: auto; + height: auto; + cursor: move; + user-select: none; } &-footer { @@ -83,4 +93,8 @@ &-list > li.active > img { opacity: 1; } + + &-transition { + transition: all .3s ease-out; + } } \ No newline at end of file