一、需求描述
实现鼠标框选区域放大显示。
- 需求1:放大
按住鼠标左键不放 ——》向右侧拖动,框选出需要放大的区域后 ——》松开鼠标 ——》框选区域放大显示 - 需求2:还原
按住鼠标左键不放 ——》向左侧拖动,框选出随意大小的区域后 ——》松开鼠标 ——》视图显示大小还原 - 需求3:多个视频
页面中,同时展示多个视频
二、实现逻辑
- 记录框选出的需要放大的区域的位置(坐标)和大小(尺寸)
- 将框选大小与原画面大小对比,计算放大倍数
- 计算放大后画面需要平移的数据,使其画面中心点位于原画面展示中心
- 还原功能,即还原画面原始大小
三、代码实现
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;
};
四、实现效果
框选区域放大效果展示