微信小程序内实现图层的移动、缩放、旋转等其他编辑操作

这篇具有很好参考价值的文章主要介绍了微信小程序内实现图层的移动、缩放、旋转等其他编辑操作。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、标签的形式

图层有3种

1、背景图层不可操作,只能替换,不可改变层级(最底层)

2、图片图层可移动,缩放(支持双指缩放),旋转,可改变层级

3、文字图层可移动,缩放(会改变文字大小,支持双指缩放),旋转,文字编辑区域拉伸长度和宽度(不会改变文字大小),可编辑更改文字属性(内容,字体大小,字体颜色,描边大小,描边颜色,编辑框背景色),可改变层级

关于不同设备的编辑区域尺寸的转化:

我们以720*1280的设计图为准,先获取设备上编辑区域的尺寸(实际宽高的像素),然后用编辑区域的宽高和设计图的宽高,就可以得出它们之间的dpi,之后就可以用这个dpi进行转换。

图层对象是一个json对象,里面保存很多对象属性 :

{
    show: true, //是否显示图层

    align: "center", // 文字对齐方式
    content: "abcdefg", // 文字内容或图片链接
    fontBgColor: 'red', // 编辑框背景色
    fontColor: "999999", // 文字颜色
    fontFamily: "宋体", // 字体
    fontSize: 5.5, // 字体大小
    strokeColor: "000000", // 描边颜色
    strokeShow: true, // 是否显示描边
    strokeSize: 12, // 描边大小

    left: 27.5, // 左上角x坐标
    top: 27.5, // 左上角y坐标
    type: "bt", // 图层类型
    width: 35.75, // 宽
    height: 66, // 高
    x: 45.375, // 中心点x坐标
    y: 60.5, // 中心点y坐标
    zIndex: 2 // 层级

}
    <!-- 编辑区域 -->
    <view class="img-canvas" id="img-canvas">

      <block wx:for="{{itemList}}" wx:key="key">

        <!-- 背景图(不可操作,只能替换)-->
        <image
          wx:if="{{item.type == 'bg'}}"
          class="img"
          src="{{item.content}}"
          style="width:{{item.width}}px; height:{{item.height}}px;z-index:{{ item.zIndex }}"
          data-id="{{item.id}}"
          capture-catch:touchstart="wraptouchStart"
          />
          
        <!-- 图层编辑框 -->
        <view
          wx:elif="{{item.show}}"
          class="img-wrap {{ item.active ? 'touch-active' : ''}}" 
          style="transform: rotate({{ item.angle ? item.angle : 0 }}deg); top:{{ item.top }}px; left:{{ item.left }}px; z-index:{{ item.zIndex }}">

            <!-- 图片图层 -->
            <image
              wx:if="{{ item.type == 'ai' || item.type == 'tt' }}"
              class="img"
              src="{{item.content}}"
              style="width:{{item.width}}px; height:{{item.height}}px;"
              data-id="{{item.id}}"
              capture-catch:touchstart="wraptouchStart" 
              capture-catch:touchmove="wraptouchMove"/>

            <!-- 文字图层 -->
            <text
              decode
              wx:if="{{item.type == 'bt' || item.type == 'zm'}}"
              id="{{'txt'+item.id}}"
              class="txt {{ item.type == 'zm' ? 'slh' : '' }}"
              style="width:{{ item.width ? item.width +'px' : 'auto' }}; height:{{ item.height ? item.height + 'px' : 'auto' }}; font-size: {{ item.fontSize }}px; color: {{ item.fontColor }}; -webkit-text-stroke: {{ item.strokeShow ? item.strokeSize + 'px ' + item.strokeColor : 'none' }}; text-align: {{ item.align }}; background: {{ item.fontBgColor }}; font-family: {{ item.fontFamily }}"
              data-id="{{item.id}}"
              capture-catch:touchstart="wraptouchStart" 
              capture-catch:touchmove="wraptouchMove"
              data-content="{{item.content}}"
              data-color="{{item.fontColor}}">{{ item.content }}</text>

          <!-- 删除按钮 -->
          <view 
            class="x" 
            wx:if="{{ item.active }}"
            data-id="{{item.id}}"
            bindtap="deleteItem">
            <image src="/assets/x.png" />
          </view>

          <!-- 缩放按钮 -->
          <view
            class="s"
            wx:if="{{ item.active }}" 
            style="transform-origin:center;"
            data-id="{{item.id}}"
            capture-catch:touchstart="oTouchStart" 
            capture-catch:touchmove="oTouchMove">
          </view>

          <view
            class="s s2"
            wx:if="{{ item.active }}" 
            style="transform-origin:center;"
            data-id="{{item.id}}"
            capture-catch:touchstart="oTouchStart" 
            capture-catch:touchmove="oTouchMove">
          </view>

          <view
            class="s s3"
            wx:if="{{ item.active }}" 
            style="transform-origin:center;"
            data-id="{{item.id}}"
            capture-catch:touchstart="oTouchStart" 
            capture-catch:touchmove="oTouchMove">
          </view>

          <!-- 旋转按钮 -->
          <view 
            class="o"
            wx:if="{{ item.active && item.type == 'tt' }}" 
            style="transform-origin:center;"
            data-id="{{item.id}}"
            capture-catch:touchstart="oScaleStart" 
            capture-catch:touchmove="oScaleMove">
            <image src="/assets/o.png"/>
          </view>

          <!-- 拉宽按钮 -->
          <view 
            class="lw"
            wx:if="{{ item.active && (item.type == 'bt') }}" 
            style="transform-origin:center;"
            data-id="{{item.id}}"
            data-type="w"
            capture-catch:touchstart="oLwhStart" 
            capture-catch:touchmove="oLwhMove">
          </view>

          <!-- 拉高按钮 -->
          <view 
            class="lh"
            wx:if="{{ item.active && (item.type == 'bt') }}" 
            style="transform-origin:center;"
            data-id="{{item.id}}"
            data-type="h"
            capture-catch:touchstart="oLwhStart" 
            capture-catch:touchmove="oLwhMove">
          </view>

        </view>

      </block>
    </view>
    .img-canvas {
      position: relative;
      background: white;
      display: block;
      margin: 0 auto;
      width: 360rpx;
      height: 640rpx;
      overflow: hidden;

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

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

        .img {
          display: block;
        }

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

          &.slh {
            max-width: 370rpx;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
          }
          // white-space: nowrap;
          // word-wrap: break-word;
        }

        .x {
          z-index: 2;
          width: 50rpx;
          height: 50rpx;
          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 5rpx 5rpx 5rpx rgba(0, 0, 0, 0.2);

          image {
            width: 100%;
            height: 100%;
          }
        }

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

          image {
            width: 100%;
            height: 100%;          
          }
        }

        .s {
          width: 30rpx;
          height: 30rpx;
          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 5rpx 5rpx 5rpx 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: 15rpx;
          height: 40rpx;
          background: white;
          border-radius: 5rpx;
        }

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

      }
    }
