mirror of
https://github.com/infeng/react-viewer.git
synced 2025-12-08 17:36:40 +00:00
complete viewer core
This commit is contained in:
parent
aa530d62b4
commit
fd724512a3
21
.vscode/launch.json
vendored
Normal file
21
.vscode/launch.json
vendored
Normal 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
BIN
demo/images/tibet-6.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 160 KiB |
@ -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>
|
||||
);
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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
169
src/ViewerCore.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -5,4 +5,6 @@ interface ViewerProps {
|
||||
onClose?: () => void;
|
||||
/** 需要进行浏览的图片地址集合 */
|
||||
images?: any[];
|
||||
/** 当前图像index */
|
||||
activeIndex?: number;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user