Implement dragging, zooming, rotating and other operations of label elements in the vue project

1. Mouse events

  • click: Fired when the mouse is pressed (usually the main button is pressed).
  • dblclick: Triggered when the mouse is double-clicked on the same element.
  • mousedown : Triggered when the mouse button is pressed.
  • mouseup : Fires when the pressed mouse button is released.
  • mousemove : Triggered when the mouse moves inside a node. This event fires continuously as the mouse continues to move. In order to avoid performance problems, it is recommended to make some restrictions on the listening function of this event, for example, it can only be run once within a certain period of time.
  • mouseenter: Triggered when the mouse enters a node, this event will not be triggered when the mouse enters a child node (see below for details).
  • mouseover: Triggered when the mouse enters a node, and this event will be triggered again when the mouse enters a child node (see below for details).
  • mouseout: Triggered when the mouse leaves a node, and this event will also be triggered when the mouse leaves the parent node (see below for details).
  • mouseleave : Triggered when the mouse leaves a node, leaving the parent node will not trigger this event (see below for details).
  • contextmenu: Triggered when the right mouse button is pressed (before the context menu appears), or when the "context menu key" is pressed.
  • wheel: Triggered when the mouse wheel is rolled, this event inherits from the WheelEvent interface.

Click event: The user first completes the mousedown action at the same position, and then completes the mouseup action. Therefore, the order of triggering is that mousedown is triggered first, mouseup is triggered next, and click is triggered last.

dblclick event: will be triggered after mousedown, mouseup, click.

mouseover event and mouseenter event: both are triggered when the mouse enters a node. The difference between the two is that the mouseenter event is only triggered once, and as long as the mouse moves inside the node, the mouseover event will be triggered multiple times on the child node.

mouseout and mouseleave events: both are triggered when the mouse enters a node. The difference between the two is that the mouseleave event is triggered once when leaving the node but not multiple times, while the mouseout event may be triggered multiple times if it leaves the parent element without leaving the child element.

MouseEvent Property

The MouseEvent interface inherits the Event interface, so it has all the properties and methods of the Event. It also has its own properties and methods.

  • pageX, pageY: The distancerelative to the upper left corner of the page , which is not affected by page scrolling, that is, plus the scrolling distance of the scroll axis.
  • clientX, clientY: the distance from the upper left corner of the page, affected by page scrolling, the default value is 0, setting this property will not move the mouse.
  • offsetX, offsetY: For the coordinates of the upper left corner of oneself , starting from the padding, calculate the upper left corner relative to the element , regardless of the positioning problem, and calculate the inner intersection point. It is a unique property of IE browser.
  • layerX, layerY: Go up to find the upper left corner of the parent element with the positioning attribute . Affected by the positioning of the element, it will find the upper left corner of the first positioned element from this element (if it has a positioning attribute, it is relative to itself ), if there is none, it is relative to the upper left corner of the body.
  • offsetWidth, offsetHeight: Get the entire width and height of the element, including the content area, padding and border.
  • clientWidth, clientHeight: These two attributes can get the visible width and height of the element, and will get the element width and height, including the content area and inner margin.
  • offsetParent: It can be used to obtain the positioning of the current element's positioning parent element (the ancestor element with positioning enabled), and returns body if the positioning is not enabled.
  • offsetLeft, offsetTop: The horizontal and vertical offsets of the current element relative to its positioned parent element.
  • scrollWidth, scrollHeight: You can get the width and height of the entire scrolling area of ​​the element.
  • scrollLeft, scrollTop: You can get the scrolling distance of the horizontal and vertical scroll bars.
  • screenX, screenY: value, coordinates of the upper left corner of the mouse relative to the screen (unit pixel), the default value is 0, setting this property will not move the mouse.
  • ctrlKey : Boolean value, whether the Ctrl key is pressed at the same time, the default value is false.
  • shiftKey: Boolean value, whether the Shift key is pressed at the same time, the default value is false.
  • altKey : Boolean value, whether to press the Alt key at the same time, the default value is false.
  • metaKey: Boolean value, whether to press the Meta key at the same time, the default value is false.
  • button: value, indicating which mouse button is pressed, the default value is 0, indicating that the primary button (usually the left button of the mouse) is pressed or the current event does not define this property; 1 indicates that the auxiliary button is pressed (usually the middle of the mouse key), 2 means the secondary key was pressed (usually the right mouse button).
  • buttons: value, indicating which buttons of the mouse are pressed, it is a three-bit binary value, and the default is 0 (no button is pressed). 1 (binary 001) means the primary key was pressed (usually the left button), 2 (binary 010) means the secondary key was pressed (usually the right button), and 4 (binary 100) means the secondary key was pressed (usually the middle key). So, returning 3 (binary 011) means that the left and right buttons were pressed at the same time.
  • relatedTarget: node object, representing the related node of the event, the default is null. For mouseenter and mouseover events, it indicates the element node that the mouse has just left; for mouseout and mouseleave events, it indicates the element node that the mouse is entering.