let toucheWrap = {} // 图层容器
let index = 0, // 当前点击图片的index
    items: any[] = []

let doubleTouchesArr: any[] = []; // 双指操作的数组

// 模版尺寸 720*1280
// 不同编辑区域尺寸可以根据这个dpi进行比例转换
let sDpi = 1;


Page({

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady() {

    // 获取画板容器数据
    wx.createSelectorQuery()
    .select('#img-canvas') // 在 WXML 中填入的 id
    .boundingClientRect()
    .exec((res) => {
      console.log('容器', res)
      toucheWrap = res[0];
      sDpi = 720/res[0].width;
    })

  },


  // 计算坐标点到圆心的距离
  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;
  },

})
// 判断key是否存在,排除ts报错
export const isValidKey = (key:string,object:object):key is keyof typeof object =>{
  return key in object
}

// 生成唯一id
export function algorithm(){
	let abc = ['a','b','c','d','e','f','g','h','i','g','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'];
	let [max,min]=[Math.floor(Math.random()*(10-7+1)+1),Math.floor(Math.random()*(17-10+1)+17)];
	abc = abc.sort(()=>0.4-Math.random()).slice(max,min).slice(0,8).join("");
	let a=new Date().getTime()+abc;
	return a
}

1、移动

  // 点击图层
  wraptouchStart (e: any) {

    // 循环图片数组获取点击的图片信息
    for (let i = 0; i < items.length; i++) {
      items[i].active = false;
      if (e.currentTarget.dataset.id == items[i].id) {
        index = i;
        items[index].active = true;
      }
    }

 
    if(items[index].type == 'bg') {
      return
    }

    // 双指操作
    if(e.touches.length > 1) {
  
      doubleTouchesArr = [...e.touches]

    }  else {
   
      // 获取点击的坐标值
      items[index].lx = e.touches[0].clientX;
      items[index].ly = e.touches[0].clientY;

    }

  }
  // 拖动图层
  wraptouchMove (e: any) {

    // 双指缩放
    if(e.touches.length > 1) {

      let _mw = Math.sqrt(Math.pow(doubleTouchesArr[0].pageX - doubleTouchesArr[1].pageX, 2) + Math.pow(doubleTouchesArr[0].pageY - doubleTouchesArr[1].pageY, 2));

      let _w = Math.sqrt(Math.pow(e.touches[0].pageX - e.touches[1].pageX, 2) + Math.pow(e.touches[0].pageY - e.touches[1].pageY, 2));

      let _s = _w / _mw

      // 使用缩放
      // let _oSWidth = items[index].scale > 0 ? items[index].width * items[index].scale : items[index].width;
      // let _nSWidth = _oSWidth * _s;
      
      // items[index].scale = _nSWidth / items[index].width;

      // 不使用缩放
      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

      if(items[index].type == 'bt') {
        items[index].fontSize = items[index].fontSize * _s
      }

      // 双指操作的坐标
      doubleTouchesArr = [...e.touches]

      
    } else {

      items[index]._lx = e.touches[0].clientX;
      items[index]._ly = e.touches[0].clientY;
  
      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.touches[0].clientX;
      items[index].ly = e.touches[0].clientY;


    }
    
  }

