canvas 画区域,现有功能支持画多区域,删除选中单独某个区域。后续增加拖拽等

canvas 画区域,现有功能支持画多区域,删除选中单独某个区域。后续增加拖拽等

话不多说,上代码!!!
此版本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>
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值