【vue3】js + css 实现 视频框选放大:局部细节放大、放大镜效果

一、需求描述

实现鼠标框选区域放大显示。

  • 需求1:放大
    按住鼠标左键不放 ——》向右侧拖动,框选出需要放大的区域后 ——》松开鼠标 ——》框选区域放大显示
  • 需求2:还原
    按住鼠标左键不放 ——》向左侧拖动,框选出随意大小的区域后 ——》松开鼠标 ——》视图显示大小还原
  • 需求3:多个视频
    页面中,同时展示多个视频
    放大示例

二、实现逻辑

  1. 记录框选出的需要放大的区域的位置(坐标)和大小(尺寸)
  2. 将框选大小与原画面大小对比,计算放大倍数
  3. 计算放大后画面需要平移的数据,使其画面中心点位于原画面展示中心
  4. 还原功能,即还原画面原始大小

三、代码实现

1、html 部分

<template>
	<div id="all" class="all">
	  <div v-for="item in obj" :key="item.id" class="video" :id="'video_' + item.id">
	    <div @mousedown="
	        (e) =>
	          downBig(
	            e,
	            item.amplify,
	            'rectArea' + item.id,
	            'videoMonitor_' + item.id,
	            'video_' + item.id
	          )
	      " @mousemove="(e) => move(e, item.amplify)">
	
	      <!-- 视频 start -->
	      <video class="video-info" :id="'videoMonitor_' + item.id" muted src="./img/工匠视频.mp4"></video>
	      <!-- 视频 end -->
	
	      <!-- 拖拽选择框 start -->
	      <div :ref="'rectArea' + item.id" class="rect"></div>
	      <!-- 拖拽选择框 end -->
	
	    </div>
	
	    <!-- 视频恢复原始大小角标 start -->
	    <div 
	    	class="reduction" 
	    	v-if="item.amplify.videoZoomFlag" 
	    	@click="rest(item.amplify, 'videoMonitor_' + item.id)">
	      还原
	    </div>
	    <!-- 视频恢复原始大小角标 end -->
	
	  </div>
	</div>
</template>

2、css 部分(less)

	.all {
	  display: flex;
	  align-items: center;
	  width: 100%;
	  position: relative;
	}
	
	.video {
	  border: 2px solid #000000;
	  box-sizing: border-box;
	  background: rgba(80, 80, 80);
	
	  display: block;
	  width: 50%;
	  height: 0;
	  padding-top: 28%;
	  position: relative;
	  overflow: hidden;
	
	  // “还原”角标
	  .reduction {
	    width: 10%;
	    background: #4e89ff73;
	    color: #fff;
	    position: absolute;
	    right: 0;
	    top: 0;
	    text-align: center;
	    cursor: pointer;
	  }
	
	  //视频
	  .video-info {
	    position: absolute;
	    top: 0;
	    left: 0;
	    width: 100%;
	    height: 100%;
	  }
	  // 拖拽框
	  .rect {
	    position: absolute;
	    border: 2px solid red;
	    left: 0px;
	    top: 0px;
	    width: 0px;
	    height: 0px;
	    background-color: transparent;
	    visibility: hidden;
	    z-index: 100;
	  }
	}

3、js 部分

import { getCurrentInstance } from "vue";
const internalInstance = getCurrentInstance();
const obj= [
  {
    id: "A",
    // 视频or放大后的视频
    amplify: {
      videoZoomFlag: false, //是否展示放大后的视频
      // 视频播放窗口起始点位置
      top: 0,
      left: 0,
      // 记录鼠标按下时的坐标
      downX: 0,
      downY: 0,
      // 记录鼠标抬起时候的坐标
      mouseX2: 0,
      mouseY2: 0,
      //拖拽选择框DOM元素
      rect: null, // 拖拽选择框节点rectArea
      // 是否需要(允许)处理鼠标的移动事件,默认识不处理
      select: false,
      // 监控局部放大请求数据
      rectInfo: {
        videoWidth: 0, //播放界面的宽
        videoHeight: 0,
        rectWidth: 0, //选择框的宽
        rectHeight: 0,
        rectCenterOffsetX: 0, //选择框中心坐标
        rectCenterOffsetY: 0,
      },
      showControl: true, // 是否显示控制条:拖拽选区时不显示
      bigVideoBig: false, //当前是否为放大视频
    },
  }
];

