vue3使用canvas实现图层的移动、缩放、旋转等其他编辑操作

一、Canvas的点击区域检测以及如何监听Canvas上各种图形的点击事件

1、利用数学的力量

一些简单有规则的图形,比如矩形,椭圆,多边形......我们可以使用一些数学函数来计算判断。

这种方式非常棒,当你的画布上没有大量的图形时,他可能是非常快的。

但是这种方式很难处理那些非常复杂的几何图形。比如说,你正在使用具有二次曲线的线。

2、模拟点击区域

点击区域的思路很简单,我们只需要获取点击区域的像素,并且找到拥有相同颜色的图形即可。

但是这种方式可能无效,因为不同的图形可能拥有相同的颜色。为了避免这种问题,我们应该创建一个隐藏的“点击canvas画布”,它将跟主canvas画布拥有几乎相同的图形,并且每一个图形都拥有唯一的颜色。因此我们需要对每一个圆圈生成随机的颜色。

然后,我们需要绘制每个图形2次。第一次在主画布上(可见的),然后在“点击canvas画布”上(不可见)。

当你点击主canvas时,你需要做的就是获取到你点击处的位置坐标,然后在“点击canvas画布”上找到跟主cavnas同样位置的像素的颜色。

这种方式最主要的瓶颈在于你需要绘制2次。因此性能可能下降2倍!

但是我们可以简化hitCanvas的绘制,比如,跳过shadows或者strokes绘制,简化图形,比如,用矩形来代替文本。简化绘制后的方式可能是非常快的。因为从canvas上去一像素和从一个颜色hash对象(colorsHash)中取值是非常快的操作。

// 主canvas
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
 
// 点击canvas
const hitCanvas = document.createElement('canvas');
const hitCtx = hitCanvas.getContext('2d');
 
// 颜色对象
const colorsHash = {};
 
// 生成随机颜色
function getRandomColor() {
 const r = Math.round(Math.random() * 255);
 const g = Math.round(Math.random() * 255);
 const b = Math.round(Math.random() * 255);
 return `rgb(${r},${g},${b})`;
}
 
 
// 要绘制的图形对象数组
const circles = [{
  id: '1', x: 40, y: 40, radius: 10, color: 'rgb(255,0,0)'
}, {
  id: '2', x: 100, y: 70, radius: 10, color: 'rgb(0,255,0)'
}];
 
circles.forEach(circle => {
  while(true) {
     const colorKey = getRandomColor();

     if (!colorsHash[colorKey]) {
        circle.colorKey = colorKey;

        colorsHash[colorKey] = circle;

        return;
     }
  }
});
 
// 绘制图形
circles.forEach(circle => {
  // 主canvas
  ctx.beginPath();
  ctx.arc(circle.x, circle.y, circle.radius, 0, 2 * Math.PI, false);
  ctx.fillStyle = circle.color;
  ctx.fill();
  
  // 点击canvas
  hitCtx.beginPath();
  hitCtx.arc(circle.x, circle.y, circle.radius, 0, 2 * Math.PI, false);
  hitCtx.fillStyle = circle.colorKey;
  hitCtx.fill();
});
 
 
// 监听点击事件
canvas.addEventListener('click', (e) => {
  // 获取鼠标点击位置坐标
  const mousePos = {
    x: e.clientX - canvas.offsetLeft,
    y: e.clientY - canvas.offsetTop
  };

  // 获取点击位置的像素
  const pixel = hitCtx.getImageData(mousePos.x, mousePos.y, 1, 1).data;
  const color = `rgb(${pixel[0]},${pixel[1]},${pixel[2]})`;
  const shape = colorsHash[color];

  // 判断是否颜色对象为空
  if (shape) {
     alert('click on circle: ' + shape.id);
  }
});

扫描二维码关注公众号,回复: 16345887 查看本文章

二、给JSON对象排序

众所周知,json对象是没有顺序的。只有数组才有排序功能。

但我们遇到的业务场景里面,不仅仅需要对数组排序,也有需要对对象排序的情况。

let data = {
    a: {age: 18, height: 189}, 
    b: {age: 18, height: 175}
}
let map = new Map()

for (let k in data) {
    map.set(k, data[k])
}

let arrayObj = Array.from(map)

// 根据 age 排序
arrayObj.sort((a, b) => {
    return b[1]['age'] -a[1]['age']
})

此时会获得一个新的数组,你打印出来发现,格式变了,但我们想要的还是一开始那样的json格式,那就再把它转回来就好了。

