React图片预览组件,支持缩放、旋转、上一张下一张功能

1、功能需求:由于项目业务需要一个图片预览的功能,又不想引入太多组件依赖,所以决定自己编写一套,实现了图片放大缩小、旋转、查看下一张或上一张图片功能,如图1.0截图所示。

2、外部资源:这里的icon图标采用的是 iconfont 里面的图标,自己可以自行寻找自己喜欢的图标代替,或者使用默认的图标,默认的图标css地址为

  https://at.alicdn.com/t/font_1966765_c473t2y8dvr.css

3、功能说明:该组件支持鼠标滚轮放大缩小及esc关闭功能,也可通过配置进行禁用,根据项目实际应用进行配置。这里采用的 less 进行样式编写。

4、组件名称:photo-preview。

5、组件截图:

     

                     图1.0截图    

6、组件代码:

less 样式:

.photo-preview__thumb-img {
    cursor: pointer;
}

.photo-preview {
    margin: 0;
    position: fixed;
    left: 0;
    top: 0;
    bottom: 0;
    right: 0;
    z-index: 999999;
    background-color: rgba(0, 0, 0, 0.5);
    animation: fadeIn 0.4s;

    .photo-preview__in {
        position: absolute;
        left: 0;
        top: 0;
        right: 0;
        bottom: 0;
        overflow: auto;
        user-select: none;
        display: flex;
        justify-content: center;
        align-items: center;

        &::-webkit-scrollbar {
            width: 15px;
            height: 15px;
        }

        &::-webkit-scrollbar-track {
            border-radius: 0;
        }

        &::-webkit-scrollbar-thumb {
            border-radius: 0;
            background-color: silver;
        }

        .photo-preview__img-wrap {
            transition-duration: 0.2s;
            position: absolute;
            &::before {
                content: ' ';
                display: block;
                width: 100%;
                height: 100%;
                position: absolute;
                margin-left: 20px;
                margin-bottom: 20px;
            }
            img {
                position: absolute;
                width: 100%;
                height: 100%;
            }
        }
    }

    .photo-preview__loading {
        position: relative;
        &::before {
            content: ' ';
            display: block;
            border-top: 5px solid #999999;
            border-right: 5px solid #999999;
            border-bottom: 5px solid #999999;
            border-left: 5px solid #ffffff;
            width: 50px;
            height: 50px;
            border-radius: 50%;
            animation: rotating 0.8s linear 0s infinite;
        }
    }

    .photo-preview__tool {
        border-radius: 45px;
        padding: 5px 10px;
        height: 45px;
        background-color: #ffffff;
        opacity: 0.3;
        position: fixed;
        top: 20px;
        right: 20px;
        user-select: none;
        transition-duration: 0.5s;
        display: flex;

        &:hover {
            opacity: 0.9;
        }

        .iconfont {
            font-size: 25px;
            text-align: center;
            width: 35px;
            height: 35px;
            line-height: 35px;
            color: #444444;
            // display: inline-block;
            transition-duration: 0.4s;
            margin: 0 2px;
            cursor: pointer;
        }

        .icon-close:hover {
            transform: scale(1.15);
        }

        .icon-turn-left {
            transform: rotate(50deg);
        }

        .icon-turn-left:hover {
            transform: rotate(0deg);
        }

        .icon-turn-right {
            transform: rotate(-50deg);
        }

        .icon-turn-right:hover {
            transform: rotate(0deg);
        }
        .icon-go-left,
        .icon-go-right {
            &[data-disable='true'] {
                // pointer-events: none;
                cursor: wait;
            }
        }
    }
}

// 渐现
@keyframes fadeIn {
    0% {
        opacity: 0;
    }

    100% {
        opacity: 1;
    }
}

// 旋转
@keyframes rotating {
    0% {
        transform: rotate(0deg);
    }

    100% {
        transform: rotate(360deg);
    }
}
View Code

js 组件代码:

/**
 * @param {type: number, desc: 当前点击的图片索引} imgIndex
 * @param {type: array, desc: 传入的图片列表,结构也应该是[{bigUrl:'imgUrl', alt:'图片描述'}]} imgs
 * @param {type: string, desc: 弹框显示出来的大图} bigUrl
 * @param {type: string, desc: 默认显示的小图片} url
 * @param {type: string, desc: 图片描述} alt
 * @param {type: object, desc: 操作按钮显示,默认都显示,如果对象中指定哪个按钮为false那么表示不显示, 
    example : {
        toSmall: bool,  //缩小按钮是否显示
        toBig: bool,   //放大按钮是否显示
        turnLeft: bool, //左转按钮是否显示
        turnRight: bool  //右转按钮是否显示
        close: bool, //关闭按钮是否显示
        esc: bool, //键盘中的esc键事件是否触发
        mousewheel: bool, // 鼠标滚轮事件是否触发
    }} tool
 *
 * 示例: @example
 *  <PhotoPreview 
 *      bigUrl={item.bigUrl} 
 *      url={item.url} 
 *      alt={item.alt} 
 *      tool={{ turnLeft: false, turnRight: false }} 
 *   />
 * 
 */