2、旋转

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

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

    // 移动前的角度
    items[index].anglePre = this.countDeg(items[index].x, items[index].y, items[index].tx, items[index].ty);
    
  }
  // 移动旋转图标
  oScaleMove (e: any) {
    
    // 记录移动后的位置
    items[index]._tx = e.touches[0].pageX - toucheWrap.left;
    items[index]._ty = e.touches[0].pageY - 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.touches[0].pageX - toucheWrap.left;
    items[index].ty = e.touches[0].pageY - toucheWrap.top;

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


  }

3、缩放

  // 点击伸缩图标
  oTouchStart (e: any) {
    // 找到点击的那个图片对象,并记录
    for (let i = 0; i < items.length; i++) {
      items[i].active = false;
      if (e.currentTarget.dataset.id == items[i].id) {
        index = i;
        items[index].active = true;
      }
    }

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

    // 获取图片半径
    items[index].r = this.getDistancs(items[index].x, items[index].y, items[index].tx, items[index].ty);
    
  }
  // 移动伸缩图标
  oTouchMove (e: any) {
    
    // 记录移动后的位置
    items[index]._tx = e.touches[0].pageX - toucheWrap.left;
    items[index]._ty = e.touches[0].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;

    // 使用缩放
    // 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
    
    if(items[index].type == 'bt' || items[index].type == 'zm') {
      items[index].fontSize = items[index].fontSize * _s
    }

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

  }