let obj = {}

for (let i in arrayObj) {
   let k = arrayObj[i][0]
   let value = arrayObj[i][1]
   obj[k] = value
}

想要转成map的话,可以如下:

var result = new Map(arrayObj.map(i => [i[0], i[1]]));

 三、画板分辨率转换

// 默认分辨率
let defaultRealResolutionPower = '720*1280'

// 转化分辨率
function formResolutionPower(resolutionPower: String = defaultRealResolutionPower) {

      // 分辨率
      if (resolutionPower) {
        let _sarr = resolutionPower.split("*");

        let _arr = [Number(_sarr[0]), Number(_sarr[1])];

        // 设置在画板里绘制的实际分辨率
        this.realResolutionPower = _arr

        // 获取包裹canvas的外层标签的属性
        const winBox = document.getElementById('winBox')?.getBoundingClientRect()

        // 判断横竖屏
        if (_arr[0] > _arr[1]) {
          this.sDpi = _arr[0] / winBox?.width
          // 编辑分辨率
          this.resolutionPower = JSON.parse(JSON.stringify([715, _arr[1] / this.sDpi]))
        } else {
          this.sDpi = _arr[1] / winBox?.height
          // 编辑分辨率
          this.resolutionPower = JSON.parse(JSON.stringify([_arr[0] / this.sDpi, 512]))
        }

        this.$nextTick(() => {
          const canvasBox = document.getElementById('canvasBox')?.getBoundingClientRect()

          // 设置模版可编辑区域的宽高和左上角坐标
          this.toucheWrap = {
            width: this.resolutionPower[0],
            height: this.resolutionPower[1],
            top: canvasBox.top,
            left: canvasBox.left
          }

          console.log(this.toucheWrap)

        })

        this.callBack('formResolutionPower')

      }
}