import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import '_less/photo-preview';

class PhotoPreview extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            bigUrl: props.bigUrl === '' ? props.url : props.bigUrl,
            tool: Object.assign(PhotoPreview.defaultProps.tool, props.tool),
            imgIndex: props.imgIndex,
            imgs: props.imgs,

            loadEl: true, // loading元素显示隐藏
            figureEl: false, // 生成图片预览元素
            imgOriginalWidth: 0, // 当前大图默认宽度值
            imgOriginalHeight: 0, // 当前大图默认高度值
            imgAttr: {
                // 大图的地址及描述
                src: '',
                alt: '',
            },
            imgParentStyle: {
                // 大图父级div元素样式
                width: '0px',
                height: '0px',
            },
            rotateDeg: 0, // 图片旋转角度
        };

        // this.bigImgRef = React.createRef();
    }

    // 预览图片超出window宽或高的处理
    beyondWindow = () => {
        const { imgParentStyle, rotateDeg } = this.state;
        const iWidth = parseFloat(imgParentStyle.width);
        const iHeight = parseFloat(imgParentStyle.height);
        let ips = imgParentStyle;
        if (rotateDeg % 360 === 90 || rotateDeg % 360 === 270) {
            if (iHeight > window.innerWidth) {
                ips = { ...ips, left: `${(iHeight - iWidth) / 2 + 20}px` };
            } else {
                ips = { ...ips, left: 'auto' };
            }
            if (iWidth > window.innerHeight) {
                ips = { ...ips, top: `${(iWidth - iHeight) / 2 + 20}px` };
            } else {
                ips = { ...ips, top: 'auto' };
            }
        } else {
            if (iWidth > window.innerWidth) {
                ips = { ...ips, left: '20px' };
            } else {
                ips = { ...ips, left: 'auto' };
            }
            if (iHeight > window.innerHeight) {
                ips = { ...ips, top: '20px' };
            } else {
                ips = { ...ips, top: 'auto' };
            }
        }
        this.setState({
            imgParentStyle: ips,
        });
    };

    // 图片缩小事件
    toSmallEvent = () => {
        const { tool, imgParentStyle, imgOriginalWidth, imgOriginalHeight } = this.state;
        if (tool.toSmall === false) {
            return;
        }
        let width = parseFloat(imgParentStyle.width) / 1.5;
        let height = parseFloat(imgParentStyle.height) / 1.5;
        // 图片缩小不能超过5倍
        if (width < imgOriginalWidth / 5) {
            width = imgOriginalWidth / 5;
            height = imgOriginalHeight / 5;
        }
        this.setState(
            {
                imgParentStyle: Object.assign(imgParentStyle, { width: `${width}px`, height: `${height}px` }),
            },
            () => {
                this.beyondWindow();
            }
        );
    };

    // 图片放大事件
    toBigEvent = () => {
        const { tool, imgParentStyle, imgOriginalWidth, imgOriginalHeight } = this.state;
        if (tool.toBig === false) {
            return;
        }
        let width = parseFloat(imgParentStyle.width) * 1.5;
        let height = parseFloat(imgParentStyle.height) * 1.5;
        // 图片放大不能超过5倍
        if (width > imgOriginalWidth * 5) {
            width = imgOriginalWidth * 5;
            height = imgOriginalHeight * 5;
        }
        this.setState(
            {
                imgParentStyle: Object.assign(imgParentStyle, { width: `${width}px`, height: `${height}px` }),
            },
            () => {
                this.beyondWindow();
            }
        );
    };

    // 向左旋转事件
    turnLeftEvent = () => {
        const { tool, rotateDeg, imgParentStyle } = this.state;
        if (tool.turnLeft === false) {
            return;
        }
        const iRotateDeg = rotateDeg - 90;
        this.setState(
            {
                imgParentStyle: Object.assign(imgParentStyle, { transform: `rotate(${iRotateDeg}deg)` }),
                rotateDeg: iRotateDeg,
            },
            () => {
                this.beyondWindow();
            }
        );
    };

    // 向右旋转事件
    turnRightEvent = () => {
        const { tool, rotateDeg, imgParentStyle } = this.state;
        if (tool.turnRight === false) {
            return;
        }
        const iRotateDeg = rotateDeg + 90;
        this.setState(
            {
                imgParentStyle: Object.assign(imgParentStyle, { transform: `rotate(${iRotateDeg}deg)` }),
                rotateDeg: iRotateDeg,
            },
            () => {
                this.beyondWindow();
            }
        );
    };

    // 上一张图片
    goLeftEvent = () => {
        const { imgIndex, imgs, loadEl } = this.state;
        // 如果还在loading加载中,不予许上一张下一张操作
        if (loadEl) {
            return;
        }
        const nImgIndex = imgIndex - 1;
        // console.log(nImgIndex);
        if (nImgIndex < 0) {
            return;
        }
        this.setState(
            {
                imgIndex: nImgIndex,
                rotateDeg: 0,
                imgParentStyle: {
                    width: '0px',
                    height: '0px',
                },
            },
            () => {
                this.photoShow(imgs[nImgIndex].bigUrl, imgs[nImgIndex].alt, false);
            }
        );
    };

    // 下一张图片
    goRightEvent = () => {
        const { imgIndex, imgs, loadEl } = this.state;
        // 如果还在loading加载中,不予许上一张下一张操作
        if (loadEl) {
            return;
        }
        const nImgIndex = imgIndex + 1;
        // console.log(nImgIndex);
        if (nImgIndex > imgs.length - 1) {
            return;
        }
        this.setState(
            {
                imgIndex: nImgIndex,
                rotateDeg: 0,
                imgParentStyle: {
                    width: '0px',
                    height: '0px',
                },
            },
            () => {
                // 如果不存在大图,那么直接拿小图代替。
                const bigUrl = imgs[nImgIndex].bigUrl || imgs[nImgIndex].url;
                this.photoShow(bigUrl, imgs[nImgIndex].alt);
            }
        );
    };

    // 关闭事件
    closeEvent = () => {
        // 恢复到默认值
        const { imgIndex, imgs } = this.props;
        this.setState({
            imgIndex,
            imgs,
            figureEl: false,
            rotateDeg: 0,
            imgParentStyle: {
                width: '0px',
                height: '0px',
            },
        });
        document.body.style.cssText = '';
        window.removeEventListener('mousewheel', this._psMousewheelEvent);
        window.removeEventListener('keydown', this._psKeydownEvent);
        window.removeEventListener('resize', this._psWindowResize);
    };

    // 鼠标滚轮事件
    _psMousewheelEvent = (event) => {
        const { figureEl, tool } = this.state;
        if (figureEl && tool.mousewheel) {
            if (event.wheelDelta > 0) {
                this.toBigEvent();
            } else {
                this.toSmallEvent();
            }
        }
    };

    // 键盘按下事
    _psKeydownEvent = (event) => {
        const { figureEl, tool } = this.state;
        if (event.keyCode === 27 && tool.esc && figureEl) {
            this.closeEvent();
        }
    };

    // 窗口发生改变的时候
    _psWindowResize = () => {
        const { figureEl } = this.state;
        if (figureEl) {
            this.beyondWindow();
        }
    };

    // 图片展示
    photoShow = (url, alt, winEventToggle) => {
        // 图片加载并处理
        this.setState({
            loadEl: true,
            figureEl: true,
        });
        const img = new Image();
        img.src = url;
        img.onload = async () => {
            this.setState(
                {
                    loadEl: false,
                    imgOriginalWidth: img.width,
                    imgOriginalHeight: img.height,
                    imgAttr: {
                        src: url,
                        alt,
                    },
                    imgParentStyle: {
                        width: `${img.width}px`,
                        height: `${img.height}px`,
                    },
                },
                () => {
                    this.beyondWindow();
                }
            );
        };

        // 是否需再次执行window事件
        const wev = winEventToggle || true;
        if (wev) {
            // console.log('wev');
            // window触发事件
            window.addEventListener('mousewheel', this._psMousewheelEvent);
            window.addEventListener('keydown', this._psKeydownEvent);
            window.addEventListener('resize', this._psWindowResize);
            document.body.style.cssText = 'overflow:hidden';
        }
    };

    UNSAFE_componentWillReceiveProps(newProps) {
        console.log(`new-props:${newProps.nImgIndex}`);
    }

    render() {
        const { alt, url } = this.props;
        const { bigUrl, tool, figureEl, loadEl, imgAttr, imgParentStyle, imgIndex, imgs } = this.state;
        const iParentStyle = { ...imgParentStyle };
        return (
            <>
                <img
                    onClick={this.photoShow.bind(this, bigUrl, alt)}
                    src={url}
                    alt={alt}
                    className="photo-preview__thumb-img"
                />
                {figureEl
                    ? ReactDOM.createPortal(
                          <>
                              <figure className="photo-preview">
                                  <div className="photo-preview__in">
                                      {loadEl ? (
                                          <div className="photo-preview__loading"></div>
                                      ) : (
                                          <div className="photo-preview__img-wrap" style={iParentStyle}>
                                              {/* ref={this.bigImgRef} */}
                                              <img src={imgAttr.src} alt={imgAttr.alt} />
                                          </div>
                                      )}
                                      <div className="photo-preview__tool">
                                          {tool.toSmall ? (
                                              <i className="iconfont icon-to-small" onClick={this.toSmallEvent}></i>
                                          ) : null}
                                          {tool.toBig ? (
                                              <i className="iconfont icon-to-big" onClick={this.toBigEvent}></i>
                                          ) : null}
                                          {tool.turnLeft ? (
                                              <i className="iconfont icon-turn-left" onClick={this.turnLeftEvent}></i>
                                          ) : null}
                                          {tool.turnRight ? (
                                              <i className="iconfont icon-turn-right" onClick={this.turnRightEvent}></i>
                                          ) : null}

                                          {imgIndex !== '' && imgs.length > 1 ? (
                                              <>
                                                  <i
                                                      className="iconfont icon-go-left"
                                                      onClick={this.goLeftEvent}
                                                      data-disable={loadEl ? 'true' : 'false'}
                                                  ></i>
                                                  <i
                                                      className="iconfont icon-go-right"
                                                      onClick={this.goRightEvent}
                                                      data-disable={loadEl ? 'true' : 'false'}
                                                  ></i>
                                              </>
                                          ) : null}

                                          {tool.close ? (
                                              <i className="iconfont icon-close" onClick={this.closeEvent}></i>
                                          ) : null}
                                      </div>
                                  </div>
                              </figure>
                          </>,
                          document.body
                      )
                    : null}
            </>
        );
    }
}

