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

没找到其他的,只好自力更生,但是也没有完全自力更生。我留意到了Element Plus的Image组件是可以大图预览的,毕竟Element Plus是开源的,只要稍微改一下,对图片和视频资源做一个判断,然后分别显示img和video不就可以了。于是我找到了Element Plus的image-viewer的源码,做了一下修改,核心的修改地方如上面所说的,加了判断和video

<div class="el-image-viewer__canvas"><imgv-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"/><videocontrols="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"><divref="wrapper":tabindex="-1"class="el-image-viewer__wrapper":style="{ zIndex }"><divclass="el-image-viewer__mask"@click.self="hideOnClickModal && hide()"></div><!-- CLOSE --><spanclass="el-image-viewer__btn el-image-viewer__close"@click="hide"><i class="el-icon-close"></i></span><!-- ARROW --><template v-if="!isSingle"><spanclass="el-image-viewer__btn el-image-viewer__prev":class="{ 'is-disabled': !infinite && isFirst }"@click="prev"><i class="el-icon-arrow-left"></i></span><spanclass="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 --><divv-if="isImage"class="el-image-viewer__btn el-image-viewer__actions"><div class="el-image-viewer__actions__inner"><iclass="el-icon-zoom-out"@click="handleActions('zoomOut')"></i><iclass="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><iclass="el-icon-refresh-left"@click="handleActions('anticlocelise')"></i><iclass="el-icon-refresh-right"@click="handleActions('clocelise')"></i></div></div><!-- CANVAS --><div class="el-image-viewer__canvas"><imgv-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"/><videocontrols="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', // 37up: 'ArrowUp', // 38right: 'ArrowRight', // 39down: 'ArrowDown', // 40esc: 'Escape',delete: 'Delete',backspace: 'Backspace',
}const isFirefox = function () {return !!window.navigator.userAgent.match(/firefox/i)
}const rafThrottle = function (fn) {let locked = falsereturn function (...args) {if (locked) returnlocked = truewindow.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 = nulllet _mouseWheelHandler = nulllet _dragHandler = nullconst 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 } = propsreturn 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.valueconst 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) {// ESCcase EVENT_CODE.esc:hide()break// SPACEcase EVENT_CODE.space:toggleMode()break// LEFT_ARROWcase EVENT_CODE.left:prev()break// UP_ARROWcase EVENT_CODE.up:handleActions('zoomIn')break// RIGHT_ARROWcase EVENT_CODE.right:next()break// DOWN_ARROWcase EVENT_CODE.down:handleActions('zoomOut')break}})_mouseWheelHandler = rafThrottle((e) => {const delta = e.wheelDelta ? e.wheelDelta : -e.detailif (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) returnconst { offsetX, offsetY } = transform.valueconst startX = e.pageXconst startY = e.pageYconst divLeft = wrapper.value.clientLeftconst divRight =wrapper.value.clientLeft + wrapper.value.clientWidthconst divTop = wrapper.value.clientTopconst 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.pageXconst mouseY = e.pageYif (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) returnconst modeNames = Object.keys(Mode)const modeValues = Object.values(Mode)const currentMode = mode.value.nameconst index = modeValues.findIndex((i) => i.name === currentMode)const nextIndex = (index + 1) % modeNames.lengthmode.value = Mode[modeNames[nextIndex]]reset()}function prev() {if (isFirst.value && !props.infinite) returnconst len = props.urlList.lengthindex.value = (index.value - 1 + len) % len}function next() {if (isLast.value && !props.infinite) returnconst len = props.urlList.lengthindex.value = (index.value + 1) % len}function handleActions(action, options = {}) {if (loading.value) returnconst { 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))}breakcase 'zoomIn':transform.value.scale = parseFloat((transform.value.scale + zoomRate).toFixed(3))breakcase 'clocelise':transform.value.deg += rotateDegbreakcase 'anticlocelise':transform.value.deg -= rotateDegbreak}transform.value.enableTransition = enableTransition}watch(currentMedia, () => {nextTick(() => {const $media = media.valueif (!$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 underneathwrapper.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"><MediaViewerv-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,就没弄

Vue3 预览图片和视频相关推荐

  1. vue2.x 预览图片组件

    前段时间接了新需求,需要对图片进行预览以及文件的下载,没找到合适的组件来实现这需求,最终自己写了该组件来实现此功能,该组件可预览图片.视频,均可下载,有旋转.放大/缩小功能.[该组件没有用到第三方插件 ...

  2. Android 使用CameraX实现预览/拍照/录制视频/图片分析/对焦/缩放/切换摄像头等操作

    1. CameraX架构 看官方文档 CameraX架构 有如下这一段话 使用CameraX,借助名为"用例"的抽象概念与设备的相机进行交互. 预览 : 接受用于显示预览的Surf ...

  3. 如何截取阿里云oss的视频第一帧作为预览图片

    转载于:https://blog.csdn.net/guo_qiangqiang/article/details/108865279 oss阿里云视频如何截取第一帧作为预览图片 第一步.得到视频地址 ...

  4. Taro框架中 Image 和 Video 组件预览图片/视频时添加明显的关闭按钮以关闭全屏预览

    需求 Taro框架中 Image 和 Video 组件预览图片/视频时 添加明显的关闭按钮 以关闭全屏观看,避免用户直接操作返回后导致页面空白(原有消息记录消失 - 重新进入项目首页) [补充] 全屏 ...

  5. Android视频编辑器(二)预览、录制视频加上水印和美白磨皮效果

    前言      这是视频编辑器系列的第二篇文章,在上篇文章中,我们讲解了利用OpenGl和SurfaceView进行视频预览,MediaCodec和MeidaMuxer进行视频录制和断点续录.而这篇主 ...

  6. kitty终端ranger预览图片

    之前在macOS中使用iterm2终端用ranger预览图片正常.最近切到archlinux了,使用kitty终端模拟器.也想实现ranger中预览图片. 相关阅读: <终端中的文件管理器ran ...

  7. Java图片流导出图片为黑屏,Matisse预览图片黑屏,Glide内存溢出

    项目中要到图片.视频选择的功能,然后google了一下,找到Matisse,知乎的图片选择框架,用的人还挺多的,果断依赖gradle,然后开始我的踩坑之旅. 首先,框架本身的图片框架glide是v3版 ...

  8. 小红书图片剪裁框架+微信图片选择器+超高清大图预览+图片自定义比例剪裁,支持 UI 自定义、支持跨进程回调

    YImagePicker 项目地址:yangpeixing/YImagePicker 简介: 小红书图片剪裁框架+微信图片选择器+超高清大图预览+图片自定义比例剪裁,支持 UI 自定义.支持跨进程回调 ...

  9. 小红书多图剪裁+微信图片选择器+大图预览+图片剪裁等等 相册

    最近发现一个挺不错的开源库,推荐给大家. 简介:小红书多图剪裁+微信图片选择器+大图预览+图片剪裁(支持圆形剪裁和镂空剪裁),已适配androidQ,借鉴并升级matisse加载内核!超强定制性可轻松 ...

最新文章

  1. Android中ListView与RadioButton结合----自定义单选列表
  2. linux挂载点的容量设置
  3. Luogu P1160 【队列安排】
  4. 【推荐系统】基于图嵌入技术的推荐系统长文综述
  5. 国际软件设计文档——概要设计说明书
  6. Spring AMQP 教程
  7. jpa 连接多个mysql 数据库_SpringBoot 连接多个数据库
  8. static 和 visibility hidden 的区别
  9. Wget下载网页与镜像网站
  10. 怎么制作GIF图片并添加文字
  11. Ubuntu18.04 iso文件下载地址
  12. 双系统怎么给Linux扩容,linux/win 双系统环境下为linux扩容
  13. 随机森林模型sklearn_sklearn中的随机森林
  14. 2-1 Socket家族的基石
  15. cos47度怎么用计算机算,cos47度等于多少
  16. jquery+ajax实现分页功能
  17. php实现推广海报,php微信推广海报PHP CodeIgniter框架源码解析
  18. u盘kali linux淘宝,爱了!3 个受欢迎的 U盘Linux 发行版|Linux 中国
  19. 2019年起微信和支付宝都必须执行的新规定,你知道吗?
  20. S3C2440时钟和电源管理:空闲模式:电源管理模块断开CPU时钟FCLK,而只给外设提供时钟,CPU不耗时钟,故而减少功耗,任何中断请求都可将CPU从空闲模式唤醒。

热门文章

  1. 记事本写的html文件保存到c:\inetpub\wwwroot失败
  2. Java获取世界各国各城市代码_java获取中国城市代码 中国城市ID
  3. 使用cmd进入指定文件夹目录
  4. 输入商品单价和商品数量(输入负数时代表输入结束),自动计算商品总价,若支付金额不足会提示生育应付金额
  5. 机器学习基础(四)预测方法(分类回归)概述
  6. 从本地数据库查询全国省市县信息
  7. dfp方法例题_最优化之DFP算法考试题
  8. 解决Visual Studio C4996报错
  9. 发一个android系统手机的屏幕校准软件
  10. 摹客RP,新增轮播图组件