关于vue.js:Vue3-预览图片和视频

66次阅读

共计 8660 个字符,预计需要花费 22 分钟才能阅读完成。

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

正文完
 0