PhotoPreview.defaultProps = {
    bigUrl: '',
    alt: '',
    tool: {
        toSmall: true, // 缩小按钮
        toBig: true, // 放大按钮
        turnLeft: true, // 左转按钮
        turnRight: true, // 右转按钮
        close: true, // 关闭按钮
        esc: true, // esc键触发
        mousewheel: true, // 鼠标滚轮事件是否触发
    },
    imgIndex: '',
    imgs: [],
};
PhotoPreview.propTypes = {
    bigUrl: PropTypes.string,
    url: PropTypes.string.isRequired,
    alt: PropTypes.string,
    tool: PropTypes.object,
    imgIndex: PropTypes.number,
    imgs: PropTypes.array,
};
export default PhotoPreview;
View Code

js 组件案例代码:

// 导入图片预览组件
import PhotoPreview from '_components/photo-preview';

// 模拟图片列表数据
const atlasImgList = [
    {
        url: 'http://dummyimage.com/200x100/ff3838&text=Hello',
        bigUrl: 'http://dummyimage.com/400x200/ff3838&text=Hello',
        alt: 'Hello',
    },
    {
        url: 'http://dummyimage.com/200x100/ff9f1a&text=Photo',
        bigUrl: 'http://dummyimage.com/400x200/ff9f1a&text=Photo',
        alt: 'Photo',
    },
    {
        url: 'http://dummyimage.com/200x100/c56cf0&text=Preview',
        bigUrl: 'http://dummyimage.com/400x200/c56cf0&text=Preview',
        alt: 'Preview',
    },
    {
        url: 'http://dummyimage.com/100x100/3ae374&text=!',
        bigUrl: 'http://dummyimage.com/200x200/3ae374&text=!',
        alt: '!',
    },
];

const Test = () => {
    return (
        <>
            {atlasImgList.map((item, index) => {
                return (
                    <PhotoPreview
                        keys={index}
                        imgIndex={index}
                        imgs={atlasImgList}
                        url={item.url}
                        bigUrl={item.bigUrl}
                        alt={item.alt}
                        // tool={{ turnLeft: false, turnRight: false }}
                    />
                );
            })}
        </>
    );
};

export default Test;
View Code

猜你喜欢

转载自www.cnblogs.com/zion0707/p/13377652.html