我的项目中遇到一组数据既有可能是图片,也有可能是视频,须要同时预览的状况,搜了一下,找到了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>
功败垂成