四、绘制主画布和点击画布

        <canvas 
          id="canvas" 
          ref="canvas" 
          style="display: block; width: 100%; height: 100%;"
          @mousedown.left.stop.prevent="wrapStart($event)" 
          @mousemove.left.stop.prevent="wrapMove($event)"
          @mouseup.stop.prevent="wrapUp($event)" 
          @mouseleave.stop.prevent="wrapUp($event)"
          @mousedown.right.stop.prevent="wrapStart($event, true)"></canvas>
    // 初始化画板
    initCanvas() {
      console.log('初始化画板', this.realResolutionPower)

      // 真实画板
      if(!this.canvas) {
        this.canvas = document.getElementById('canvas')
        this.ctx = this.canvas.getContext('2d')
      }
      this.canvas.width = this.realResolutionPower[0]
      this.canvas.height = this.realResolutionPower[1]

      // 模拟画板
      if(!this._canvas) {
        this._canvas = document.createElement('canvas')
        this._ctx = this._canvas.getContext('2d')
      }
      this._canvas.width = this.realResolutionPower[0]
      this._canvas.height = this.realResolutionPower[1]

      // 初始化video
      if(!this._video) {
        this._video = document.createElement('video')
        this._video.setAttribute('autoplay', true)
        this._video.setAttribute('loop', true)
      }

    },

    // 生成随机颜色
    getRandomColor() {
      const r = Math.round(Math.random() * 255)
      const g = Math.round(Math.random() * 255)
      const b = Math.round(Math.random() * 255)
      // const a = Math.round(Math.random() * 255)
      return `rgb(${r},${g},${b})`
    },

    // 绘制视频片段
    drawSpPartItem() {
      const _items = JSON.parse(JSON.stringify(this.items))

      _items.sort((a, b) => a.zIndex - b.zIndex)
      // console.log('绘制图层:', _items)

      this.ctx.clearRect(0, 0, this.realResolutionPower[0], this.realResolutionPower[1])
      this._ctx.clearRect(0, 0, this.realResolutionPower[0], this.realResolutionPower[1])

      this._tcList = [] // 模拟点击的图层列表

      let activeItem = null;

      for (let i = 0; i < _items.length; i++) {
        let item = _items[i]

        if (!item.display || !item.show) {
          continue
        }

        if (['bg', 'tt', 'ai'].includes(item.type)) {

          // 视频
          if (item.iobsType == 'video') {
            if (this._video.src !== item.content) {
              this._video.src = item.content
              console.log(this._video)
            }

            this.ctx.drawImage(this._video, item.left, item.top, item.width, item.height)
          } else {
            // 图片
            const img = new Image()
            // img.crossOrigin = 'anonymous'
            img.src = item.content
            // const img = await this.loadImage(item.content)

            this.ctx.drawImage(img, item.left, item.top, item.width, item.height)
          
          }

          // 锁定
          if(!item.lock) {
            // 绘制模拟canvas
            const _color = this.getRandomColor()
            this._tcList.push({
              id: item.id,
              color: _color
            })
            this._ctx.save()
            this._ctx.fillStyle = _color
            this._ctx.fillRect(item.left, item.top, item.width, item.height)
            this._ctx.restore()
          }
          

        } else if (['zm', 'bt'].includes(item.type)) {

          this.ctx.save()
          this.ctx.font = item.fontSize + 'px ' + item.fontFamily
          this.ctx.textAlign = item.align

          let showStroke = false
          if (item.strokeShow && item.strokeSize) {
            this.ctx.strokeStyle = item.strokeColor
            this.ctx.lineWidth = item.strokeSize
            showStroke = true
          }

          this.ctx.textBaseline = 'top'

          this.ctx.fillStyle = item.fontBgColor

          // const measureText = this.ctx.newMeasureText(item.content)

          // 绘制文字背景色
          this.ctx.fillRect(item.left, item.top, item.width, item.height)

          // 绘制文字
          this.ctx.fillStyle = item.fontColor

          const _content = item.type == 'zm' ? '智能字幕' : item.content

          // this.ctx.strokeText(item.content, item.left, item.top)
          if (item.align == 'center') {
            this.ctx.wrapText(showStroke, _content, item.left + item.width / 2, item.top, item.width, item.fontSize)
          } 
          else if (item.align == 'right') {
            this.ctx.wrapText(showStroke, _content, item.left + item.width, item.top, item.width, item.fontSize)
          } 
          else {
            this.ctx.wrapText(showStroke, _content, item.left, item.top, item.width, item.fontSize)
          }
          this.ctx.restore()

          // 锁定
          if(!item.lock) {
            // 绘制模拟canvas
            const _color = this.getRandomColor()
            this._tcList.push({
              id: item.id,
              color: _color
            })
            this._ctx.save()
            this._ctx.fillStyle = _color
            this._ctx.fillRect(item.left, item.top, item.width, item.height)
            this._ctx.restore()
          }
        }

        // 绘制编辑框
        if (item.active && !item.lock) {
          activeItem = item
        }

      }

      if (activeItem) {
        this.drawEditBox(activeItem)
      }
    },

    // 绘制图层
    async drawItems() {
      cancelAnimationFrame(this.requestAnimationFrameId)

      if(this.previewStatus === -1) {
        this.drawSpPartItem()
      } else {
        this.drawPreview()
      }

      this.requestAnimationFrameId = requestAnimationFrame(this.drawItems)
    },

    // 绘制编辑边框
    drawEditBox(item) {

      this.ctx.save()
      const r = 10 // 圆半径
      const w = 20 // 矩形宽度

      // 绘制矩形
      this.ctx.lineWidth = 5
      this.ctx.strokeStyle = '#779BF1'
      this.ctx.strokeRect(item.left, item.top, item.width, item.height)

      // 阴影
      this.ctx.shadowBlur = 5;
      this.ctx.shadowColor = "#779BF1";

      let _color
      /**
       * 
       * 拉伸按钮
       * 
       */

      if(item.type === 'zm') {

        // 中上
        this.ctx.fillStyle = 'white'
        this.ctx.fillRect(item.left + item.width/2 - w/2, item.top - w/2, w, w)

        // 绘制模拟canvas
        _color = this.getRandomColor()
        this._tcList.push({
          type: 'lh-btn',
          cursor: 'ns-resize', // 上下
          id: item.id,
          color: _color
        })
        this._ctx.fillStyle = _color
        this._ctx.fillRect(item.left + item.width/2 - w/2, item.top - w/2, w, w)


        // 中下
        this.ctx.fillStyle = 'white'
        this.ctx.fillRect(item.left + item.width/2 - w/2, item.top + item.height - w/2, w, w)

        // 绘制模拟canvas
        _color = this.getRandomColor()
        this._tcList.push({
          type: 'lh-btn',
          cursor: 'ns-resize', // 上下
          id: item.id,
          color: _color
        })
        this._ctx.fillStyle = _color
        this._ctx.fillRect(item.left + item.width/2 - w/2, item.top + item.height - w/2, w, w)


        // 中左
        this.ctx.fillStyle = 'white'
        this.ctx.fillRect(item.left - w/2, item.top + item.height/2 - w/2, w, w)

        // 绘制模拟canvas
        _color = this.getRandomColor()
        this._tcList.push({
          type: 'lw-btn',
          cursor: 'ew-resize', // 左右
          id: item.id,
          color: _color
        })
        this._ctx.fillStyle = _color
        this._ctx.fillRect(item.left - w/2, item.top + item.height/2 - w/2, w, w)


        // 中右
        this.ctx.fillStyle = 'white'
        this.ctx.fillRect(item.left + item.width - w/2, item.top + item.height/2 - w/2, w, w)

        // 绘制模拟canvas
        _color = this.getRandomColor()
        this._tcList.push({
          type: 'lw-btn',
          cursor: 'ew-resize', // 左右
          id: item.id,
          color: _color
        })
        this._ctx.fillStyle = _color
        this._ctx.fillRect(item.left + item.width - w/2, item.top + item.height/2 - w/2, w, w)
      }

      /**
       * 
       *  缩放按钮
       * 
       * */ 

      // 左上
      this.ctx.beginPath()
      this.ctx.arc(item.left, item.top, r, 0, 2 * Math.PI)
      this.ctx.stroke()
      this.ctx.fillStyle = 'white'
      this.ctx.fill()

      // 绘制模拟canvas
      _color = this.getRandomColor()
      this._tcList.push({
        type: 's-btn',
        cursor: 'nwse-resize', // 左上角
        id: item.id,
        color: _color
      })
      this._ctx.beginPath()
      this._ctx.arc(item.left, item.top, r, 0, 2 * Math.PI)
      this._ctx.stroke()
      this._ctx.fillStyle = _color
      this._ctx.fill()


      // 右上
      this.ctx.beginPath()
      this.ctx.arc(item.left + item.width, item.top, r, 0, 2 * Math.PI)
      this.ctx.stroke()
      this.ctx.fillStyle = 'white'
      this.ctx.fill()

      // 绘制模拟canvas
      _color = this.getRandomColor()
      this._tcList.push({
        type: 's-btn',
        cursor: 'nesw-resize', // 右上角
        id: item.id,
        color: _color
      })
      this._ctx.beginPath()
      this._ctx.arc(item.left + item.width, item.top, r, 0, 2 * Math.PI)
      this._ctx.stroke()
      this._ctx.fillStyle = _color
      this._ctx.fill()


      // 左下
      this.ctx.beginPath()
      this.ctx.arc(item.left, item.top + item.height, r, 0, 2 * Math.PI)
      this.ctx.stroke()
      this.ctx.fillStyle = 'white'
      this.ctx.fill()

      // 绘制模拟canvas
      _color = this.getRandomColor()
      this._tcList.push({
        type: 's-btn',
        cursor: 'nesw-resize', // 左下角
        id: item.id,
        color: _color
      })
      this._ctx.beginPath()
      this._ctx.arc(item.left, item.top + item.height, r, 0, 2 * Math.PI)
      this._ctx.stroke()
      this._ctx.fillStyle = _color
      this._ctx.fill()


      // 右下
      this.ctx.beginPath()
      this.ctx.arc(item.left + item.width, item.top + item.height, r, 0, 2 * Math.PI)
      this.ctx.stroke()
      this.ctx.fillStyle = 'white'
      this.ctx.fill()

      // 绘制模拟canvas
      _color = this.getRandomColor()
      this._tcList.push({
        type: 's-btn',
        cursor: 'nwse-resize', // 右下角
        id: item.id,
        color: _color
      })
      this._ctx.beginPath()
      this._ctx.arc(item.left + item.width, item.top + item.height, r, 0, 2 * Math.PI)
      this._ctx.stroke()
      this._ctx.fillStyle = _color
      this._ctx.fill()


      this.ctx.restore()
    },