2. Realize the logic

To achieve the effect of mouse selection of the label and dragging after holding down the mouse, first select the label and use the mousedown event, and then hold down the mouse, you can set a draggable mark when the mousedown event is triggered, such as moveable= true, then in the mousemove event, judge whether the moveable is true, if it is true, it means that it can be dragged, and then you can change the top and left of the label element according to the obtained coordinates, so as to realize the movement, if it is false, nothing will be executed , and then if the mouse is released, the mouseup event is triggered, and moveable is set to false in this event.

1. Obtain the width and height of the URL image

    // 创建实例对象
	let img = new Image();
	// 图片地址
	img.src = "图片url链接";
	
	img.onload = function () {
 		let res = {
			width: img.width,
			height: img.height
		}
		console.log(res); // 获取到图片的宽高
 	}

2. Possible problems

1. The mouseup event is lost when dragging on the PC side

Description: When dragging and dragging left and right elements on the PC side, use mousedown+mousemove+mouseup to realize left and right dragging and movement. If the operation is repeated many times, there will always be a mouseup event that does not enter, resulting in the failure to clear the mousemove event.

Solution: Browser mouse events have default behaviors, such as event bubbling and other behaviors. We need to disable these default behaviors in mousedown and mousemove events. At the same time, add a css style to the listening element: user-select: none; prevent its text from being selected and moving to affect the mouseup event.

2. The area where the mousemove event can be triggered is too small

Description: If we bind the mousemove event to the mobile label element, it may be that the size of the label itself is not large enough, resulting in the mouse running out of the label area when moving, resulting in event loss and unsmooth movement experience.

Solution: Bind the mousemove event to the parent label of the largest area that can be moved.

3. Case code