/**
 * @description: 鼠标按下
 * @param {*} e 事件
 * @param {*} amplify 对应的amplify对象
 * @param {*} rectArea 对应的ref rectAreaA
 * @param {*} videoZoom 对应的id名称 videoMonitor_A
 * @param {*} video_AB 对应的视频盒子的id video_A
 * @return {*}
 */
const downBig = async (e, amplify, rectArea, videoZoom, video_AB) => {
  amplify.showControl = false; // 暂时不显示控制条
  // 获取鼠标按下时的坐标位置
  amplify.downX = e.clientX; //鼠标相对于浏览器有效区域x轴的位置
  amplify.downY = e.clientY;
  // 鼠标按下时才允许处理鼠标的移动事件
  amplify.select = true;
  // internalInstance.refs[rectArea][0]:获取“rectArea”节点
  amplify.rect = internalInstance.refs[rectArea][0];
  // 播放器窗口离浏览器窗口顶部距离
  amplify.top = document
    .getElementById(video_AB)
    .getBoundingClientRect().top;
  // 播放器窗口离浏览器窗口左侧距离
  amplify.left = document
    .getElementById(video_AB)
    .getBoundingClientRect().left;
  // 获取播放器窗口大小
  amplify.rectInfo.videoHeight =
    document.getElementById(video_AB).offsetHeight;
  amplify.rectInfo.videoWidth =
    document.getElementById(video_AB).offsetWidth;
  // 添加鼠标抬起事件
  document._params = { amplify, videoZoom }; // 传递监听事件所需参数
  document.addEventListener("mouseup", up);
};

/**
 * @description: 鼠标移动
 * @param {*} e 事件
 * @param {*} amplify 对应的amplify对象
 * @return {*}
 */
const move = async (e, amplify) => {
  if (amplify.select) {
    // 获取鼠标移动时的坐标位置
    amplify.mouseX2 = e.clientX; //鼠标相对于浏览器有效区域x轴的位置
    amplify.mouseY2 = e.clientY;

    // A(左上) part
    if (
      amplify.mouseX2 < amplify.downX &&
      amplify.mouseY2 < amplify.downY
    ) {
      amplify.rect.style.left = amplify.mouseX2 - amplify.left + "px";
      amplify.rect.style.top = amplify.mouseY2 - amplify.top + "px";
      amplify.videoZoomFlag = false;
    }
    // B(右上) part
    if (
      amplify.mouseX2 > amplify.downX &&
      amplify.mouseY2 < amplify.downY
    ) {
      amplify.rect.style.left = amplify.downX - amplify.left + "px";
      amplify.rect.style.top = amplify.mouseY2 - amplify.top + "px";
      amplify.videoZoomFlag = false;
    }
    // C(左下) part
    if (
      amplify.mouseX2 < amplify.downX &&
      amplify.mouseY2 > amplify.downY
    ) {
      amplify.rect.style.left = amplify.mouseX2 - amplify.left + "px";
      amplify.rect.style.top = amplify.downY - amplify.top + "px";
      amplify.videoZoomFlag = false;
    }
    // D(右下) part
    if (
      amplify.mouseX2 > amplify.downX &&
      amplify.mouseY2 > amplify.downY
    ) {
      amplify.rect.style.left = amplify.downX - amplify.left + "px";
      amplify.rect.style.top = amplify.downY - amplify.top + "px";
      amplify.videoZoomFlag = true;
    }
    // 选择框大小
    amplify.rect.style.width =
      Math.abs(amplify.mouseX2 - amplify.downX) + "px";
    amplify.rect.style.height =
      Math.abs(amplify.mouseY2 - amplify.downY) + "px";
    // 选择框显示
    amplify.rect.style.visibility = "visible";
  }
};

/**
 * @description: 鼠标抬起
 * @param {*} amplify 对应的amplify对象
 * @param {*} videoZoom 对应的id名称 videoMonitor_A
 * @return {*}
 */
