我的项目中遇到一组数据既有可能是图片,也有可能是视频,须要同时预览的状况,搜了一下,找到了vue-gallery,试了一下之后发现没法在VUE3下没法用,不晓得是真的齐全没法用,还是因为我用的Composition API才没法用,没去纠结。

没找到其余的,只好自力更生,然而也没有齐全自力更生。我留意到了Element Plus的Image组件是能够大图预览的,毕竟Element Plus是开源的,只有略微改一下,对图片和视频资源做一个判断,而后别离显示img和video不就能够了。于是我找到了Element Plus的image-viewer的源码,做了一下批改,外围的批改中央如下面所说的,加了判断和video

<div class="el-image-viewer__canvas">    <img        v-for="(url, i) in urlList"        v-show="i === index && isImage"        ref="media"        :key="url"        :src="url"        :style="mediaStyle"        class="el-image-viewer__img"        @load="handleMediaLoad"        @error="handleMediaError"        @mousedown="handleMouseDown"    />    <video        controls="controls"        v-for="(url, i) in urlList"        v-show="i === index && isVideo"        ref="media"        :key="url"        :src="url"        :style="mediaStyle"        class="el-image-viewer__img"        @load="handleMediaLoad"        @error="handleMediaError"        @mousedown="handleMouseDown"    ></video></div>

而后把图片预览的相干操作比方放大放大旋转等工具条在视频的时候给暗藏,把Element Plus的局部ts语法改成js,局部工具函数给拿进去,事件函数on和off给重写下,就完事了,残缺代码如下