4、拉伸文字编辑框

  // 点击文字拉宽高图标
  oLwhStart (e: any) {
    // 找到点击的那个图片对象,并记录
    for (let i = 0; i < items.length; i++) {
      items[i].active = false;
      if (e.currentTarget.dataset.id == items[i].id) {
        index = i;
        items[index].active = true;
      }
    }

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

    // 获取触摸点到圆心距离
    items[index].r = this.getDistancs(items[index].x, items[index].y, items[index].tx, items[index].ty);
    
  }
  // 移动文字拉宽高图标
  oLwhMove (e: any) {

    let _type = e.currentTarget.dataset.type
    
    // 记录移动后的位置
    items[index]._tx = e.touches[0].pageX - toucheWrap.left;
    items[index]._ty = e.touches[0].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;

  
  }

 5、删除图层

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

    }

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

    
  },

6、更改层级

  // 改变图层层级
  changeZIndex(e: any) {
    console.log(e)
    let isAdd = e.detail.add

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

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

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

    if(items[index].zIndex > items.length-1) {
      items[index].zIndex = items.length-1;
      wx.showToast({
        title: '已是最顶层',
        icon: 'none',
        duration: 2000
      })
    }

    if(items[index].zIndex < 1) {
      items[index].zIndex = 1;
      wx.showToast({
        title: '已是最底层',
        icon: 'none',
        duration: 2000
      })
    }

  },