const up = async () => {
  // 获取监听事件传递的参数
  let { amplify, videoZoom } = document._params;
  amplify.showControl = true; // 选区完成后,显示控制条
  //鼠标抬起后不允许处理鼠标移动事件
  amplify.select = false;
  if (amplify.rect.style.visibility !== "hidden") {
    //获取选择框大小
    amplify.rectInfo.rectWidth = Math.abs(amplify.mouseX2 - amplify.downX);
    amplify.rectInfo.rectHeight = Math.abs(amplify.mouseY2 - amplify.downY);

    //获取选择框中心坐标
    amplify.rectInfo.rectCenterOffsetX =
      parseInt(amplify.rect.style.left) + amplify.rectInfo.rectWidth / 2;
    amplify.rectInfo.rectCenterOffsetY =
      parseInt(amplify.rect.style.top) + amplify.rectInfo.rectHeight / 2;

    //框选区域大小按视频播放窗口宽高比转换使框选部分放大后显示不失真:保持按播放宽高等比变化
    let rectRate = amplify.rectInfo.rectWidth / amplify.rectInfo.rectHeight;
    let videoRate =
      amplify.rectInfo.videoWidth / amplify.rectInfo.videoHeight;
    // 情况一:框选宽高比小于播放窗口宽高比:使用播放窗口比率统一框选宽度
    if (rectRate < videoRate) {
      amplify.rectInfo.rectWidth = amplify.rectInfo.rectHeight * videoRate;
      // 框选部分在播放窗口左侧边缘的情况
      if (
        amplify.rectInfo.rectCenterOffsetX <
        amplify.rectInfo.rectWidth / 2
      ) {
        amplify.rectInfo.rectCenterOffsetX = amplify.rectInfo.rectWidth / 2;
      }
      // 框选部分在播放窗口右侧边缘的情况
      if (
        amplify.rectInfo.rectCenterOffsetX +
          amplify.rectInfo.rectWidth / 2 >
        amplify.rectInfo.videoWidth
      ) {
        amplify.rectInfo.rectCenterOffsetX =
          amplify.rectInfo.videoWidth - amplify.rectInfo.rectWidth / 2;
      }

      // 情况二:框选宽高比大于等于播放窗口宽高比:使用播放窗口比率统一框选高度
    } else {
      amplify.rectInfo.rectHeight = amplify.rectInfo.rectWidth / videoRate;
      // 处理框选部分在播放窗口顶部边
      if (
        amplify.rectInfo.rectCenterOffsetY <
        amplify.rectInfo.rectHeight / 2
      ) {
        amplify.rectInfo.rectCenterOffsetY =
          amplify.rectInfo.rectHeight / 2;
      }
      // 处理框选部分在播放窗口底部边
      if (
        amplify.rectInfo.rectCenterOffsetY +
          amplify.rectInfo.rectHeight / 2 >
        amplify.rectInfo.videoHeight
      ) {
        amplify.rectInfo.rectCenterOffsetY =
          amplify.rectInfo.videoHeight - amplify.rectInfo.rectHeight / 2;
      }
    }
    //  处理视频
    handleVideo(amplify, videoZoom);
  }
  //重置选择框
  resetRect(amplify, videoZoom);
};

/**
 * @description: 视频处理
 * @param {*} amplify 对应的amplify对象
 * @param {*} videoZoom 对应的id名称 videoMonitor_A
 * @return {*}
 */
const handleVideo = async (amplify, videoZoom) => {
  // 视频放大显示
  if (
    amplify.rectInfo.videoWidth / amplify.rectInfo.rectWidth <= 10 &&
    amplify.videoZoomFlag
  ) {
    let videoZoomEle = document.getElementById(videoZoom);
    // 放大倍数
    let times = amplify.rectInfo.videoWidth / amplify.rectInfo.rectWidth;

    /* 1、当前视频为放大后视频 */
    if (amplify.bigVideoBig) {
      // 视频放大后大小
      videoZoomEle.style.width =
        (
          ((videoZoomEle.offsetWidth * times) /
            amplify.rectInfo.videoWidth) *
          100
        ).toFixed(2) + "%";
      videoZoomEle.style.height =
        (
          ((videoZoomEle.offsetHeight * times) /
            amplify.rectInfo.videoHeight) *
          100
        ).toFixed(2) + "%";

      // 移动放大后视频使框选区域显示在原播放窗口
      videoZoomEle.style.top =
        (
          ((((videoZoomEle.style.top.split("%")[0] / 100) *
            amplify.rectInfo.videoHeight -
            (amplify.rectInfo.rectCenterOffsetY -
              amplify.rectInfo.rectHeight / 2)) *
            times) /
            amplify.rectInfo.videoHeight) *
          100
        ).toFixed(2) + "%";
      videoZoomEle.style.left =
        (
          ((((videoZoomEle.style.left.split("%")[0] / 100) *
            amplify.rectInfo.videoWidth -
            (amplify.rectInfo.rectCenterOffsetX -
              amplify.rectInfo.rectWidth / 2)) *
            times) /
            amplify.rectInfo.videoWidth) *
          100
        ).toFixed(2) + "%";
    } else {
      /* 2、当前视频为正常大小视频 */
      amplify.bigVideoBig = true;
      // 放大后视频大小
      videoZoomEle.style.width = 100 * times + "%";
      videoZoomEle.style.height = 100 * times + "%";

      // 移动放大后视频使框选区域显示在原播放窗口
      videoZoomEle.style.top =
        (
          ((-(
            amplify.rectInfo.rectCenterOffsetY -
            amplify.rectInfo.rectHeight / 2
          ) *
            times) /
            amplify.rectInfo.videoHeight) *
          100
        ).toFixed(2) + "%";
      videoZoomEle.style.left =
        -(
          (((amplify.rectInfo.rectCenterOffsetX -
            amplify.rectInfo.rectWidth / 2) *
            times) /
            amplify.rectInfo.videoWidth) *
          100
        ).toFixed(2) + "%";
    }
  } else {
    // 显示默认视频大小
    rest(amplify, videoZoom);
  }
};