<template>    <transition name="viewer-fade">        <div            ref="wrapper"            :tabindex="-1"            class="el-image-viewer__wrapper"            :style="{ zIndex }"        >            <div                class="el-image-viewer__mask"                @click.self="hideOnClickModal && hide()"            ></div>            <!-- CLOSE -->            <span                class="el-image-viewer__btn el-image-viewer__close"                @click="hide"            >                <i class="el-icon-close"></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"></i>                </span>                <span                    class="el-image-viewer__btn el-image-viewer__next"                    :class="{ 'is-disabled': !infinite && isLast }"                    @click="next"                >                    <i class="el-icon-arrow-right"></i>                </span>            </template>            <!-- ACTIONS -->            <div                v-if="isImage"                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>                </div>            </div>            <!-- CANVAS -->            <div class="el-image-viewer__canvas">                <img                    v-for="(url, i) in urlList"                    v-show="i === index && isImage"                    ref="media"                    :key="url"                    :src="url"                    :style="mediaStyle"                    class="el-image-viewer__img"                    @load="handleMediaLoad"                    @error="handleMediaError"                    @mousedown="handleMouseDown"                />                <video                    controls="controls"                    v-for="(url, i) in urlList"                    v-show="i === index && isVideo"                    ref="media"                    :key="url"                    :src="url"                    :style="mediaStyle"                    class="el-image-viewer__img"                    @load="handleMediaLoad"                    @error="handleMediaError"                    @mousedown="handleMouseDown"                ></video>            </div>        </div>    </transition></template><script>import { computed, ref, onMounted, watch, nextTick } from 'vue'const EVENT_CODE = {    tab: 'Tab',    enter: 'Enter',    space: 'Space',    left: 'ArrowLeft', // 37    up: 'ArrowUp', // 38    right: 'ArrowRight', // 39    down: 'ArrowDown', // 40    esc: 'Escape',    delete: 'Delete',    backspace: 'Backspace',}const isFirefox = function () {    return !!window.navigator.userAgent.match(/firefox/i)}const rafThrottle = function (fn) {    let locked = false    return function (...args) {        if (locked) return        locked = true        window.requestAnimationFrame(() => {            fn.apply(this, args)            locked = false        })    }}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'const CLOSE_EVENT = 'close'const SWITCH_EVENT = 'switch'export default {    name: 'MediaViewer',    props: {        urlList: {            type: Array,            default: () => [],        },        zIndex: {            type: Number,            default: 2000,        },        initialIndex: {            type: Number,            default: 0,        },        infinite: {            type: Boolean,            default: true,        },        hideOnClickModal: {            type: Boolean,            default: false,        },    },    emits: [CLOSE_EVENT, SWITCH_EVENT],    setup(props, { emit }) {        let _keyDownHandler = null        let _mouseWheelHandler = null        let _dragHandler = null        const loading = ref(true)        const index = ref(props.initialIndex)        const wrapper = ref(null)        const media = ref(null)        const mode = ref(Mode.CONTAIN)        const transform = ref({            scale: 1,            deg: 0,            offsetX: 0,            offsetY: 0,            enableTransition: false,        })        const isSingle = computed(() => {            const { urlList } = props            return urlList.length <= 1        })        const isFirst = computed(() => {            return index.value === 0        })        const isLast = computed(() => {            return index.value === props.urlList.length - 1        })        const currentMedia = computed(() => {            return props.urlList[index.value]        })        const isVideo = computed(() => {            const currentUrl = props.urlList[index.value]            return currentUrl.endsWith('.mp4')        })        const isImage = computed(() => {            const currentUrl = props.urlList[index.value]            return currentUrl.endsWith('.jpg') || currentUrl.endsWith('.png')        })        const mediaStyle = computed(() => {            const { scale, deg, offsetX, offsetY, enableTransition } =                transform.value            const style = {                transform: `scale(${scale}) rotate(${deg}deg)`,                transition: enableTransition ? 'transform .3s' : '',                marginLeft: `${offsetX}px`,                marginTop: `${offsetY}px`,            }            if (mode.value.name === Mode.CONTAIN.name) {                style.maxWidth = style.maxHeight = '100%'            }            return style        })        function hide() {            deviceSupportUninstall()            emit(CLOSE_EVENT)        }        function deviceSupportInstall() {            _keyDownHandler = rafThrottle((e) => {                switch (e.code) {                    // ESC                    case EVENT_CODE.esc:                        hide()                        break                    // SPACE                    case EVENT_CODE.space:                        toggleMode()                        break                    // LEFT_ARROW                    case EVENT_CODE.left:                        prev()                        break                    // UP_ARROW                    case EVENT_CODE.up:                        handleActions('zoomIn')                        break                    // RIGHT_ARROW                    case EVENT_CODE.right:                        next()                        break                    // DOWN_ARROW                    case EVENT_CODE.down:                        handleActions('zoomOut')                        break                }            })            _mouseWheelHandler = rafThrottle((e) => {                const delta = e.wheelDelta ? e.wheelDelta : -e.detail                if (delta > 0) {                    handleActions('zoomIn', {                        zoomRate: 0.015,                        enableTransition: false,                    })                } else {                    handleActions('zoomOut', {                        zoomRate: 0.015,                        enableTransition: false,                    })                }            })            document.addEventListener('keydown', _keyDownHandler, false)            document.addEventListener(                mousewheelEventName,                _mouseWheelHandler,                false            )        }        function deviceSupportUninstall() {            document.removeEventListener('keydown', _keyDownHandler, false)            document.removeEventListener(                mousewheelEventName,                _mouseWheelHandler,                false            )            _keyDownHandler = null            _mouseWheelHandler = null        }        function handleMediaLoad() {            loading.value = false        }        function handleMediaError(e) {            loading.value = false        }        function handleMouseDown(e) {            if (loading.value || e.button !== 0) return            const { offsetX, offsetY } = transform.value            const startX = e.pageX            const startY = e.pageY            const divLeft = wrapper.value.clientLeft            const divRight =                wrapper.value.clientLeft + wrapper.value.clientWidth            const divTop = wrapper.value.clientTop            const divBottom =                wrapper.value.clientTop + wrapper.value.clientHeight            _dragHandler = rafThrottle((ev) => {                transform.value = {                    ...transform.value,                    offsetX: offsetX + ev.pageX - startX,                    offsetY: offsetY + ev.pageY - startY,                }            })            document.addEventListener('mousemove', _dragHandler, false)            document.addEventListener(                'mouseup',                (e) => {                    const mouseX = e.pageX                    const mouseY = e.pageY                    if (                        mouseX < divLeft ||                        mouseX > divRight ||                        mouseY < divTop ||                        mouseY > divBottom                    ) {                        reset()                    }                    document.removeEventListener(                        'mousemove',                        _dragHandler,                        false                    )                },                false            )            e.preventDefault()        }        function reset() {            transform.value = {                scale: 1,                deg: 0,                offsetX: 0,                offsetY: 0,                enableTransition: false,            }        }        function toggleMode() {            if (loading.value) return            const modeNames = Object.keys(Mode)            const modeValues = Object.values(Mode)            const currentMode = mode.value.name            const index = modeValues.findIndex((i) => i.name === currentMode)            const nextIndex = (index + 1) % modeNames.length            mode.value = Mode[modeNames[nextIndex]]            reset()        }        function prev() {            if (isFirst.value && !props.infinite) return            const len = props.urlList.length            index.value = (index.value - 1 + len) % len        }        function next() {            if (isLast.value && !props.infinite) return            const len = props.urlList.length            index.value = (index.value + 1) % len        }        function handleActions(action, options = {}) {            if (loading.value) return            const { zoomRate, rotateDeg, enableTransition } = {                zoomRate: 0.2,                rotateDeg: 90,                enableTransition: true,                ...options,            }            switch (action) {                case 'zoomOut':                    if (transform.value.scale > 0.2) {                        transform.value.scale = parseFloat(                            (transform.value.scale - zoomRate).toFixed(3)                        )                    }                    break                case 'zoomIn':                    transform.value.scale = parseFloat(                        (transform.value.scale + zoomRate).toFixed(3)                    )                    break                case 'clocelise':                    transform.value.deg += rotateDeg                    break                case 'anticlocelise':                    transform.value.deg -= rotateDeg                    break            }            transform.value.enableTransition = enableTransition        }        watch(currentMedia, () => {            nextTick(() => {                const $media = media.value                if (!$media.complete) {                    loading.value = true                }            })        })        watch(index, (val) => {            reset()            emit(SWITCH_EVENT, val)        })        onMounted(() => {            deviceSupportInstall()            // add tabindex then wrapper can be focusable via Javascript            // focus wrapper so arrow key can't cause inner scroll behavior underneath            wrapper.value?.focus?.()        })        return {            index,            wrapper,            media,            isSingle,            isFirst,            isLast,            currentMedia,            isImage,            isVideo,            mediaStyle,            mode,            handleActions,            prev,            next,            hide,            toggleMode,            handleMediaLoad,            handleMediaError,            handleMouseDown,        }    },}</script>

应用

<teleport to="body">    <MediaViewer        v-if="previewState.isShow"        :z-index="1000"        :initial-index="previewState.index"        :url-list="previewState.srcList"        :hide-on-click-modal="true"        @close="closeViewer"    /></teleport>

功败垂成

留神:我在外面间接用了Elment Plus的款式,如果要独自应用还得把这些款式也给提取进去,因为是scss我的我的项目没有用,要提取有点麻烦而且我原本就用的Element Plus,就没弄