<template>
  <!-- 视频编辑 -->
  <div class="img-canvas" 
    id="img-canvas" 
    :style="{
      width: width+'px',
      height: height+'px'
    }"
    @mousedown.stop.prevent="wrapStart($event)" 
    @mousemove.stop.prevent="wrapMove($event)"
    @mouseup.stop.prevent="wrapUp($event)"
    @mouseleave.stop.prevent="wrapUp($event)">

    <template v-for="(item, key) in items">
      <img
        :key="key"
        v-if="item.type == 'bg'"
        class="img"
        :src="item.content"
        :style="{
          width: item.width+'px', 
          height:item.height+'px', 
          zIndex: item.zIndex
        }"
        @click.stop.prevent="wraptouchStart($event, item.id)" />
        
      <div
        :key="key"
        v-else-if="item.show"
        :class="item.active ? 'img-wrap touch-active' : 'img-wrap'" 
        :style="{
          transform: 'rotate('+(item.angle ? item.angle : 0)+'deg)', 
          top: item.top+'px', 
          left: item.left+'px', 
          zIndex: item.zIndex
        }">
          <img
            v-if="item.type == 'ai' || item.type == 'tt'"
            class="img"
            :src="item.content"
            :style="{
              width: item.width+'px', 
              height:item.height+'px'
            }"
            @mousedown.prevent="wraptouchStart($event, item.id)" 
          />

          <div
            v-if="item.type == 'bt' || item.type == 'zm'"
            :id="'txt'+item.id"
            :class="item.type == 'zm' ? 'slh txt' : 'txt'"
            :style="{
              width: item.width ? item.width +'px' : 'auto', 
              height: item.height ? item.height + 'px' : 'auto', 
              fontSize: item.fontSize+'px', 
              color: item.fontColor, 
              textStroke: item.strokeShow ? item.strokeSize + 'px ' + item.strokeColor : 'none', 
              textAlign: item.align, 
              background: item.fontBgColor, 
              fontFamily: item.fontFamily
            }"
            @mousedown.prevent="wraptouchStart($event, item.id)" 
            :data-content="item.type == 'zm' ? '此处是字幕' : item.content"
            :data-color="item.fontColor"
            v-html="item.type == 'zm' ? ('此处是字幕') : item.content"></div>

        <!-- 删除按钮 -->
        <div 
          class="x" 
          v-if="item.active"
          @click.stop="deleteItem(item.id)">
          <img src="@/assets/x.png" />
        </div>

        <!-- 缩放按钮 -->
        <div
          class="s"
          v-if="item.active" 
          style="transform-origin:center;"
          @mousedown.prevent="oTouchStart($event, item.id)" 
        >
        </div>

        <div
          class="s s2"
          v-if="item.active" 
          style="transform-origin:center;"
          @mousedown.prevent="oTouchStart($event, item.id)" 
          >
        </div>

        <div
          class="s s3"
          v-if="item.active" 
          style="transform-origin:center;"
          @mousedown.prevent="oTouchStart($event, item.id)" 
        >
        </div>

        <!-- 旋转按钮 -->
        <div 
          class="o"
          v-if="item.active && item.type == 'tt'" 
          style="transform-origin:center;"
          @mousedown.prevent="oScaleStart($event, item.id)" 
        >
          <img src="@/assets/o.png"/>
        </div>

        <!-- 拉宽按钮 -->
        <div 
          class="lw"
          v-if="item.active && (item.type == 'bt')" 
          style="transform-origin:center;"
          @mousedown.prevent="oLwhStart($event, item.id, 'w')" 
        >
        </div>

        <!-- 拉高按钮 -->
        <div 
          class="lh"
          v-if="item.active && (item.type == 'bt')" 
          style="transform-origin:center;"
          @mousedown.prevent="oLwhStart($event, item.id, 'h')" 
        >
        </div>

      </div>

    </template>
  </div>
</template>
<style lang="less" scoped>
  .img-canvas {
    user-select: none;
    position: relative;
    background: yellowgreen;
    display: block;
    margin: 0 auto;
    width: 360px;
    height: 640px;

    .img-wrap {
      position: absolute;
      top: 20px;
      left: 20px;
      transform-origin: center;
      padding: 10px;
      box-sizing: border-box;

      &.touch-active {
        &::after {
          position: absolute;
          top: 0;
          left: 0;
          content: '';
          width: 100%;
          height: 100%;
          border: 6px solid #0054D1;
          box-sizing: border-box;
          pointer-events: none;
        }
      }

      .img {
        display: block;
      }

      .txt {
        display: block;
        /* 通过属性选择器结合伪元素before 实现文字外描边效果 */
        &[data-content]::before {
          /* attr()是用来获取被选中元素的某属性值,并且在样式文件中使用 */
          content: attr(data-content);
          position: absolute;
          /* 实现元素外描边的关键 */
          -webkit-text-stroke: 0;
          /* 文本颜色 */
          color: attr(data-color);
        }

        &.slh {
          display: block;
          white-space: nowrap;
          overflow: hidden;
          text-overflow: ellipsis;
          &[data-content]::before {
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
          }
        }
        
      }

      .x {
        z-index: 2;
        width: 50px;
        height: 50px;
        position: absolute;
        top: 0;
        left: 0;
        transform: translate(-50%, -50%);
        background: white;
        border-radius: 100%;
        display: flex;
        align-items: center;
        justify-content: center;
        box-shadow:0 5px 5px 5px rgba(0, 0, 0, 0.2);
        image {
          width: 100%;
          height: 100%;
        }
      }

      .o {
        width: 50px;
        height: 50px;
        position: absolute;
        bottom: -20px;
        left: 50%;
        transform: translate(-50%, 100%);
        background: white;
        border-radius: 100%;
        display: flex;
        align-items: center;
        justify-content: center;
        box-shadow:0 5px 5px 5px rgba(0, 0, 0, 0.2);
        image {
          width: 100%;
          height: 100%;
      
        }
      }

      .s {
        width: 30px;
        height: 30px;
        position: absolute;
        top: 0;
        right: 0;
        transform: translate(50%, -50%);
        background: #0054D1;
        border-radius: 100%;
        display: flex;
        align-items: center;
        justify-content: center;
        box-shadow:0 5px 5px 5px rgba(0, 0, 0, 0.2);

        &.s2 {
          top: auto;
          bottom: 0;
          right: 0;
          transform: translate(50%, 50%);
        }

        &.s3 {
          top: auto;
          bottom: 0;
          left: 0;
          right: auto;
          transform: translate(-50%, 50%);
        }
      }

      .lw {
        z-index: 2;
        position: absolute;
        top: 50%;
        right: 0;
        transform: translate(50%, -50%);
        width: 15px;
        height: 40px;
        background: white;
        border-radius: 5px;
      }

      .lh {
        z-index: 2;
        position: absolute;
        left: 50%;
        bottom: 0;
        transform: translate(-50%, 50%);
        width: 40px;
        height: 15px;
        background: white;
        border-radius: 5px;
      }

    }
  }