五、判断点击区域

    // 鼠标开始点击
    wrapStart(e, _isRightClick = false) {

      // 右键点击
      if(_isRightClick) {
        console.log('右键点击')
        this.mousePos = {
          x: e.clientX,
          y: e.clientY
        }

        this.showRmenu = true
      }

      // 判断画板上的点击区域
      const mousePos = {
        x: (e.pageX - this.toucheWrap.left) * this.sDpi,
        y: (e.pageY - this.toucheWrap.top) * this.sDpi
      }

      const pixel = this._ctx.getImageData(mousePos.x, mousePos.y, 1, 1).data
      const color = `rgb(${pixel[0]},${pixel[1]},${pixel[2]})`

      for (let i = 0; i < this._tcList.length; i++) {
        if (this._tcList[i].color == color) {
          const id = this._tcList[i].id
          // 缩放按钮
          if (this._tcList[i].type == 's-btn') {
            this.oTouchStart(e, id)
          } 
          // 拉伸(高度)按钮
          else if(this._tcList[i].type == 'lh-btn') {
            this.oLwhStart(e, id, 'h')
          } 
          // 拉伸(宽度)按钮
          else if(this._tcList[i].type == 'lw-btn') {
            this.oLwhStart(e, id, 'w')
          }
          else {
            // 点击图层
            this.wraptouchStart(e, id, _isRightClick)
          }

          break
        }
      }

      this.drag = true;
    },
    // 鼠标放开
    wrapUp(e) {
      // console.log("wrapUp");
      this.drag = false;
      this.oTouchUp();

      this.xMLine = false;
      this.yMLine = false;
    },
    // 鼠标取消
    oTouchUp(e, id) {
      if (this.items && this.items[this.index]) {
        // console.log("oTouchUp");
        this.items[this.index].lMoveabled = false;
        this.items[this.index].sMoveabled = false;
        this.items[this.index].moveabled = false;
      }
    },
    // 点击图层
    wraptouchStart(e, id, _isRightClick) {

      console.log("点击图层", e, e.target.focus);

      // 循环图片数组获取点击的图片信息
      for (let i = 0; i < this.items.length; i++) {
        this.items[i].active = false;
        this.items[i].moveabled = false;
        this.items[i].sMoveabled = false;

        if (id == this.items[i].id) {

          // 不是编辑封面的时候
          if(!this.isCoverEdit) {

            this.selectSpPartIndex = this.spPartIndex
            this.selectItemId = id
          }

          this.canvas.style.cursor = 'move'

          this.index = i
          this.items[this.index].active = true
          this.items[this.index].moveabled = true

          if (this.items[this.index].isEdit) {
            e.stopPropagation()
          } else {
            e.preventDefault()
          }
        }
      }

      let editType = "";

      if (this.items[this.index].type == "bg") {
        editType = "bg";
      } else if (this.items[this.index].type == "ai") {
        editType = "ai";
      } else if (this.items[this.index].type == "tt") {
        editType = "tt";
      } else if (this.items[this.index].type == "bt") {
        editType = "bt";
      } else if (this.items[this.index].type == "zm") {
        editType = "zm";
      }

      if (this.isCoverEdit) {
        this.fmItems = this.items;
      } else {
        this.spPart[this.spPartIndex] = this.items;
      }

      this.editType = editType;
      this.isInitEdit = false;

      // 获取点击的坐标值
      this.items[this.index].lx = e.pageX;
      this.items[this.index].ly = e.pageY;

      this.items = JSON.parse(JSON.stringify(this.items));

    },

