话不多说,上代码!!!
此版本vue2
结构
结构写成一个组件,有两个can.vue,index.vue ,名字随便起,不写成组件也行,看需求。
can.vue
<template>
<div>
<el-button @click="huizhi">绘制</el-button>
<div class="canvasVideo">
<canvasDraw ref="cavansRegionRef"
videoUrl="http://192.168.xxx.xxx:30030/panda.mp4?1685068664632" />
</div>
</div>
</template>
<script>
import canvasDraw from "./index.vue";
export default {
name: "DialogAreaDash",
components: {
canvasDraw
},
data() {
return {
//回显数据
pointColorArr: [
[{
x: 38,
y: 75
},
{
x: 423,
y: 68
},
{
x: 408,
y: 425
},
{
x: 35,
y: 420
},
{
x: 38,
y: 75
}
],
[{
x: 543,
y: 48
},
{
x: 1039,
y: 51
},
{
x: 1049,
y: 463
},
{
x: 512,
y: 470
},
{
x: 543,
y: 48
}
]
]
};
},
mounted() {
this.huixian()
},
methods: {
huixian() {
this.$refs.cavansRegionRef.initCanvas(this.pointColorArr)
},
huizhi() {
this.$refs.cavansRegionRef.initCanvas(this.pointColorArr)
this.$refs.cavansRegionRef.startDraw = true
},
},
};
</script>
<style>
.canvasVideo {
position: relative;
margin: 0 auto;
width: 85%;
height: 600px;
}
</style>
index.vue
<template>
<div>
<div class="canvas-box solid" >
<video
ref="canvasBox"
:src="videoUrl || ''"
:id="videoId"
:autoplay="false"
controls
@mouseenter="handleEnter"
@mouseleave="handleLeave"
></video>
<!-- <slot name="video" class="video-box" ref="videoBox"></slot> -->
<!--用来和鼠标进行交互操作的canvas-->
<canvas ref="canvas" v-show="videoShow"></canvas>
<!--存储已生成的点线,避免被清空-->
<canvas
ref="canvasSave"
id="canvasSave"
@click="handleCanvasSaveClick"
@mousemove="handleCanvasSaveMove"
@mouseenter="handleEnter"
@mouseleave="handleLeave"
@mousedown="handleCanvasMouseDown"
@mouseup="handleCanvasMouseUp"
@contextmenu.prevent.stop = "handleOnmousedown"
v-show="videoShow"
>
</canvas>
<div id="del" @click="delCanvas">删除</div>
</div>
</div>
</template>
<script>
export default {
name: "DialogAreaDashIndex",
data() {
return {
coincidentDistance: 10, // 重合距离
oIndex: -1, //判断鼠标是否移动到起始点处,-1为否,1为是
pointArr: [], //存放坐标的数组
MovePointArr: [], //存放 拖拽移动时 坐标的数组
pointX: "",
pointY: "",
ctxSave: "",
canSave: "",
ctx: "",
can: "",
cloneData:[],
arrData:[],//坐标
newData:[],//处理后的坐标
scaling:1,//比例
startDraw:false,//是否操作canvas 触发‘离开展示区域,进入视频可操作视频’这个功能
fillStyle:'rgba(161,195,255,0.4)',
lineColor: "rgba(64, 158, 255, 1)",
selectStyle:'rgba(255,0,0,0.3)',
del:null,//删除dom
moveIndex:null,//选中区域的下标
videoShow:false,//true 开始画区域 false 操作视频
};
},
props: {
// background:{
// type: [String],
// default: "",
// },
videoId:{
type:String,
default:'video-Id'
},
videoUrl:{
type:String,
default:''
},
},
watch: {
},
mounted() {
},
methods: {
//初始化
async initCanvas(pointColorArr) {
if(!this.videoUrl) return;
this.videoShow = true
this.arrData = []
var video = document.getElementById(this.videoId)
this.del = document.getElementById('del')
video.oncanplay = ()=>{
let videoWidth = video.videoWidth
let videoHeight = video.videoHeight
let heights = video.clientHeight //video 的高度
let Gcd = this.getGcd(videoWidth,videoHeight) //获取最大公约数
let width = videoWidth/Gcd //宽高的比例
let height = videoHeight/Gcd //宽高的比例
let itemHeight = heights/height //1份占的值
this.scaling = (videoHeight/heights).toFixed(2) //外面高与真实高的比
let widths = itemHeight*width //得到的最后真实video宽度
this.can = this.$refs["canvas"];
this.can.width = widths;
this.can.height = heights;
// console.log(can.width, can.height);
this.ctx = this.can.getContext("2d");
this.ctx.strokeStyle = this.lineColor; //线条颜色
this.ctx.fillStyle = this.fillStyle; //填充颜色
this.canSave = this.$refs["canvasSave"];
this.canSave.width =widths;
this.canSave.height =heights;
this.ctxSave = this.canSave.getContext("2d");
this.ctxSave.strokeStyle = this.lineColor; //线条颜色
this.ctxSave.fillStyle = this.fillStyle; //填充颜色
let arrData = pointColorArr.map(i=>{
return i.map(j=>{
let obj ={
x: (Number(j.x)/this.scaling).toFixed(2),
y: (Number(j.y)/this.scaling).toFixed(2)
}
return obj
})
})
this.cloneData = JSON.parse(JSON.stringify(arrData))
let arr = arrData.map(item=>{
let obj = {
ponitArr:item,
checked:false,
color:this.fillStyle
}
return obj
})
arr.forEach(item=>{
this.moveChecked(item.ponitArr,item.color)
})
}
},
getGcd(a, b) {
let n1,n2;
if (a > b) {
n1 = a;
n2 = b;
} else {
n1 = b;
n2 = a;
}
let remainder = n1 % n2;
if (remainder === 0) {
return n2;
} else {
return this.getGcd(n2, remainder)
}
},
//绘制文字
canvasText(text, point) {
this.ctxSave.font = "20px Consolas"; //字体样式的属性
this.ctxSave.textAlign = "center"; //设置文本对齐方式
this.ctxSave.textBaseline = "middle"; //文本基线
let textWidth = this.ctxSave.measureText(text).width;
var canvasWidth = this.can.width;
this.ctxSave.fillStyle = "red"; //字体颜色
this.ctxSave.fillText(text, +point.x + 100, +point.y+100); //绘制文字
this.ctxSave.arc(point.x, point.y, 3, 0, Math.PI * 2); //基准点
},
//离开video 区域
handleLeave(){
if(this.startDraw) return
this.videoShow = true
},
//进来video 区域
handleEnter(){
if(this.startDraw) return
this.videoShow = false
},
// 计算向量叉乘
crossMul(v1, v2) {
return v1.x * v2.y - v1.y * v2.x;
},
// 判断两条线段是否相交
checkCross(p1, p2, p3, p4) {
let v1 = { x: p1.x - p3.x, y: p1.y - p3.y },
v2 = { x: p2.x - p3.x, y: p2.y - p3.y },
v3 = { x: p4.x - p3.x, y: p4.y - p3.y },
v = this.crossMul(v1, v3) * this.crossMul(v2, v3);
v1 = { x: p3.x - p1.x, y: p3.y - p1.y };
v2 = { x: p4.x - p1.x, y: p4.y - p1.y };
v3 = { x: p2.x - p1.x, y: p2.y - p1.y };
return v <= 0 && this.crossMul(v1, v3) * this.crossMul(v2, v3) <= 0
? true
: false;
},
// 判断点是否在多边形内
checkPP(point, polygon) {
let p1, p2, p3, p4;
p1 = point;
p2 = { x: -100, y: point.y };
let count = 0;
// 对每条边都和射线作对比
for (let i = 0; i < polygon.length - 1; i++) {
p3 = polygon[i];
p4 = polygon[i + 1];
if (this.checkCross(p1, p2, p3, p4) == true) {
count++;
}
}
p3 = polygon[polygon.length - 1];
p4 = polygon[0];
if (this.checkCross(p1, p2, p3, p4) == true) {
count++;
}
// 如果为偶数 说明在多边形外面,如果为奇数,说明在多边形内部
return count % 2 == 0 ? false : true;
},
// 通过多边形的各点位获得重心
centerPoint(pointArr) {
let X = 0,
Y = 0;
for (let i = 0; i < pointArr.length; i++) {
X = X + +pointArr[i].x;
Y = Y + +pointArr[i].y;
}
X = X / pointArr.length;
Y = Y / pointArr.length;
return { x: X, y: Y };
},
nextCanvas(){
},
handleCanvasMouseUp() {
},
handleCanvasMouseDown(e) {
},
//点击
handleCanvasSaveClick(e) {
console.log(' this.arrData', this.arrData)
if(this.newData.some(i=>i.checked)){
return
}
if (e.offsetX) {
this.pointX = e.offsetX;
this.pointY = e.offsetY;
var piX, piY;
if (this.oIndex > 0 && this.pointArr.length > 0) {
piX = this.pointArr[0].x;
piY = this.pointArr[0].y;
//画点
this.makearc(
this.ctx,
piX,
piY,
this.GetRandomNum(2, 2),
0,
180,
this.lineColor
);
// console.log('piX2', piX,piY)
this.pointArr.push({ x: piX, y: piY });
this.canvasSave(this.pointArr); //保存点线同步到另一个canvas
this.saveCanvas(); //生成画布
} else {
piX = this.pointX;
piY = this.pointY;
this.makearc(
this.ctx,
piX,
piY,
this.GetRandomNum(2, 2),
0,
180,
this.lineColor
);
// console.log('piX', piX,piY)
this.pointArr.push({ x: piX, y: piY });
this.canvasSave(this.pointArr); //保存点线同步到另一个canvas
}
}
},
handleCanvasSaveMove(e) {
//处理回显数据
if(!this.startDraw) return
let arr = []
if(this.cloneData.length>0 && this.arrData.length==0){
arr = this.cloneData.map(i=>{
return i
})
}
this.cloneData = []
this.arrData= [...this.arrData,...arr]
//鼠标滑动数据处理
if(this.arrData.length>0){
this.newData = this.arrData.map((i,index)=>{
let obj = {
ponitArr:i,
checked:this.checkPP({ x: e.offsetX , y: e.offsetY,},i),
color:this.checkPP({ x: e.offsetX , y: e.offsetY,},i)?this.selectStyle:this.fillStyle
}
if(this.checkPP({ x: e.offsetX , y: e.offsetY,},i)){
this.clickMove = true;
this.pointX = e.offsetX;
this.pointY = e.offsetY;
}
return obj
})
//清除画布
this.ctxSave.clearRect(0, 0, this.can.width, this.can.height);
let checkedNum = []
//选中添加颜色
this.newData.forEach((item,index)=>{
//当前鼠标下选中的判断
if(item.checked){
checkedNum.push(item)
this.moveIndex = index
}else{
this.moveChecked(item.ponitArr,item.color)
}
})
//叠加时,选中数组最后的一个 层级最高
if(checkedNum.length>0){
if(checkedNum.length>1){
checkedNum.forEach((i,j)=>{
if(checkedNum.length-1==j){
this.moveChecked(checkedNum[checkedNum.length-1].ponitArr,checkedNum[checkedNum.length-1].color)
}else{
this.moveChecked(i.ponitArr,this.fillStyle)
}
})
}else{
this.moveChecked(checkedNum[0].ponitArr,checkedNum[0].color)
}
}else{
//鼠标离开操作
if(this.del){
this.del.style.display= 'none'
this.moveIndex = null
}
}
}
if (e.offsetX) {
this.pointX =e.offsetX;
this.pointY =e.offsetY;
var piX, piY;
/*清空画布*/
this.ctx.clearRect(0, 0, this.can.width, this.can.height);
/*鼠标下跟随的圆点*/
this.makearc(
this.ctx,
this.pointX,
this.pointY,
this.GetRandomNum(4, 4),
0,
180,
this.lineColor
);
if (this.pointArr.length > 0) {
if (
this.pointX > this.pointArr[0].x - this.coincidentDistance &&
this.pointX < this.pointArr[0].x + this.coincidentDistance &&
this.pointY > this.pointArr[0].y - this.coincidentDistance &&
this.pointY < this.pointArr[0].y + this.coincidentDistance
) {
if (this.pointArr.length > 1) {
piX = this.pointArr[0].x;
piY = this.pointArr[0].y;
// console.log(piX, piY);
this.ctx.clearRect(0, 0, this.can.width, this.can.height);
this.makearc(
this.ctx,
piX,
piY,
this.GetRandomNum(4, 4),
0,
180,
this.lineColor
);
this.oIndex = 1;
}
} else {
piX = this.pointX;
piY = this.pointY;
this.oIndex = -1;
}
/*开始绘制*/
this.ctx.beginPath();
this.ctx.moveTo(this.pointArr[0].x, this.pointArr[0].y);
if (this.pointArr.length > 1) {
for (var i = 1; i < this.pointArr.length; i++) {
this.ctx.lineTo(this.pointArr[i].x, this.pointArr[i].y);
}
}
this.ctx.lineTo(piX, piY);
this.ctx.fillStyle = this.fillStyle; //填充颜色
this.ctx.fill(); //填充
this.ctx.stroke(); //绘制
}
}
},
//点击事件 鼠标左键为0,中间件为1,右键为2;
handleOnmousedown(e){
if(e.button == 2){
//选中状态可操作
if(this.moveIndex!=null){
this.del.style.display= 'block'
this.del.style.top = e.y+'px'
this.del.style.left =e.x+'px'
}
}
},
//删除区域
delCanvas(){
this.newData.splice(this.moveIndex,1)
this.arrData.splice(this.moveIndex,1)
this.del.style.display = 'none'
//清除画布
this.ctxSave.clearRect(0, 0, this.can.width, this.can.height);
this.newData.forEach(i=>{
this.moveChecked(i.ponitArr,i?.color || this.fillStyle)
})
},
//绘制区域
moveChecked(pointArr,color){
if(pointArr.length>0){
/*开始绘制*/
this.ctxSave.beginPath();
this.ctxSave.moveTo(pointArr[0].x, pointArr[0].y);
if (pointArr.length > 1) {
for (var i = 1; i < pointArr.length; i++) {
this.ctxSave.lineTo(pointArr[i].x, pointArr[i].y);
}
}
this.ctxSave.lineTo(pointArr[0].x,pointArr[0].y);
this.ctxSave.fillStyle = color; //填充颜色
this.ctxSave.fill(); //填充
this.ctxSave.stroke(); //绘制
}
},
// 存储已生成的点线
canvasSave(pointArr) {
this.ctxSave.clearRect(0, 0, this.ctxSave.width, this.ctxSave.height);
this.ctxSave.beginPath();
if (pointArr.length > 1) {
this.ctxSave.moveTo(pointArr[0].x, pointArr[0].y);
for (var i = 1; i < pointArr.length; i++) {
this.ctxSave.lineTo(pointArr[i].x, pointArr[i].y);
this.ctxSave.fillStyle =this.fillStyle; //填充颜色
//ctxSave.fill();
this.ctxSave.stroke(); //绘制
}
this.ctxSave.closePath();
}
},
/*生成画布 结束绘画*/
saveCanvas() {
this.ctx.clearRect(0, 0, this.can.width, this.can.height);
this.ctxSave.closePath(); //结束路径状态,结束当前路径,如果是一个未封闭的图形,会自动将首尾相连封闭起来
this.ctxSave.fill(); //填充
this.ctxSave.stroke(); //绘制
this.arrData.push(this.pointArr)
this.pointArr = []
},
/*验证canvas画布是否为空函数*/
isCanvasBlank(canvas) {
var blank = document.createElement("canvas"); //创建一个空canvas对象
blank.width = canvas.width;
blank.height = canvas.height;
return canvas.toDataURL() == blank.toDataURL(); //为空 返回true
},
/*canvas生成圆点*/
GetRandomNum(Min, Max) {
var Range = Max - Min;
var Rand = Math.random();
return Min + Math.round(Rand * Range);
},
makearc(ctx, x, y, r, s, e, color) {
ctx.clearRect(0, 0, this.can.width, this.can.height); //清空画布
ctx.beginPath();
ctx.fillStyle = color;
ctx.arc(x, y, r, s, e);
ctx.fill();
},
resetCanvas(){
this.ctx.clearRect(0, 0, this.can.width, this.can.height);
this.ctxSave.clearRect(0, 0, this.canSave.width, this.canSave.height);
this.pointArr = [];
},
reset() {
if(this.ctx || this.ctxSave){
this.ctx.clearRect(0, 0, this.can.width, this.can.height);
this.ctxSave.clearRect(0, 0, this.canSave.width, this.canSave.height);
this.pointArr = [];
this.arrData = []
this.newData = []
this.cloneData = []
this.videoShow= false;//cavans 展示隐藏
this.startDraw = false
}
},
},
};
</script>
<style lang="scss" scoped>
.canvas-box {
width: 100%;
height: 100%;
> canvas,video{
position: absolute;
left: 50%;
transform: translate(-50%, -50%);
top: 50%;
z-index:99;
}
canvas{
z-index: 999;
}
#del{
display: none;
cursor: pointer;
position: fixed;
font-size: 20px;
width: 100px;
color: red;
background: #f3f3f3;
z-index: 9999;
}
video{
min-width: 660px;
max-height: 600px;
}
}
</style>