最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Vue的彈出框實現(xiàn)圖片預覽和視頻預覽功能

 更新時間:2025年08月26日 09:26:54   作者:逢場作戲2  
本文介紹基于Vue3的媒體預覽組件,支持圖片與視頻預覽,具備縮放、旋轉、拖拽功能,采用ElementUI Dialog容器,通過計算屬性實現(xiàn)動態(tài)樣式,優(yōu)化拖拽性能,自動調整方向,便于集成使用

VUE 的彈出框實現(xiàn)圖片預覽和視頻預覽

這是一個基于Vue3封裝的媒體預覽組件,主要功能包括:

  1. 多格式支持:可同時預覽圖片和視頻
  2. 圖片操作功能
    • 縮放(支持滾輪縮放和按鈕控制)
    • 旋轉(90度增量旋轉)
    • 拖拽(僅在放大狀態(tài)下可用)
  3. 自適應顯示:圖片自動適應容器大小
  4. 響應式設計:使用Element UI的Dialog作為容器

組件特點:

  • 通過計算屬性動態(tài)計算圖片樣式
  • 使用requestAnimationFrame優(yōu)化拖拽性能
  • 支持圖片加載后自動調整方向
  • 提供視頻播放控制功能

該組件封裝了完整的交互邏輯,可方便地集成到項目中實現(xiàn)媒體預覽功能。

下面是實現(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;
  // 拖拽相關變量
  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; // 只有在放大時才能拖拽
    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';
    }
    // 阻止默認行為,防止圖片被選中
    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;
    });
    // 阻止默認行為
    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('翻轉', degree, (imageRotation.value + degree) % 360);
    imageRotation.value += degree;
    // zoomIn();
    // 旋轉時重置縮放級別以避免布局問題
    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;
    // 計算基礎縮放比例,確保圖片適應容器
    const baseScale = Math.min(dialogWidth / displayWidth, dialogHeight / displayHeight);
    // 應用用戶縮放級別
    const finalScale = baseScale * zoomLevel.value;
    // 計算縮放后的尺寸
    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 = () => {
    // 重置旋轉和縮放
    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)
    }
    // 可選:調試用
    // 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>

補充:vue圖片預覽

vue圖片預覽

vue結合vant實現(xiàn)圖片視頻預覽

1. 給圖片或視頻添加點擊事件

<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.準備一個彈框:

<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準備變量:

data() {
	return {
		popubIsShow: false,
		img_src: ''
		};
	},

4.定義方法:

showPopup(src, type) {
	this.img_src = src
	this.popubIsShow = true;
	this.popup_type = type
},

ok,解決

到此這篇關于VUE 的彈出框實現(xiàn)圖片預覽和視頻預覽的文章就介紹到這了,更多相關vue彈出框內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Vue3數(shù)據(jù)更新,頁面沒有同步更新的問題及解決

    Vue3數(shù)據(jù)更新,頁面沒有同步更新的問題及解決

    這篇文章主要介紹了Vue3數(shù)據(jù)更新,頁面沒有同步更新的問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • vue+F2生成折線圖的方法

    vue+F2生成折線圖的方法

    這篇文章主要為大家詳細介紹了vue+F2生成折線圖的方法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • 淺談Vuex@2.3.0 中的 state 支持函數(shù)申明

    淺談Vuex@2.3.0 中的 state 支持函數(shù)申明

    這篇文章主要介紹了淺談Vuex@2.3.0 中的 state 支持函數(shù)申明,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-11-11
  • vue3如何通過ref獲取元素離頂部的距離

    vue3如何通過ref獲取元素離頂部的距離

    這篇文章主要給大家介紹了關于vue3如何通過ref獲取元素離頂部的距離,文中通過代碼以及圖文介紹的非常詳細,對大家學習或者使用vue3具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-10-10
  • vue路由相對路徑跳轉方式

    vue路由相對路徑跳轉方式

    這篇文章主要介紹了vue路由相對路徑跳轉方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • Vue-router跳轉和location.href的區(qū)別及說明

    Vue-router跳轉和location.href的區(qū)別及說明

    Vue?Router是Vue.js官方的路由管理器,它允許我們通過定義路由來管理應用程序的不同視圖和狀態(tài),Vue路由跳轉主要有以下幾種方式:<router-link>標簽、this.$router.push方法、this.$router.replace方法和this.$router.go方法
    2025-01-01
  • 多頁vue應用的單頁面打包方法(內含打包模式的應用)

    多頁vue應用的單頁面打包方法(內含打包模式的應用)

    這篇文章主要介紹了多頁vue應用的單頁面打包方法(內含打包模式的應用),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-06-06
  • vue的傳參方式匯總和router使用技巧

    vue的傳參方式匯總和router使用技巧

    這篇文章主要介紹了vue的傳參方式和router使用技巧,本文給大家列舉了好幾種傳參方式,需要的朋友可以參考下
    2018-05-05
  • vue項目中字符串換行顯示方式(返回的數(shù)據(jù)包含‘\r\n’字符)

    vue項目中字符串換行顯示方式(返回的數(shù)據(jù)包含‘\r\n’字符)

    這篇文章主要介紹了vue項目中字符串換行顯示方式(返回的數(shù)據(jù)包含‘\r\n’字符),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • 關于electron-vue打包后運行白屏的解決方案

    關于electron-vue打包后運行白屏的解決方案

    這篇文章主要介紹了關于electron-vue打包后運行白屏的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-10-10

最新評論

花莲市| 利川市| 永和县| 锡林郭勒盟| 荣成市| 铜山县| 曲阳县| 项城市| 花莲市| 邛崃市| 甘泉县| 边坝县| 金川县| 黄山市| 藁城市| 丰原市| 镇康县| 澄迈县| 高青县| 舟曲县| 泗阳县| 秀山| 鹰潭市| 宜良县| 咸宁市| 长海县| 锦屏县| 邛崃市| 云南省| 襄汾县| 临江市| 宝鸡市| 辽阳县| 平乐县| 拜城县| 政和县| 赣州市| 呼玛县| 曲靖市| 徐闻县| 德昌县|