vue+pc图片预览(放大、缩小、比例化、旋转、下载)

1,364 阅读1分钟

概述

一站式解决pc端图片预览以及下载

使用环境以及前提条件

vue2.x pc端图片预览 基于element  UI

组件目录

image.png

image-viewer.vue 文件源码

可以复制使用

<template>
    <transition name="viewer-fade">
        <div class="el-image-viewer__wrapper" :style="{ 'z-index': zIndex }">
            <div class="el-image-viewer__mask"></div>
            <!-- CLOSE -->
            <span class="el-image-viewer__btn el-image-viewer__close" @click="hide">
            <i class="el-icon-circle-close" style="color: #fff"></i>
            </span>
            <!-- ARROW -->
            <template v-if="!isSingle">
                <span
                    class="el-image-viewer__btn el-image-viewer__prev"
                    :class="{ 'is-disabled': !infinite && isFirst }"
                    @click="prev">
                    <i class="el-icon-arrow-left"/>
                </span>
                <span
                    class="el-image-viewer__btn el-image-viewer__next"
                    :class="{ 'is-disabled': !infinite && isLast }"
                    @click="next">
                    <i class="el-icon-arrow-right"/>
                </span>
            </template>
            <!-- ACTIONS -->
            <div class="el-image-viewer__btn el-image-viewer__actions">
                <div class="el-image-viewer__actions__inner">
                    <i class="el-icon-zoom-out" @click="handleActions('zoomOut')"></i>
                    <i class="el-icon-zoom-in" @click="handleActions('zoomIn')"></i>
                    <i class="el-image-viewer__actions__divider"></i>
                    <i :class="mode.icon" @click="toggleMode"></i>
                    <i class="el-image-viewer__actions__divider"></i>
                    <i class="el-icon-refresh-left" @click="handleActions('anticlocelise')"></i>
                    <i class="el-icon-refresh-right" @click="handleActions('clocelise')"></i>
                    <i class="el-icon-bottom" @click="handleActions('down')"></i>
                </div>
            </div>
            <!-- CANVAS -->
            <div class="el-image-viewer__canvas">
                <img
                    v-for="(url, i) in urlList"
                    v-if="i === index"
                    ref="img"
                    class="el-image-viewer__img"
                    :key="url"
                    :src="currentImg"
                    :style="imgStyle"
                    @load="handleImgLoad"
                    @error="handleImgError"
                    @mousedown="handleMouseDown" 
                 />
            </div>
        </div>
    </transition>
</template>