/**
 * @description: 重置选择框
 * @return {*}
 */
const resetRect = async (amplify, videoZoom) => {
  // 移除监听事件
  document._params.amplify = amplify;
  document._params.videoZoom = videoZoom;
  document.removeEventListener("mouseup", up);
  // 重置相关数据
  amplify.rect.style.visibility = "hidden";
  amplify.rect.style.width = "0px";
  amplify.rect.style.height = "0px";
  amplify.top = 0;
  amplify.left = 0;
  amplify.downX = 0;
  amplify.downY = 0;
  amplify.mouseX2 = 0;
  amplify.mouseY2 = 0;
  amplify.rect = null;
  amplify.select = false;
  amplify.rectInfo = {
    videoWidth: 0,
    videoHeight: 0,
    rectWidth: 0,
    rectHeight: 0,
    rectCenterOffsetX: 0,
    rectCenterOffsetY: 0,
  };
  amplify.showControl = true;
};

/**
 * @description: 还原视频初始大小
 * @param {*} amplify 对应的amplify对象
 * @param {*} videoZoom 对应的id名称 videoMonitor_A
 * @return {*}
 */
const rest = (amplify, videoZoom) => {
  amplify.videoZoomFlag = false;
  amplify.bigVideoBig = false;

  // 还原视频原始大小
  document.getElementById(videoZoom).style.width = "100%";
  document.getElementById(videoZoom).style.height = "100%";
  document.getElementById(videoZoom).style.top = 0;
  document.getElementById(videoZoom).style.left = 0;
};

四、实现效果

框选区域放大效果展示

五、单视频demo文件

demo资源

六、参考博文

vue2 实现视频局部放大功能

  • 27
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
可以实现吗? 这个问题需要具体了解vue3和element UI的实现方式。在element UI中,对话框组件默认是不支持拖动内容框进行放大的。不过,我们可以通过自定义对话框的样式和事件监听来实现这个功能。 具体实现方式如下: 1. 通过自定义样式设置对话框的resize属性为both,使其能够支持拖动边框改变大小。 2. 监听对话框边框拖放的事件,获取拖动开始时对话框的高度和宽度。 3. 根据拖动距离计算出对话框需要改变的高度和宽度。 4. 更新对话框的高度和宽度。 下面是一个简单的示例代码: ```html <template> <el-dialog class="custom-dialog" :resize="true" :visible.sync="dialogVisible" :width="dialogWidth" :height="dialogHeight" :title="dialogTitle" @resize-start="onResizeStart" @resize-stop="onResizeStop" > 对话框内容... </el-dialog> </template> <script> export default { data() { return { dialogVisible: true, dialogWidth: '50%', dialogHeight: '50%', dialogTitle: '自定义对话框' } }, methods: { onResizeStart() { this.dialogStartHeight = this.$refs.dialog.$el.offsetHeight this.dialogStartWidth = this.$refs.dialog.$el.offsetWidth this.mouseStartX = event.clientX this.mouseStartY = event.clientY }, onResizeStop() { const distanceX = event.clientX - this.mouseStartX const distanceY = event.clientY - this.mouseStartY const newHeight = (this.dialogStartHeight + distanceY) + 'px' const newWidth = (this.dialogStartWidth + distanceX) + 'px' this.dialogHeight = newHeight this.dialogWidth = newWidth } } } </script> <style> .custom-dialog .el-dialog__wrapper { resize: both !important; } </style> ``` 需要注意的是,这只是一个简单的示例代码,实际开发中还需要考虑更多的细节问题,比如边界处理、拖动时的性能等。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值