Vue的彈出框?qū)崿F(xiàn)圖片預(yù)覽和視頻預(yù)覽功能
VUE 的彈出框?qū)崿F(xiàn)圖片預(yù)覽和視頻預(yù)覽
這是一個(gè)基于Vue3封裝的媒體預(yù)覽組件,主要功能包括:
- 多格式支持:可同時(shí)預(yù)覽圖片和視頻
- 圖片操作功能:
- 縮放(支持滾輪縮放和按鈕控制)
- 旋轉(zhuǎn)(90度增量旋轉(zhuǎn))
- 拖拽(僅在放大狀態(tài)下可用)
- 自適應(yīng)顯示:圖片自動(dòng)適應(yīng)容器大小
- 響應(yīng)式設(shè)計(jì):使用Element UI的Dialog作為容器
組件特點(diǎn):
- 通過計(jì)算屬性動(dòng)態(tài)計(jì)算圖片樣式
- 使用requestAnimationFrame優(yōu)化拖拽性能
- 支持圖片加載后自動(dòng)調(diào)整方向
- 提供視頻播放控制功能
該組件封裝了完整的交互邏輯,可方便地集成到項(xiàng)目中實(shí)現(xiàn)媒體預(yù)覽功能。
下面是實(shí)現(xiàn)代碼:
<template>
<el-dialog v-model="visible" width="1184px" class="preview-dialog" close align-center>
<template v-if="!isVideoPreview" #footer>
<div class="preview-dialog-footer">
<el-button type="text" @click="zoomOut" class="zoom-button">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
<line x1="8" y1="11" x2="14" y2="11"></line>
</svg>
</el-button>
<el-button type="text" @click="zoomIn" class="zoom-button">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
<line x1="11" y1="8" x2="11" y2="14"></line>
<line x1="8" y1="11" x2="14" y2="11"></line>
</svg>
</el-button>
<el-button type="text" @click="rotateImage(90)" class="rotate-button">
<img :src="Rotate" />
</el-button>
</div>
</template>
<div class="preview-content" @wheel="handleWheel">
<img
v-if="!isVideoPreview"
:src="previewUrl"
@load="onImageLoad"
:style="imageStyle"
ref="previewImage"
@mousedown="startDrag"
@mousemove="onDrag"
@mouseup="endDrag"
@mouseleave="endDrag"
/>
<video v-if="isVideoPreview" :src="previewUrl" class="media-video" controls autoplay></video>
</div>
</el-dialog>
</template>
<script setup>
import { ref, computed, watch } from 'vue';
import Rotate from '@/assets/home/icon/rotate.svg';
const props = defineProps({
modelValue: Boolean,
previewUrl: {
type: String,
default: ''
},
isVideoPreview: Boolean
});
const emit = defineEmits(['update:modelValue']);
const visible = ref(props.modelValue);
watch(
() => props.modelValue,
(newVal) => {
visible.value = newVal;
}
);
watch(visible, (val) => {
emit('update:modelValue', val);
});
const imageRotation = ref(0);
const previewImage = ref(null);
const dialogWidth = 1184;
const dialogHeight = 648;
const zoomLevel = ref(1);
// 縮放限制
const minZoom = 0.1;
const maxZoom = 5;
// 拖拽相關(guān)變量
const isDragging = ref(false);
const dragStartX = ref(0);
const dragStartY = ref(0);
const imageStartLeft = ref(0);
const imageStartTop = ref(0);
const imageLeft = ref(0);
const imageTop = ref(0);
const rafId = ref(0);
const zoomIn = () => {
if (zoomLevel.value < maxZoom) {
zoomLevel.value = Math.min(zoomLevel.value + 0.1, maxZoom);
}
};
const zoomOut = () => {
if (zoomLevel.value > minZoom) {
zoomLevel.value = Math.max(zoomLevel.value - 0.1, minZoom);
}
};
const handleWheel = (event) => {
event.preventDefault();
if (event.deltaY < 0) {
zoomIn();
} else {
zoomOut();
}
};
const startDrag = (event) => {
if (zoomLevel.value <= 1) return; // 只有在放大時(shí)才能拖拽
isDragging.value = true;
dragStartX.value = event.clientX;
dragStartY.value = event.clientY;
imageStartLeft.value = imageLeft.value;
imageStartTop.value = imageTop.value;
if (previewImage.value) {
previewImage.value.style.cursor = 'grabbing';
}
// 阻止默認(rèn)行為,防止圖片被選中
event.preventDefault();
};
const onDrag = (event) => {
if (!isDragging.value || zoomLevel.value <= 1) return;
// 使用 requestAnimationFrame 優(yōu)化性能
if (rafId.value) {
cancelAnimationFrame(rafId.value);
}
rafId.value = requestAnimationFrame(() => {
const deltaX = event.clientX - dragStartX.value;
const deltaY = event.clientY - dragStartY.value;
imageLeft.value = imageStartLeft.value + deltaX;
imageTop.value = imageStartTop.value + deltaY;
rafId.value = 0;
});
// 阻止默認(rèn)行為
event.preventDefault();
};
const endDrag = () => {
isDragging.value = false;
if (rafId.value) {
cancelAnimationFrame(rafId.value);
rafId.value = 0;
}
if (previewImage.value) {
previewImage.value.style.cursor = 'grab';
}
};
const rotateImage = (degree) => {
console.log('翻轉(zhuǎn)', degree, (imageRotation.value + degree) % 360);
imageRotation.value += degree;
// zoomIn();
// 旋轉(zhuǎn)時(shí)重置縮放級(jí)別以避免布局問題
zoomLevel.value = 1;
// 重置拖拽位置
imageLeft.value = 0;
imageTop.value = 0;
};
const imageDimensions = computed(() => {
if (!previewImage.value) return { width: 0, height: 0 };
const img = previewImage.value;
const naturalWidth = img.naturalWidth;
const naturalHeight = img.naturalHeight;
const isRotated = imageRotation.value % 180 !== 0;
const displayWidth = isRotated ? naturalHeight : naturalWidth;
const displayHeight = isRotated ? naturalWidth : naturalHeight;
return { width: displayWidth, height: displayHeight };
});
const imageStyle = computed(() => {
if (!previewImage.value) return {};
const { width: displayWidth, height: displayHeight } = imageDimensions.value;
// 計(jì)算基礎(chǔ)縮放比例,確保圖片適應(yīng)容器
const baseScale = Math.min(dialogWidth / displayWidth, dialogHeight / displayHeight);
// 應(yīng)用用戶縮放級(jí)別
const finalScale = baseScale * zoomLevel.value;
// 計(jì)算縮放后的尺寸
const scaledWidth = displayWidth * finalScale;
const scaledHeight = displayHeight * finalScale;
// 居中定位
const left = (dialogWidth - scaledWidth) / 2 + imageLeft.value;
const top = (dialogHeight - scaledHeight) / 2 + imageTop.value;
return {
position: 'absolute',
left: `${left}px`,
top: `${top}px`,
width: `${scaledWidth}px`,
height: `${scaledHeight}px`,
transform: `rotate(${imageRotation.value}deg)`,
transformOrigin: 'center center',
cursor: zoomLevel.value > 1 ? 'grab' : 'default'
};
});
const onImageLoad = () => {
// 重置旋轉(zhuǎn)和縮放
imageRotation.value = 0;
zoomLevel.value = 1;
imageLeft.value = 0;
imageTop.value = 0;
for (let index = 0; index < 4; index++) {
console.log('執(zhí)行第幾次', index + 1);
rotateImage(90); //執(zhí)行四次 可以讓圖片以合適的寬度呈現(xiàn)
}
// 可選:調(diào)試用
// console.log('Image loaded:', previewImage.value.naturalWidth, previewImage.value.naturalHeight);
};
</script>
<style lang="scss" scoped>
.preview-dialog {
:deep(.el-dialog) {
height: 648px;
display: flex;
flex-direction: column;
}
:deep(.el-dialog__body) {
flex: 1;
overflow: hidden !important;
text-align: center;
padding: 0;
position: relative;
}
.preview-dialog-footer {
display: flex;
justify-content: center;
align-items: center;
}
.rotate-button {
font-size: 20px;
padding: 10px;
}
.preview-content {
width: 1184px;
height: 648px;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
position: relative;
img {
max-width: none;
max-height: none;
object-fit: contain;
user-select: none;
// 添加硬件加速
transform: translateZ(0);
backface-visibility: hidden;
perspective: 1000px;
}
.media-video {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
}
}
</style>補(bǔ)充:vue圖片預(yù)覽
vue圖片預(yù)覽
vue結(jié)合vant實(shí)現(xiàn)圖片視頻預(yù)覽
1. 給圖片或視頻添加點(diǎn)擊事件
<div class="comment_image_box_list" v-for="(imgitem, imgindex) in item.img_url" :key="imgindex+'i'"> <img :src="imgitem" alt="" @click="showPopup(imgitem, 1)" /> </div> <div class="comment_image_box_list" v-for="(vdoitem, vdoindex) in item.video_url" :key="vdoindex+ 'v'"> <video :src="vdoitem" @click="showPopup(vdoitem, 2)" :poster="vdoitem + '?x-oss-process=video/snapshot,t_1000,f_jpg,w_800,h_600,m_fast'"></video> </div>
2.準(zhǔn)備一個(gè)彈框:
<van-popup v-model="popubIsShow"> <img class="popubImg" :src="img_src" alt="" v-if="popup_type == 1" /> <video class="popubImg" :src="img_src" :poster="img_src + '?x-oss-process=video/snapshot,t_1000,f_jpg,w_800,h_600,m_fast'" controls v-if="popup_type == 2"></video> </van-popup>
3.data準(zhǔn)備變量:
data() {
return {
popubIsShow: false,
img_src: ''
};
},4.定義方法:
showPopup(src, type) {
this.img_src = src
this.popubIsShow = true;
this.popup_type = type
},ok,解決
到此這篇關(guān)于VUE 的彈出框?qū)崿F(xiàn)圖片預(yù)覽和視頻預(yù)覽的文章就介紹到這了,更多相關(guān)vue彈出框內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 如何通過Vue3+Element?Plus自定義彈出框組件
- vue2 element 彈出框拖拽會(huì)出現(xiàn)一層陰影問題解決方法
- vue 彈出框 引入另一個(gè)vue頁(yè)面的示例代碼
- vue如何實(shí)現(xiàn)簡(jiǎn)易的彈出框
- vue實(shí)現(xiàn)簡(jiǎn)單的登錄彈出框
- vue組件實(shí)現(xiàn)彈出框點(diǎn)擊顯示隱藏效果
- Vue實(shí)現(xiàn)圖片預(yù)覽功能的詳細(xì)指南
- vue實(shí)現(xiàn)圖片預(yù)覽放大以及縮小問題
- Vue.js實(shí)現(xiàn)對(duì)視頻預(yù)覽的示例代碼
- Vue+ElementUI實(shí)現(xiàn)文件照片音頻視頻預(yù)覽功能
相關(guān)文章
Vue3數(shù)據(jù)更新,頁(yè)面沒有同步更新的問題及解決
這篇文章主要介紹了Vue3數(shù)據(jù)更新,頁(yè)面沒有同步更新的問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
淺談Vuex@2.3.0 中的 state 支持函數(shù)申明
這篇文章主要介紹了淺談Vuex@2.3.0 中的 state 支持函數(shù)申明,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧2017-11-11
Vue-router跳轉(zhuǎn)和location.href的區(qū)別及說(shuō)明
Vue?Router是Vue.js官方的路由管理器,它允許我們通過定義路由來(lái)管理應(yīng)用程序的不同視圖和狀態(tài),Vue路由跳轉(zhuǎn)主要有以下幾種方式:<router-link>標(biāo)簽、this.$router.push方法、this.$router.replace方法和this.$router.go方法2025-01-01
多頁(yè)vue應(yīng)用的單頁(yè)面打包方法(內(nèi)含打包模式的應(yīng)用)
這篇文章主要介紹了多頁(yè)vue應(yīng)用的單頁(yè)面打包方法(內(nèi)含打包模式的應(yīng)用),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06
vue項(xiàng)目中字符串換行顯示方式(返回的數(shù)據(jù)包含‘\r\n’字符)
這篇文章主要介紹了vue項(xiàng)目中字符串換行顯示方式(返回的數(shù)據(jù)包含‘\r\n’字符),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04
關(guān)于electron-vue打包后運(yùn)行白屏的解決方案
這篇文章主要介紹了關(guān)于electron-vue打包后運(yùn)行白屏的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10

