• 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 {
                 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;
                .photo-preview__img-placeholder {
                    display: block;
                    width: 100%;
                    height: 100%;
                    position: absolute;
                    pointer-events: none;
                }
                img {
                    position: absolute;
                    width: 100%;
                    height: 100%;
                    // cursor: move;
                }
            }
        }
    
        .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;
                }
            }
        }
    }
    
    body[photo-preview-show='true'] {
        overflow: hidden;
    }
    
    // 渐现
    @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 React from 'react';
    import ReactDOM from 'react-dom';
    import PropTypes from 'prop-types';
    import '@/less/components/photo-preview.less';
    
    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元素样式
                     '0px',
                    height: '0px',
                },
                rotateDeg: 0, // 图片旋转角度
                increaseNum: 20, // 图片放大时距离空隙
            };
    
            // 获取相关元素
            this.bigImgRef = React.createRef();
            this.ppiRef = React.createRef();
        }
    
        // 预览图片超出window宽或高的处理
        beyondWindow = () => {
            const { imgParentStyle, rotateDeg, increaseNum } = this.state;
            const iWidth = parseFloat(imgParentStyle.width) + increaseNum * 2;
            const iHeight = parseFloat(imgParentStyle.height) + increaseNum * 2;
            const ppiEl = this.ppiRef.current;
    
            let ips = imgParentStyle;
            if (rotateDeg % 360 === 90 || rotateDeg % 360 === 270) {
                if (iHeight > window.innerWidth) {
                    ips = { ...ips, left: `${(iHeight - iWidth) / 2 + increaseNum}px` };
                } else {
                    ips = { ...ips, left: 'auto' };
                }
                if (iWidth > window.innerHeight) {
                    ips = { ...ips, top: `${(iWidth - iHeight) / 2 + increaseNum}px` };
                } else {
                    ips = { ...ips, top: 'auto' };
                }
            } else if (
                (rotateDeg % 360 === -90 && iWidth > iHeight) ||
                (rotateDeg % 360 === -270 && iWidth > iHeight)
            ) {
                // 如果是-90或-270,并且图片宽大于高的话,那么则需要做兼容处理
                let left = 'auto';
                let top = 'auto';
                if (iHeight > ppiEl.clientWidth) {
                    left = `${-(iHeight / 2) + increaseNum * 2}px`;
                }
                if (iWidth > ppiEl.clientHeight) {
                    top = `${iHeight / 2 + increaseNum / 2}px`;
                }
                ips = { ...ips, left: `${left}`, top: `${top}` };
            } else if (
                (rotateDeg % 360 === -90 && iHeight > iWidth) ||
                (rotateDeg % 360 === -270 && iHeight > iWidth)
            ) {
                // 如果是-90或-270,并且图片高大于宽的话,那么则需要做兼容处理
                let left = 'auto';
                let top = 'auto';
                if (iHeight > ppiEl.clientWidth) {
                    left = `${iWidth / 2}px`;
                }
                if (iWidth > ppiEl.clientHeight) {
                    top = `${-(iWidth / 2) + increaseNum * 2}px`;
                }
    
                ips = { ...ips, left: `${left}`, top: `${top}` };
            } else {
                if (iWidth > window.innerWidth) {
                    ips = { ...ips, left: `${increaseNum}px` };
                } else {
                    ips = { ...ips, left: 'auto' };
                }
                if (iHeight > window.innerHeight) {
                    ips = { ...ips, top: `${increaseNum}px` };
                } 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}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}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: {
                         '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: {
                         '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: {
                     '0px',
                    height: '0px',
                },
            });
            window.removeEventListener('mousewheel', this._psMousewheelEvent);
            window.removeEventListener('keydown', this._psKeydownEvent);
            window.removeEventListener('resize', this._psWindowResize);
            document.body.removeAttribute('photo-preview-show');
        };
    
        // 大图被执行拖拽操作
        bigImgMouseDown = (event) => {
            event.preventDefault();
            const ppiEl = this.ppiRef.current;
            const bigImgEl = this.bigImgRef.current;
            const diffX = event.clientX - bigImgEl.offsetLeft;
            const diffY = event.clientY - bigImgEl.offsetTop;
            // 鼠标移动的时候
            bigImgEl.onmousemove = (ev) => {
                const moveX = parseFloat(ev.clientX - diffX);
                const moveY = parseFloat(ev.clientY - diffY);
                const mx = moveX > 0 ? -moveX : Math.abs(moveX);
                const my = moveY > 0 ? -moveY : Math.abs(moveY);
                let sl = ppiEl.scrollLeft + mx * 0.1;
                let sr = ppiEl.scrollTop + my * 0.1;
                if (sl <= 0) {
                    sl = 0;
                } else if (sl >= ppiEl.scrollWidth - ppiEl.clientWidth) {
                    sl = ppiEl.scrollWidth - ppiEl.clientWidth;
                }
                if (sr <= 0) {
                    sr = 0;
                } else if (sr >= ppiEl.scrollHeight - ppiEl.clientHeight) {
                    sr = ppiEl.scrollHeight - ppiEl.clientHeight;
                }
                ppiEl.scrollTo(sl, sr);
            };
            // 鼠标抬起的时候
            bigImgEl.onmouseup = () => {
                bigImgEl.onmousemove = null;
                bigImgEl.onmouseup = null;
            };
            // 鼠标离开的时候
            bigImgEl.onmouseout = () => {
                bigImgEl.onmousemove = null;
                bigImgEl.onmouseup = null;
            };
        };
    
        // 鼠标滚轮事件
        _psMousewheelEvent = (event) => {
            // event.preventDefault();
            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: {
                             `${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.setAttribute('photo-preview-show', 'true');
            }
        };
    
        UNSAFE_componentWillReceiveProps(newProps) {
            console.log(`new-props:${newProps.nImgIndex}`);
        }
    
        render() {
            const { alt, url } = this.props;
            const {
                bigUrl,
                tool,
                figureEl,
                loadEl,
                imgAttr,
                imgParentStyle,
                imgIndex,
                imgs,
                increaseNum,
            } = this.state;
            const iParentStyle = { ...imgParentStyle };
            const iSpanStyle = {
                 `${parseFloat(imgParentStyle.width) + increaseNum * 2}px`,
                height: `${parseFloat(imgParentStyle.height) + increaseNum * 2}px`,
            };
            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" ref={this.ppiRef}>
                                          {loadEl ? (
                                              <div className="photo-preview__loading"></div>
                                          ) : (
                                              <div
                                                  className="photo-preview__img-wrap"
                                                  style={iParentStyle}
                                              >
                                                  <span
                                                      className="photo-preview__img-placeholder"
                                                      style={{
                                                          ...iSpanStyle,
                                                          marginLeft: `-${increaseNum}px`,
                                                          marginTop: `-${increaseNum}px`,
                                                      }}
                                                  ></span>
                                                  <img
                                                      src={imgAttr.src}
                                                      alt={imgAttr.alt}
                                                      onMouseDown={this.bigImgMouseDown}
                                                      ref={this.bigImgRef}
                                                  />
                                              </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 React from 'react';
    // 导入图片预览组件
    import PhotoPreview from '@/components/photo-preview';
    
    // 模拟图片列表数据
    const atlasImgList = [
        {
            url: 'http://dummyimage.com/200x100/ff3838&text=Hello',
            bigUrl: 'http://dummyimage.com/800x400/ff3838&text=Hello',
            alt: 'Hello',
        },
        {
            url: 'http://dummyimage.com/200x100/ff9f1a&text=Photo',
            bigUrl: 'http://dummyimage.com/800x400/ff9f1a&text=Photo',
            alt: 'Photo',
        },
        {
            url: 'http://dummyimage.com/200x100/c56cf0&text=Preview',
            bigUrl: 'http://dummyimage.com/800x400/c56cf0&text=Preview',
            alt: 'Preview',
        },
        {
            url: 'http://dummyimage.com/100x100/3ae374&text=!',
            bigUrl: 'http://dummyimage.com/400x400/3ae374&text=!',
            alt: '!',
        },
    ];
    
    const Test = () => {
        return (
            <>
                {atlasImgList.map((item, index) => {
                    return (
                        <PhotoPreview
                            key={index}
                            imgIndex={index}
                            imgs={atlasImgList}
                            url={item.url}
                            bigUrl={item.bigUrl}
                            alt={item.alt}
                            // tool={{ turnLeft: false, turnRight: false }}
                        />
                    );
                })}
            </>
        );
    };
    
    export default Test;
    View Code
  • 相关阅读:
    五种方法来遍历Map
    怎样去理解Java中的volatile
    大二层网络----Vxlan技术
    HTTP请求响应过程
    TCP数据传输
    TCP标志位
    TCP协议中的三次握手和四次挥手(图解)
    HTTP报文分析
    HTTP报文图示
    DNS数据包结构
  • 原文地址:https://www.cnblogs.com/zion0707/p/13377652.html
Copyright © 2020-2023  润新知