六、移动图层

    // 鼠标移动
    wrapMove(e) {
      if (!this.canvas || !this.items[this.index]) {
        return
      }

      // 判断画板上的点击区域
      const mousePos = {
        x: (e.pageX - this.toucheWrap.left) * this.sDpi,
        y: (e.pageY - this.toucheWrap.top) * this.sDpi
      }

      const pixel = this._ctx.getImageData(mousePos.x, mousePos.y, 1, 1)?.data
      const color = `rgb(${pixel[0]},${pixel[1]},${pixel[2]})`

      if (!this.items[this.index]?.sMoveabled && !this.items[this.index]?.moveabled && !this.items[this.index]?.lMoveabled) {
        this.canvas.style.cursor = 'auto'

        for (let i = 0; i < this._tcList.length; i++) {
          if (this._tcList[i].color == color) {
            const id = this._tcList[i].id
            // 编辑按钮
            if (['s-btn', 'lh-btn', 'lw-btn'].includes(this._tcList[i].type)) {
              this.canvas.style.cursor = this._tcList[i].cursor
            }
            else {
              // 循环图片数组获取点击的图片信息
              for (let j = 0; j < this.items.length; j++) {
                if (this.items[j].id == id && this.items[j].active) {
                  if(this.items[j].type !== 'zm') {
                    this.canvas.style.cursor = 'move'
                  } else {
                    this.canvas.style.cursor = 'auto'
                  }
                  break
                }
              }
            }

            break
          }
        }
      }


      if (this.drag && this.items[this.index].type != "bg") {

        if (this.items[this.index].active) {
          // 缩放
          if (this.items[this.index].sMoveabled) {
            console.log("wrapMove-sMoveabled");
            this.oTouchMove(e);
          }
          // 移动
          else if (this.items[this.index].moveabled) {
            if(this.items[this.index].type !== 'zm') {
              console.log("wrapMove-moveabled")
              this.wraptouchMove(e)
            }
          }
          // 拉伸
          else if (this.items[this.index].lMoveabled) {
            console.log("wrapMove-lMoveabled");
            this.oLwhMove(
              e,
              this.items[this.index].id,
              this.items[this.index].lMoveType
            );
          }
        }
      }

    },
    // 拖动图层
    wraptouchMove(e) {
      let { items, index } = this;

      if (!items[index].moveabled) {
        return;
      }

      console.log("拖动图层", e);
      

      items[index]._lx = e.pageX;
      items[index]._ly = e.pageY;

      let _getXDistancs = null
      let _getYDistancs = null

      if (Math.round(items[index].x) == this.realResolutionPower[0] / 2) {
        this.xMLine = true;
        _getXDistancs = this.getDistancs(
          items[index].lx,
          items[index].ly,
          items[index]._lx,
          items[index]._ly
        )
      } else {
        this.xMLine = false;
      }


      if (Math.round(items[index].y) == this.realResolutionPower[1] / 2) {
        this.yMLine = true;
        _getYDistancs = this.getDistancs(
          items[index].lx,
          items[index].ly,
          items[index]._lx,
          items[index]._ly
        )
        
      } else {
        this.yMLine = false;
      }

      if((_getXDistancs != null && _getXDistancs < 20) || (_getYDistancs != null && _getYDistancs < 20)){
        return
      }

      items[index].left += (items[index]._lx - items[index].lx) * this.sDpi;
      items[index].top += (items[index]._ly - items[index].ly) * this.sDpi;
      items[index].x += (items[index]._lx - items[index].lx) * this.sDpi;
      items[index].y += (items[index]._ly - items[index].ly) * this.sDpi;

      

      items[index].lx = e.pageX;
      items[index].ly = e.pageY;

      this.items = JSON.parse(JSON.stringify(items))
      if (this.isCoverEdit) {
        this.fmItems = this.items;
      } else {
        this.spPart[this.spPartIndex] = this.items

        this.spPart = JSON.parse(JSON.stringify(this.spPart))
      }

    },