</style>
<script>

import { getUUID, algorithm } from '@/utils/utils'


export default {
  props: {
    width: {
      type: Number,
      default: 360
    },
    height: {
      type: Number,
      default: 640
    },
    templateId: {
      type: Number,
      default: 304
    }
  },

  data() {
    return {
      timeFlag: '',
      toucheWrap: {}, // 图层容器
      index: 0, // 当前点击图片的index
      items: [],
      firstCopyItems: [],
      spItems: [], // 视频对象数组信息
      fmItems:[], // 封面对象数组信息
      spPart: [], // 视频片段列表
      spPartIndex: 0, // 当前选中的视频片段的index
      sDpi: 1, // 模版尺寸 720*1280


    }
  },

  created() {
    this.sDpi = 720/this.width;
    console.log(this.$parent)
  },

  mounted() {

    this.toucheWrap = {
      width: this.width,
      height: this.height,
      top: document.getElementById('img-canvas').offsetTop,
      left: document.getElementById('img-canvas').offsetLeft
    }


  },

  onUnmounted() {
    if(this.timeFlag) {
      clearTimeout(this.timeFlag)
    }
  },

  methods: {

    // 设置图层对象的信息
    setDropItem(obj, call) {
      console.log('setDropItem', obj)

      return new Promise((resolve, reject)=> {
        let data = {}; // 存储拖拽对象信息

        let type = obj.type;
        let content = obj.content;

        // 背景、AI数字人、贴图
        if(type == 'bg' || type == 'ai' || type == 'tt') {
          // 获取图片信息
          var img = new Image();
          // 图片地址
          img.src = content;

          img.onload = () => {

            let res = {
              width: img.width,
              height: img.height
            }

            // 初始化数据
            let maxWidth = 150, maxHeight = 150; // 设置最大宽高
            if(type == 'bg') {
              maxWidth = 360
              maxHeight = 640
            }

            if (res.width > maxWidth || res.height > maxHeight) { // 原图宽或高大于最大值就执行
              if (res.width / res.height > maxWidth / maxHeight) { // 判断比例使用最大值的宽或高作为基数计算
                data.width = maxWidth;
                data.height = Math.round(maxWidth * (res.height / res.width));
              } else {
                data.height = maxHeight;
                data.width = Math.round(maxHeight * (res.width / res.height));
              }
            } else {
              data.width = res.width;
              data.height = res.height;
            }
          
            data.iobsKey = '';
            data.show = true; // 显示
            data.type = type; // 对象类型 type - [bg, ai, tt, zm]
            data.content = content; // 显示地址
            data.id = algorithm(); // id
            data.top = 0; // top定位
            data.left = 0; // left定位
            // 圆心坐标
            data.x = data.left + data.width / 2;
            data.y = data.top + data.height / 2;
            data.scale = 1; // scale缩放
            data.rotate = 0; // 旋转角度
            data.active = false; // 选中状态
            
            console.log(this.items)
            if(type == 'bg') {
              data.zIndex = 0; // 层级
            } else if(this.items.find(it => it.type == 'bg')) {
              data.zIndex = this.items.length; // 层级
            } else {
              data.zIndex = this.items.length+1; // 层级
            }

            // 覆盖原数据
            data = {
              ...data,
              ...obj
            }
            
            // 封面
            if(this.isCoverEdit || obj.isFm) {
              
              this.fmItems.push(data); // 每增加一张图片数据增加一条信息
              this.items = this.fmItems;
            } 
            // 视频
            else {
              if(this.spPart[this.spPartIndex]) {
                this.spItems = this.spPart[this.spPartIndex]
              } else {
                this.spItems = []
              }
              
              this.spItems.push(data); // 每增加一张图片数据增加一条信息
              this.spPart[this.spPartIndex] = this.spItems
              this.items = this.spPart[this.spPartIndex]
            }

            resolve();
            call && call()
          }
        } 
        // 标题
        else if (type == 'bt' || type == 'zm') {
          // 初始化数据
          data.width = 0;
          data.height = 0;

          data.show = true; // 显示
          data.fontFamily = 'simhei'; // 字体
          data.strokeShow = true; // 显示描边
          data.align = 'left'; // 文本对齐方式
          data.fontColor = '#000'; // 字体颜色
          data.fontSize = 12; // 字号大小
          data.fontBgColor = ''; // 文本背景颜色
          data.strokeSize = 0; // 描边粗细
          data.strokeColor = '#000'; // 描边颜色
          data.type = type; // 对象类型 type - [bg, ai, tt, zm]
          data.content = content; // 显示内容
          data.id = algorithm(); // id
          
          data.scale = 1; // scale缩放
          data.rotate = 0; // 旋转角度
          data.active = false; // 选中状态

          if(type == 'bg') {
            data.zIndex = 0; // 层级
          } else if(this.items.find(it => it.type == 'bg')) {
            data.zIndex = this.items.length; // 层级
          } else {
            data.zIndex = this.items.length+1; // 层级
          }

          data.top = 0; // top定位
          data.left = 0; // left定位

          // 圆心坐标
          data.x = data.left + data.width / 2;
          data.y = data.top + data.height / 2;

          // 字幕
          if(type == 'zm') {

            // 圆心坐标
            data.x = this.toucheWrap.width/2;
            data.y = this.toucheWrap.height - 20;
            
            data.ys = obj.ys
            data.yl = obj.yl
            data.yd = obj.yd
            data.ysu = obj.ysu
      
          }

          // 覆盖原数据
          data = {
            ...data,
            ...obj
          }

          // 封面
          if(this.isCoverEdit || obj.isFm) {
            this.fmItems.push(data);
            this.items = this.fmItems; // 每增加一张图片数据增加一条信息
          } 
          // 视频
          else {
            if(this.spPart[this.spPartIndex]) {
              this.spItems = this.spPart[this.spPartIndex]
            } else {
              this.spItems = []
            }

            this.spItems.push(data); // 每增加一张图片数据增加一条信息
            this.spPart[this.spPartIndex] = this.spItems;
            this.items = this.spPart[this.spPartIndex];
          }

          let _i = this.items.length - 1;

          this.items = JSON.parse(JSON.stringify(this.items))
       
          setTimeout(() => {
            let doc = document.getElementById('txt' + this.items[_i].id)
            console.log('doc',this.items[_i].id, doc)

            this.items[_i].width = doc.offsetWidth;
            this.items[_i].height = doc.offsetHeight;

            if(type == 'zm') {
              this.items[_i].left = this.items[_i].x - this.items[_i].width / 2;
              this.items[_i].top = this.items[_i].y - this.items[_i].height / 2
            } else {
              this.items[_i].x = this.items[_i].left + this.items[_i].width / 2;
              this.items[_i].y = this.items[_i].top + this.items[_i].height / 2;
            }

            // 覆盖原数据
            this.items[_i] = {
              ...this.items[_i],
              ...obj
            }
    
            if(this.isCoverEdit || obj.isFm) {
              // 封面编辑
              this.fmItems = this.items
            } else {
              // 视频编辑
              this.spPart[this.spPartIndex] = this.items
            }

            resolve()
            call && call() 
          }, 500)
                     
          
        } 
      })
    
    },

    // 改变图片层级
    changeZIndex(isAdd) {

      let _zIndex = this.items[this.index].zIndex
      isAdd ? _zIndex += 1 : _zIndex -= 1

      // 循环图片数组获取点击的图片信息
      for (let i = 0; i < this.items.length; i++) {  
        if (_zIndex == this.items[i].zIndex) {
          isAdd ? this.items[i].zIndex -= 1 : this.items[i].zIndex += 1
          break;
        }
      }

      // 上移一层 | 下移一层
      isAdd ? this.items[this.index].zIndex += 1 : this.items[this.index].zIndex -= 1

      if(this.items[this.index].zIndex > this.items.length-1) {
        this.items[this.index].zIndex = this.items.length-1;

        this.$message('已是最顶层');

      }

      if(this.items[this.index].zIndex < 1) {
        this.items[this.index].zIndex = 1;

        this.$message('已是最底层');
      
      }

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

    },

    wrapStart(e) {
      console.log('wrapStart')
      this.drag = true;
    },

    wrapMove(e) {
      if(this.drag) {
        console.log('wrapMove')
        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) {
            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);
          }
        }
      }
    },

    wrapUp(e) {
      console.log('wrapUp')
      this.drag = false;
      this.oTouchUp();
    },

    // 点击图层
    wraptouchStart (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) {
          console.log(id)
          this.index = i;
          this.items[this.index].active = true;
          this.items[this.index].moveabled = true;
        }
      }

      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'

        this.btAttribute = {
          color: this.items[this.index].fontColor,
          align: this.items[this.index].align,
          size: this.items[this.index].fontSize,
          family: this.items[this.index].fontFamily,
          bgColor: this.items[this.index].fontBgColor,
          showStroke:this.items[this.index].strokeShow,
          strokeSize: this.items[this.index].strokeSize,
          strokeColor: this.items[this.index].strokeColor
        }
        
      }
      else if(this.items[this.index].type == 'zm') {
        editType = 'zm'

        this.zmAttribute = {
          color: this.items[this.index].fontColor,
          align: this.items[this.index].align,
          size: this.items[this.index].fontSize,
          family: this.items[this.index].fontFamily,
          bgColor: this.items[this.index].fontBgColor,
          showStroke: this.items[this.index].strokeShow,
          strokeSize: this.items[this.index].strokeSize,
          strokeColor: this.items[this.index].strokeColor,
          show: this.items[this.index].show
        }
        
      }

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

      this.editType = editType
      this.isShowEditInput = false
      this.isInitEdit = false
      
      if(this.items[this.index].type == 'bg') {
        return
      }
    
      // 获取点击的坐标值
      this.items[this.index].lx = e.pageX;
      this.items[this.index].ly = e.pageY;

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

    },

    // 拖动图层
    wraptouchMove (e, id) {

      let { items, index } = this;

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

      console.log('拖动图层', e, id)

      items[index]._lx = e.pageX;
      items[index]._ly = e.pageY;
  
      // if(items[index].type == 'zm') {

      //   items[index].top += items[index]._ly - items[index].ly;

      //   items[index].y += items[index]._ly - items[index].ly;
      // } else {

        items[index].left += items[index]._lx - items[index].lx;
        items[index].top += items[index]._ly - items[index].ly;
        items[index].x += items[index]._lx - items[index].lx;
        items[index].y += items[index]._ly - items[index].ly;
      // }
  
      items[index].lx = e.pageX;
      items[index].ly = e.pageY;

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

    // 点击旋转图标
    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.pageX - this.toucheWrap.left;
      this.items[this.index].ty = e.pageY - 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) {

      let { items, index } = this;
      
      // 记录移动后的位置
      items[index]._tx = e.pageX - this.toucheWrap.left;
      items[index]._ty = e.pageY - 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.pageX - this.toucheWrap.left;
      items[index].ty = e.pageY - 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
      }
  
    },

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

      // 找到点击的那个图片对象,并记录
      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[this.index].active = true;
          this.items[this.index].sMoveabled = true;
        }
      }

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

      // 获取图片半径
      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, id) {
      console.log('移动伸缩图标')

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

      if(!items[index].sMoveabled) {
        return
      }
      
      // 记录移动后的位置
      items[index]._tx = e.pageX - toucheWrap.left;
      items[index]._ty = e.pageY - toucheWrap.top;

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

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

      if(items[index].type == 'bt' || items[index].type == 'zm') {
        let _oldFontSize = items[index].fontSize;

        items[index].fontSize = items[index].fontSize * _s;

        let maxFontSize = items[index].type == 'zm' ? 40/sDpi : 100/sDpi;

        if(items[index].fontSize > maxFontSize) {
          items[index].fontSize = maxFontSize;
          _s = 100/_oldFontSize
        } else if(items[index].fontSize < 12/sDpi) {
          items[index].fontSize = 12/sDpi;
          _s = 12/(sDpi*_oldFontSize)
        }

        if(items[index].type == 'bt') {
          this.btAttribute = {
            color: items[index].fontColor,
            align: items[index].align,
            size: items[index].fontSize,
            family: items[index].fontFamily,
            bgColor: items[index].fontBgColor,
            showStroke: items[index].strokeShow,
            strokeSize: items[index].strokeSize,
            strokeColor: items[index].strokeColor
          }
          
        } else {
          this.zmAttribute = {
            color: items[index].fontColor,
            align: items[index].align,
            size: items[index].fontSize,
            family: items[index].fontFamily,
            bgColor: items[index].fontBgColor,
            showStroke: items[index].strokeShow,
            strokeSize: items[index].strokeSize,
            strokeColor: items[index].strokeColor,
            show: items[index].show
          }
          
        }
        
      }

      // 使用缩放
      // items[index].scale = items[index].disPtoO / items[index].r;
  
      // 不使用缩放
      items[index].width =  _s * items[index].width
      items[index].height =  _s * items[index].height
      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 = items;
      if(this.isCoverEdit) {
        this.fmItems = items
      } else {
        this.spPart[this.spPartIndex] = items
      }

    },

    // 鼠标取消
    oTouchUp (e, id) {
      console.log('oTouchUp')
      this.items[this.index].lMoveabled = false;
      this.items[this.index].sMoveabled = false;
      this.items[this.index].moveabled = false;
    },

    // 点击文字拉宽高图标
    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[this.index].active = true;
          this.items[this.index].lMoveabled = true;
          this.items[this.index].lMoveType = _type;
        }
      }

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

      // 获取触摸点到圆心距离
      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) {

      let { items, index, toucheWrap } = this;

      if(!items[index].lMoveabled) {
        return
      }
      
      // 记录移动后的位置
      items[index]._tx = e.pageX - toucheWrap.left;
      items[index]._ty = e.pageY - toucheWrap.top;

      // 移动的点到圆心的距离
      items[index].disPtoO = this.getDistancs(items[index].x, items[index].y, items[index]._tx, items[index]._ty - 10)
      let _s = items[index].disPtoO / items[index].r;
  
      // 不使用缩放
      if(_type == 'w') {
        items[index].width =  _s * items[index].width
        items[index].left = items[index].x - items[index].width/2
      
      } else {
        items[index].height =  _s * items[index].height
        items[index].top = items[index].y - items[index].height/2 
      }
      

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

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

    },

    // 删除图层对象
    deleteItem (id) {
      let newList = [];
      for (let i = 0; i < this.items.length; i++) {
        // 更新层级
        if(this.items[i].zIndex > this.items[this.index].zIndex) {
          this.items[i].zIndex -= 1
        }
        if (id != this.items[i].id) {
          newList.push(this.items[i])
        }

      }

      if (newList.length > 0) {
        newList[newList.length - 1].active = true; // 剩下图片组最后一个选中
        this.index = newList.length - 1
      } else {
        this.index = 0
      }
      
      this.items = newList;

      if(this.isCoverEdit) {
        this.fmItems = this.items
      } else {
        this.spPart[this.spPartIndex] = this.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;
    },


    fetchError (msg) {

      this.loading.close();

      if(msg) {
        this.$message.error(msg);
      }
    }
  }
}
</script>

Guess you like

Origin blog.csdn.net/qq_31851435/article/details/131676138