<script>
import { on, off } from './dom';
import { rafThrottle, isFirefox } from './util';
const Mode = {
    CONTAIN: {
        name: 'contain',
        icon: 'el-icon-full-screen'
    },
    ORIGINAL: {
        name: 'original',
        icon: 'el-icon-c-scale-to-original'
    }
};
const mousewheelEventName = isFirefox() ? 'DOMMouseScroll' : 'mousewheel';
export default {
    name: 'elImageViewer',
    props: {
        urlList: {
            type: Array,
            default: () => []
        },
        zIndex: {
            type: Number,
            default: 2000
        },
        onSwitch: {
            type: Function,
            default: () => {}
        },
        onClose: {
            type: Function,
            default: () => {}
        }
    },
    data() {
        return {
            index: 0,
            isShow: false,
            infinite: true,
            loading: false,
            mode: Mode.CONTAIN,
            transform: {
                scale: 1,
                deg: 0,
                offsetX: 0,
                offsetY: 0,
                enableTransition: false
            }
        };
    },
    computed: {
        isSingle() {
            return this.urlList.length <= 1;
        },
        isFirst() {
            return this.index === 0;
        },
        isLast() {
            return this.index === this.urlList.length - 1;
        },
        currentImg() {
            return this.urlList[this.index];
        },
        imgStyle() {
            const { scale, deg, offsetX, offsetY, enableTransition } = this.transform;
            const style = {
                transform: `scale(${scale}) rotate(${deg}deg)`,
                transition: enableTransition ? 'transform .3s' : '',
                'margin-left': `${offsetX}px`,
                'margin-top': `${offsetY}px`
            };
            if (this.mode === Mode.CONTAIN) {
                style.maxWidth = style.maxHeight = '100%';
            }
            return style;
        }
    },
    watch: {
        index: {
                handler: function(val) {
                this.reset();
                this.onSwitch(val);
            }
        },
        currentImg(val) {
            this.$nextTick(_ => {
                const $img = this.$refs.img[0];
                if (!$img.complete) {
                    this.loading = true;
                }
            });
        }
    },
    methods: {
        hide() {
            this.deviceSupportUninstall();
            this.onClose();
        },
        downloadFile(url, filename) {
            let that = this
            url=url+`?${Math.random()}`
            this.getBlob(url, function(blob) {
                that.saveAs(blob, filename);
            })
        },
        getBlob(url,cb) {
            var xhr = new XMLHttpRequest();
            xhr.open('GET', url, true);
            xhr.responseType = 'blob';
            xhr.onload = function() {
                if (xhr.status === 200) {
                    cb(xhr.response);
                }
            };
            xhr.send();
        },
        saveAs(blob, filename) {
            if (window.navigator.msSaveOrOpenBlob) {
                navigator.msSaveBlob(blob, filename);
            } else {
                var link = document.createElement('a');
                var body = document.querySelector('body');
                link.href = window.URL.createObjectURL(blob);
                link.download = filename;
                // fix Firefox
                link.style.display = 'none';
                body.appendChild(link);
                link.click();
                body.removeChild(link);
                window.URL.revokeObjectURL(link.href);
            }
        },
        deviceSupportInstall() {
            this._keyDownHandler = rafThrottle(e => {
                const keyCode = e.keyCode;
                switch (keyCode) {
                    // ESC
                    case 27:
                        this.hide();
                        break;
                    // SPACE
                    case 32:
                        this.toggleMode();
                        break;
                    // LEFT_ARROW
                        case 37:
                        this.prev();
                        break;
                    // UP_ARROW
                    case 38:
                        this.handleActions('zoomIn');
                        break;
                    // RIGHT_ARROW
                    case 39:
                        this.next();
                        break;
                    // DOWN_ARROW
                    case 40:
                        this.handleActions('zoomOut');
                        break;
                }
            });
            on(document, 'keydown', this._keyDownHandler);
          },
          deviceSupportUninstall() {
            off(document, 'keydown', this._keyDownHandler);
            this._keyDownHandler = null;
          },
          handleImgLoad(e) {
               this.loading = false;
          },
          handleImgError(e) {
                this.loading = false;
                e.target.alt = '加载失败';
          },
          handleMouseDown(e) {
                if (this.loading || e.button !== 0) return;
                const { offsetX, offsetY } = this.transform;
                const startX = e.pageX;
                const startY = e.pageY;
                this._dragHandler = rafThrottle(ev => {
                    this.transform.offsetX = offsetX + ev.pageX - startX;
                    this.transform.offsetY = offsetY + ev.pageY - startY;
                });
                on(document, 'mousemove', this._dragHandler);
                on(document, 'mouseup', ev => {
                    off(document, 'mousemove', this._dragHandler);
                    e.preventDefault();
                })
           },
           reset() {
                this.transform = {
                    scale: 1,
                    deg: 0,
                    offsetX: 0,
                    offsetY: 0,
                    enableTransition: false
                };
            },
            toggleMode() {
                if (this.loading) return;
                const modeNames = Object.keys(Mode);
                const modeValues = Object.values(Mode);
                const index = modeValues.indexOf(this.mode);
                const nextIndex = (index + 1) % modeNames.length;
                this.mode = Mode[modeNames[nextIndex]];
                this.reset();
            },
            prev() {
                if (this.isFirst && !this.infinite) return;
                const len = this.urlList.length;
                this.index = (this.index - 1 + len) % len;
            },
            next() {
                if (this.isLast && !this.infinite) return;
                const len = this.urlList.length;
                this.index = (this.index + 1) % len;
            },
            handleActions(action, options = {}) {
                if (this.loading) return;
                const { zoomRate, rotateDeg, enableTransition } = Object.assign({
                zoomRate: 0.2,
                rotateDeg: 90,
                enableTransition: true,
            }, {options});
            const { transform } = this;
            switch (action) {
                case 'zoomOut':
                    if (transform.scale > 0.2) {
                        transform.scale = parseFloat((transform.scale - zoomRate).toFixed(3));
                    }
                    break;
                case 'zoomIn':
                    transform.scale = parseFloat((transform.scale + zoomRate).toFixed(3));
                    break;
                case 'clocelise':
                    transform.deg += rotateDeg;
                    break;
                case 'anticlocelise':
                    transform.deg -= rotateDeg;
                    break;
                case 'down':
                    const fileName =this.currentImg.split('.').slice(-2)[0].split('/').slice(-1)[0].split('_')[0];
                        this.downloadFile(this.currentImg,fileName)
                    }
                    transform.enableTransition = enableTransition;
            }
        },
        mounted() {
            this.deviceSupportInstall();
        }
};
</script>

其他结构源码

dom.js 源码
import Vue from 'vue';
const isServer = Vue.prototype.$isServer;
/* istanbul ignore next */
export const on = (function() {
    if (!isServer && document.addEventListener) {
        return function(element, event, handler) {
            if (element && event && handler) {
                element.addEventListener(event, handler, false);
            }
        };
    } else {
        return function(element, event, handler) {
            if (element && event && handler) {
                element.attachEvent('on' + event, handler);
            }
        };
    }
})();

/* istanbul ignore next */
export const off = (function() {
    if (!isServer && document.removeEventListener) {
        return function(element, event, handler) {
            if (element && event) {
                element.removeEventListener(event, handler, false);
            }
        };
    } else {
        return function(element, event, handler) {
            if (element && event) {
                element.detachEvent('on' + event, handler);
            }
        };
    }
})();
util.js 源码
import Vue from 'vue';
export const isFirefox = function() {
    return !Vue.prototype.$isServer && !!window.navigator.userAgent.match(/firefox/i);
};

export function rafThrottle(fn) {
    let locked = false;
    return function(...args) {
        if (locked) return;
        locked = true;
            window.requestAnimationFrame(_ => {
                fn.apply(this, args);
                locked = false;
            });
    };
}

使用效果

image.png

总结

生长在一个日新月异的年代,vue已经迎来了vue3.x , element UI 也已经更新到了 element Plus 我们的图片预览肯定也要更新 同样的可以 可以根据项目中的 node_modules中的中 element-plus 的 image-viewer组件进行修改

vue3.x 对应的 image-viewer 查找图

image.png

image.png