七、图层伸缩

    // 点击伸缩图标
    oTouchStart(e, id) {
      console.log("点击伸缩图标", e);

      // 找到点击的那个图片对象,并记录
      for (let i = 0; i < this.items.length; i++) {
        this.items[i].active = false;
        this.items[i].moveabled = false;
        this.items[i].sMoveabled = false;

        if (id == this.items[i].id) {
          this.index = i;
          this.items[i].active = true;
          this.items[i].sMoveabled = true;
        }
      }

      // 获取作为移动前的坐标
      this.items[this.index].tx = (e.pageX - this.toucheWrap.left) * this.sDpi;
      this.items[this.index].ty = (e.pageY - this.toucheWrap.top) * this.sDpi;

      // 获取图片半径
      this.items[this.index].r = this.getDistancs(
        this.items[this.index].x,
        this.items[this.index].y,
        this.items[this.index].tx,
        this.items[this.index].ty
      );

    },

    // 移动伸缩图标
    oTouchMove(e) {
      console.log("移动伸缩图标", e);

      let { items, index, toucheWrap, sDpi } = this;

      if (!items[index].sMoveabled) {
        return;
      }

      // 记录移动后的位置
      items[index]._tx = (e.pageX - toucheWrap.left) * this.sDpi;
      items[index]._ty = (e.pageY - toucheWrap.top) * this.sDpi;

      // 移动的点到圆心的距离
      items[index].disPtoO = this.getDistancs(
        items[index].x,
        items[index].y,
        items[index]._tx,
        items[index]._ty
      );


      let _s = items[index].disPtoO / items[index].r

      // 记录新旧数据
      let _oiW = items[index].width
      let _niW = _oiW * _s

      let _oiS = _niW / _oiW

      let _oiH = items[index].height
      let _niH = _oiS * _oiH

      // 使用缩放
      // items[index].scale = items[index].disPtoO / items[index].r;

      // 不使用缩放
      if (items[index].type === "bt" || items[index].type === "zm") {
        let _newFontSize = items[index].fontSize * _s

        let maxFontSize = items[index].type == 'zm' ? 46 : 100;
        let minFontSize = 12
        
        if(_newFontSize < minFontSize) {
          return
        } else if(_newFontSize > maxFontSize) {
          return
        } 

        let _txt = TextNode(
          items[index].type === 'zm' ? '智能字幕' : items[index].content, 
          `${_newFontSize}px ${items[index].fontFamily}`
        )
        
        _niW = _txt.width
        _niH = _txt.height

        items[index].fontSize = _newFontSize;

      }

      
      if(items[index].type === 'zm') {
        // 距离底部间距
        let _distance = this.realResolutionPower[1] * 0.1

        items[index].height = _niH

        // 中心坐标
        items[index].y = this.realResolutionPower[1] - _distance - (items[index].height/2)

        items[index].top = items[index].y - items[index].height / 2

      } else {

        items[index].width = _niW
        items[index].height = _niH
  
        items[index].top = items[index].y - items[index].height / 2
        items[index].left = items[index].x - items[index].width / 2
      }


      // 获取图片半径
      items[index].r = items[index].disPtoO;

      this.items = JSON.parse(JSON.stringify(items));
      if (this.isCoverEdit) {
        this.fmItems = this.items;
      } else {
        this.spPart[this.spPartIndex] = this.items

        this.spPart = JSON.parse(JSON.stringify(this.spPart))
      }

    },