7、设置图层对象

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

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

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

      // 背景、贴图
      if(type == 'bg' || type == 'tt') {
        // 获取图片信息
        wx.getImageInfo({
          src: content,
          success: res => {
            console.log(res)
            // 初始化数据
            let maxWidth = 150, maxHeight = 150; // 设置最大宽高
            if(type == 'bg') {
              maxWidth = 200
              maxHeight = 600
            }

            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
            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; // 选中状态
            
            if(type == 'bg') {
              data.zIndex = 0; // 层级
            } else if(items.find(it => it.type == 'bg')) {
              data.zIndex = items.length; // 层级
            } else {
              data.zIndex = items.length+1; // 层级
            }

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

            items.push(data)
                       

            resolve();
            call && call()

            
          },
          fail: err => {
            console.log(err)
          }
        })
      } 
      // 标题
      else if (type == 'bt' || type == 'zm') {
        // 初始化数据
        data.width = 0;
        data.height = 0;

        data.show = true; // 显示
        data.fontFamily = '黑体'; // 字体
        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(items.find(it => it.type == 'bg')) {
          data.zIndex = items.length; // 层级
        } else {
          data.zIndex = 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 = toucheWrap.width/2;
          data.y = toucheWrap.height - 20;
          
          data.ys = obj.ys
          data.yl = obj.yl
          data.yd = obj.yd
          data.ysu = obj.ysu
    
        }

        items.push(data);


        let _i = items.length - 1;
        
        this.setData({
      
          itemList: items,
        
        }, () => {
          // 获取画板容器数据
          wx.createSelectorQuery()
          .select('#txt' + items[_i].id) // 在 WXML 中填入的 id
          .boundingClientRect()
          .exec((res) => {
            // console.log('容器', res)
            items[_i].width = res[0].width;
            items[_i].height = res[0].height;

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

            // 覆盖原数据
            items[_i] = {
              ...items[_i],
              ...obj
            }
    
            

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

8、实现文字外描边的关键代码

<text
    decode
    id="{{'txt'+item.id}}"
    class="txt {{ item.type == 'zm' ? 'slh' : '' }}"
    style="width:{{ item.width ? item.width +'px' : 'auto' }}; height:{{ item.height ? item.height + 'px' : 'auto' }}; font-size: {{ item.fontSize }}px; color: {{ item.fontColor }}; -webkit-text-stroke: {{ item.strokeShow ? item.strokeSize + 'px ' + item.strokeColor : 'none' }}; text-align: {{ item.align }}; background: {{ item.fontBgColor }}; font-family: {{ item.fontFamily }}"
    data-id="{{item.id}}"
    data-content="{{item.content}}"
    data-color="{{item.fontColor}}">{{ item.content }}</text>
    .txt {
          display: block;

          /* 通过属性选择器结合伪元素before 实现文字外描边效果 */
          &[data-content]::before {
            /* attr()是用来获取被选中元素的某属性值,并且在样式文件中使用 */
            content: attr(data-content);
            white-space: pre; // 支持换行
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 100%;
            height: 100%;
            /* 实现元素外描边的关键 */
            -webkit-text-stroke: 0;
            /* 文本颜色 */
            color: attr(data-color);
            text-align: attr(data-align);
          }
    }

二、canvas的形式

更完善的案例请参考:vue3使用canvas实现图层的移动、缩放、旋转等其他编辑操作_不怕麻烦的鹿丸的博客-CSDN博客

使用标签的方式好处是可以很方便就判断是否点击到某个图层上的操作按钮,而采用canvas的话,就需要我们自己去判断点击的区域是否是某个图层上的某个操作按钮,坐标点的转换比较复杂。

<view class="edit-wrap" hidden="{{true}}">
    <canvas 
        type="2d" 
        id="myCanvas" 
        class="canvas" 
        capture-catch:touchstart="canvasEvent" 
        capture-catch:touchmove="canvasTMEvent" 
        capture-catch:touchend="canvasTEEvent"></canvas>
</view>

1、求旋转角度

可以考虑用反弦函数来求的弧度值。

2、射线法判断点是否在多边形内部

  /**
   * 射线法判断点是否在多边形内部
   * 
   * @param p <x, y>
   * @param ptPolygon Array<x, y>
   * 
   * */ 
  isInPolygon (p, ptPolygon) {
    let ncross = 0;
    for (let i = 0; i < ptPolygon.ncount; i++) {
      let p1 = ptPolygon[i];
      let p2 = ptPolygon[(i + 1) % ptPolygon.ncount]; // 相邻两条边p1,p2
      if (p1.y == p2.y) {
        continue;
      }        
      if (p.y < Math.min(p1.y, p2.y)) {
        continue;
      }
      if (p.y >= Math.max(p1.y, p2.y)) {
        continue;
      }
      let x = (p.y - p1.y)*(p2.x - p1.x) / (p2.y - p1.y) + p1.x;
      if (x > p.x) {
        ncross++; // 只统计单边交点
      }
    }

    return(ncross % 2 == 1);
  }

3、判断旋转是顺时针或逆时针

  // 判断旋转是顺时针或逆时针
  rotateDirection(p1, p2, p3) {

    // 向量叉乘方向判断
    let r = (p2.x - p1.x) * (p3.y - p2.y) - (p2.y - p1.y) * (p3.x - p2.x)
    
    if(r > 0) {
      // 逆时针
      console.log('逆时针')
      return 1
    } else if(r < 0) {
      // 顺时针
      console.log('顺时针')
      return -1
    } else {
      // 不变
      console.log('不变')
      return 0
    }
  }

4、绘制图层和改变层级

可以新建一个数组,然后把图层对象存到这个数组里,然后依次遍历数组里的对象,依次绘制图层。

改变层级的话,就是改变对象在数组里位置即可。

判断点击到哪个图层,也是依次遍历数组里的对象,最先判断到的对象为点击到的对象。文章来源地址https://www.toymoban.com/news/detail-698542.html

到了这里,关于微信小程序内实现图层的移动、缩放、旋转等其他编辑操作的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 如何实现让最上面的图层不影响下面图层的点击事件(可穿透图层的鼠标事件)

    咱先来解决问题然后再了解是怎摸个原理在了解他的兼容性 pointer-events  直译为 指针事件 ,该属性指定在什么情况下某个DOM可以成为鼠标事件的 target。 简而言之,就是允许/禁止DOM的 鼠标事件 (click事件、hover事件、mouse事件等鼠标事件) 他当然不止一个属性值还有其他的某些

    2024年02月10日
    浏览(46)
  • 微信小程序 MapContext.includePoints实现自动缩放地图大小

    记录一下微信小程序实现自动缩放地图大小的过程 官方文档地址: MapContext.includePoints 最近做了个功能,根据用户和商家之间的距离画出导航线,然后根据距离自动缩放地图的scale 然鹅!官方文档又是一笔带过,没办法,查资料查的一肚子火,好在最后还是实现了,直接贴代

    2024年02月16日
    浏览(46)
  • 计算机图形学,OpenGL编写的一个可实现旋转缩放移动的房间,内有数十种交互

    #include stdlib.h #includestdio.h #includewindows.h #include GL/glut.h #include math.h #include gl/GLU.h //颜色宏定义 #define white 1.0f, 1.0f, 1.0f #define black 0.0f, 0.0f, 0.0f #define red 1.0f, 0.0f, 0.0f #define blue 0.0f, 0.0f, 1.0f #define skyBlue 135.0/255.0, 206.0/255.0, 1.0f #define plum 1.0f, 187.0/255.0, 1.0f //浅紫色 #define pink 1.0f, 1

    2024年04月17日
    浏览(65)
  • Unity- 控制物体旋转、移动、缩放的功能

    本文章主要介绍Unity中控制物体旋转、移动、缩放的方法~ 旋转: (一)控制物体自转 (二)控制物体A绕着物体B转 脚本放在物体A上,再将物体B拖到脚本相应位置上 移动: 物体不断向前移动  扩展:使用键盘(上下左右箭头)控制物体前后左右移动 缩放: ① 物体放大一倍

    2024年02月08日
    浏览(49)
  • Unity控制相机旋转、移动、缩放等功能

    提示: 该脚本允许你以指定的速度和角度围绕模型进行相机旋转,并可以控制相机的移动和缩放 将该脚本添加到一个游戏对象上,并将其作为主摄像机。 1、在Unity编辑器中,你可以在脚本的参数变量部分调整相机的速度、缩放和移动等设置。根据需求,修改各个参数的值。

    2024年02月04日
    浏览(57)
  • 微信小程序xr-frame图层与显示(五种方法)

    前言:控制小程序隐藏与显示有五种方法,感兴趣的可以参考这个 文档,但是本文只赘述两种方法。 1.通过visible属性(详见:Three.js模型隐藏或显示) visible:作用就是控制绑定该材质的模型对象是否可见,默认值是true,LineBasicMaterial、SpriteMaterial、MeshBasicMaterial等材质都会继承基

    2024年02月09日
    浏览(65)
  • 利用微信小程序新动画API之this.animate()实现3D旋转

    从微信小程序基础库 2.9.0 开始支持一种更友好的动画创建方式,用于代替旧的 wx.createAnimation 。它具有更好的性能和更可控的接口。 在页面或自定义组件中,当需要进行关键帧动画时,可以使用 this.animate 接口(官方API文档链接地址:https://developers.weixin.qq.com/miniprogram/dev/fr

    2023年04月08日
    浏览(62)
  • Unity鼠标控制3D物体的移动、旋转、缩放

    1.使用协程 2.鼠标左键控制物体移动,鼠标碰到物体,物体颜色改变 1. 控制物体左右旋转,上下旋转 2. 控制摄像机以物体为中心旋转 脚本挂载到摄像机上 1. 基于物体本身的Transform的缩放 2. 基于摄像机的远近的缩放 脚本挂载到摄像机上

    2024年02月11日
    浏览(52)
  • unity3d---移动、缩放、旋转

    目录 1.示意图 2.触屏移动与缩放+键盘移动、旋转与缩放+鼠标移动旋转与缩放 1.示意图   2.触屏移动与缩放+键盘移动、旋转与缩放+鼠标移动旋转与缩放

    2024年02月06日
    浏览(62)
  • Blender基础操作:移动、旋转、缩放、删除、抓取、复制

    1. 物体的移动 微调面板:Adjust last operation:对上一步操作进行微调 2.物体旋转与缩放 快速归位 几个快捷键 ALT+G 坐标归零 ALT+R 旋转归零 ALT+S 缩放归零 3.删除、抓取、复制 快捷键x:确认后删除 (视图不能缩放处理方法:选择视图》对齐视图》游标居中并查看全部/视图中心对

    2024年02月11日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包