complete viewer core

This commit is contained in:
infeng 2016-10-15 15:11:48 +08:00
parent aa530d62b4
commit fd724512a3
10 changed files with 263 additions and 39 deletions

21
.vscode/launch.json vendored Normal file
View File

@ -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}"
}
]
}

BIN
demo/images/tibet-6.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

View File

@ -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<any, any> {
constructor() {
super();
this.state = {
visible: true,
visible: false,
};
}
@ -20,7 +21,7 @@ class App extends React.Component<any, any> {
<ViewerWrap
visible={this.state.visible}
onClose={() => { this.setState({ visible: false }); } }
images={[imgSrc, imgSrc2]}
images={[img2, img, img3]}
/>
</div>
);

View File

@ -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",

View File

@ -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<ViewerProps, ViewerState> {
@ -13,36 +14,64 @@ export default class Viewer extends React.Component<ViewerProps, ViewerState> {
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 (
<div style={{display: this.props.visible ? 'block' : 'none'}}>
<div className={`${this.prefixCls}-mask`}></div>
<div className={`${this.prefixCls}-close`} onClick={this.handleClose.bind(this)}></div>
<div className={`${this.prefixCls}-container`}>
</div>
<ViewerFooter
<ViewerCore
prefixCls={this.prefixCls}
images={this.props.images}
activeIndex={this.state.activeIndex}
imgSrc={activeImgSrc}
visible={this.props.visible}
/>
<div className={`${this.prefixCls}-footer`}>
<ViewerNav
prefixCls={this.prefixCls}
images={this.props.images}
activeIndex={this.state.activeIndex}
onChangeImg={this.handleChangeImg}
/>
</div>
</div>
);
}

169
src/ViewerCore.tsx Normal file
View File

@ -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<ViewerCoreProps, ViewerCoreState> {
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 (
<div
className={`${this.props.prefixCls}-core`}
onMouseDown={this.handleMouseDown}
>
<img
ref="img"
className={imgClass}
src={this.props.imgSrc}
style={imgStyle}
onMouseDown={this.handleMouseDown}
/>
</div>
);
}
}

View File

@ -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<ViewerFooterProps, any> {
render() {
return (
<div className={`${this.props.prefixCls}-footer`}>
<ViewerNav
prefixCls={this.props.prefixCls}
images={this.props.images}
activeIndex={this.props.activeIndex}
/>
</div>
);
}
}

View File

@ -4,6 +4,7 @@ export interface ViewerNavProps {
prefixCls: string;
images: any[];
activeIndex: number;
onChangeImg: (index: number) => void;
}
export default class ViewerNav extends React.Component<ViewerNavProps, any> {
@ -11,12 +12,22 @@ export default class ViewerNav extends React.Component<ViewerNavProps, any> {
activeIndex: 0,
};
handleChangeImg(newIndex) {
this.props.onChangeImg(newIndex);
}
render() {
return (
<div className={`${this.props.prefixCls}-navbar`}>
<ul className={`${this.props.prefixCls}-list`}>
{this.props.images.map((item, index) =>
<li className={index === this.props.activeIndex ? 'active' : ''}><img src={item} /></li>
<li
key={index}
className={index === this.props.activeIndex ? 'active' : ''}
onClick={this.handleChangeImg.bind(this, index)}
>
<img src={item} />
</li>
)
}
</ul>

View File

@ -5,4 +5,6 @@ interface ViewerProps {
onClose?: () => void;
/** 需要进行浏览的图片地址集合 */
images?: any[];
/** 当前图像index */
activeIndex?: number;
}

View File

@ -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;
}
}