八、拉伸宽高

    // 点击文字拉宽高图标
    oLwhStart(e, id, _type) {
      // 找到点击的那个图片对象,并记录
      for (let i = 0; i < this.items.length; i++) {
        this.items[i].active = false;
        this.items[i].moveabled = false;
        this.items[i].sMoveabled = false;
        this.items[i].lMoveabled = false;

        if (id == this.items[i].id) {
          this.index = i;
          this.items[i].active = true;
          this.items[i].lMoveabled = true;
          this.items[i].lMoveType = _type;
        }
      }

      // 获取作为移动前的坐标
      this.items[this.index].tx = (e.pageX - this.toucheWrap.left) * this.sDpi;
      this.items[this.index].ty = (e.pageY - this.toucheWrap.top) * this.sDpi;

      // 获取触摸点到圆心距离
      this.items[this.index].r = this.getDistancs(
        this.items[this.index].x,
        this.items[this.index].y,
        this.items[this.index].tx,
        this.items[this.index].ty
      );

    },

    // 移动文字拉宽高图标
    oLwhMove(e, id, _type) {
      console.log('移动文字拉宽高图标', e)

      let { items, index, toucheWrap } = this;

      if (!items[index].lMoveabled) {
        return;
      }

      // 记录移动后的位置
      items[index]._tx = (e.pageX - toucheWrap.left) * this.sDpi
      items[index]._ty = (e.pageY - toucheWrap.top) * this.sDpi

      // 移动的点到圆心的距离
      items[index].disPtoO = this.getDistancs(
        items[index].x,
        items[index].y,
        items[index]._tx,
        items[index]._ty
      );

      let _s = items[index].disPtoO / items[index].r;

      // 拉宽度
      if (_type === "w") {
        let _sW = _s * items[index].width

        if(['zm', 'bt'].includes(items[index].type)){
          let _txt = TextNode(
            items[index].type === 'zm' ? '智能字幕' : items[index].content, 
            `${items[index].fontSize}px ${items[index].fontFamily}`, 
            '', 
            items[index].height+'px'
          )
          
          if(_txt.width >= _sW) {
            return
          }
        }

        items[index].width = _sW
        items[index].left = items[index].x - items[index].width / 2;
      } 
      // 拉高度
      else {

        let _sH = _s * items[index].height

        if(['zm', 'bt'].includes(items[index].type)){
          let _txt = TextNode(
            items[index].type === 'zm' ? '智能字幕' : items[index].content,  
            `${items[index].fontSize}px ${items[index].fontFamily}`, 
            items[index].width+'px', 
            ''
            )
          
          if(_txt.height >= _sH) {
            return
          }
        }

        items[index].height = _sH

        items[index].top = items[index].y - items[index].height / 2;
      }

      // 获取触摸点到圆心距离
      items[index].r = items[index].disPtoO;

      this.items = JSON.parse(JSON.stringify(items))
      if (this.isCoverEdit) {
        this.fmItems = this.items
      } else {
        this.spPart[this.spPartIndex] = this.items

        this.spPart = JSON.parse(JSON.stringify(this.spPart))
      }

    },

九、旋转图层

    // 点击旋转图标
    oScaleStart(e, id) {
      // 找到点击的那个图片对象,并记录
      for (let i = 0; i < this.items.length; i++) {
        this.items[i].active = false;
        if (id == this.items[i].id) {
          this.index = i;
          this.items[this.index].active = true;
        }
      }

      // 获取作为移动前角度的坐标
      this.items[this.index].tx = e.layerX - this.toucheWrap.left;
      this.items[this.index].ty = e.layerY - this.toucheWrap.top;

      // 移动前的角度
      this.items[this.index].anglePre = this.countDeg(
        this.items[this.index].x,
        this.items[this.index].y,
        this.items[this.index].tx,
        this.items[this.index].ty
      );
    },

    // 移动旋转图标
    oScaleMove(e, id) {
      console.log('移动旋转图标')
      let { items, index } = this;

      // 记录移动后的位置
      items[index]._tx = e.layerX - this.toucheWrap.left;
      items[index]._ty = e.layerY - this.toucheWrap.top;

      // 移动的点到圆心的距离
      items[index].disPtoO = this.getDistancs(
        items[index].x,
        items[index].y,
        items[index]._tx,
        items[index]._ty - 10
      );

      // 移动后位置的角度
      items[index].angleNext = this.countDeg(
        items[index].x,
        items[index].y,
        items[index]._tx,
        items[index]._ty
      );
      // 角度差
      items[index].new_rotate = items[index].angleNext - items[index].anglePre;

      //叠加的角度差
      items[index].rotate += items[index].new_rotate;
      items[index].angle = items[index].type == "tt" ? items[index].rotate : 0; //赋值

      //用过移动后的坐标赋值为移动前坐标
      items[index].tx = e.layerX - this.toucheWrap.left;
      items[index].ty = e.layerY - this.toucheWrap.top;

      // 下次移动前的角度
      items[index].anglePre = this.countDeg(
        items[index].x,
        items[index].y,
        items[index].tx,
        items[index].ty
      );

      this.items = items;
      if (this.isCoverEdit) {
        this.fmItems = items;
      } else {
        this.spPart[this.spPartIndex] = items;
      }

    },

十、计算坐标点到圆心的距离

    getDistancs(cx, cy, pointer_x, pointer_y) {
      var ox = pointer_x - cx;
      var oy = pointer_y - cy;
      return Math.sqrt((ox * ox) + (oy * oy));
    },

十一、点击的坐标到圆心的角度

    /*
     * 参数cx和cy为图片圆心坐标
     * 参数pointer_x和pointer_y为手点击的坐标
     * 返回值为手点击的坐标到圆心的角度
     */
    countDeg(cx, cy, pointer_x, pointer_y) {
      var ox = pointer_x - cx;
      var oy = pointer_y - cy;
      var to = Math.abs(ox / oy);
      var angle = (Math.atan(to) / (2 * Math.PI)) * 360;

      // 相对在左上角,第四象限,js中坐标系是从左上角开始的,这里的象限是正常坐标系
      if (ox < 0 && oy < 0) {
        angle = -angle;
      }
      // 左下角,3象限
      else if (ox <= 0 && oy >= 0) {
        angle = -(180 - angle);
      }
      // 右上角,1象限
      else if (ox > 0 && oy < 0) {
        angle = angle;
      }
      // 右下角,2象限
      else if (ox > 0 && oy > 0) {
        angle = 180 - angle;
      }

      return angle;
    }

十二、拖动放置

<div class="image" 
    @dragstart="dragStart($event, item)" 
    draggable="true" 
    ref="boothRef">
</div>


<script>
    /* 拖动时触发 */
    dragStart($event, item) {
      console.log("开始拖动:", item);

      $event.dataTransfer.setData(
        "item",
        JSON.stringify({
          eType: "bg",
          type: item.resourceType,
          url: item.src,
          key: item.iobsKey
        })
      );
    },
</script>
<div 
      class="wrap"
      :class="{
        wrapfc: isFullscreen
      }"
      id="winBox" 
      ref="winBox"
      @click="canvasFullscreen(false)"
      @drop="drop($event)"
      @dragover="allowDrop($event)"
      @contextmenu.prevent="rightClick">

    ...
</div>

<script>

     /**
     * 拖动完成时触发
     */
    allowDrop($event) {
      $event.preventDefault();
    },

    /**
     * 拖动放置
     */
    drop($event) {
      $event.preventDefault();
      let data = $event.dataTransfer.getData("item");

      console.log('拖动放置:

猜你喜欢

转载自blog.csdn.net/qq_31851435/article/details/132479656
今日推荐