关于矩形旋转,拖拽,拉伸

总结:用svg和<rect/> 无法同时实现三个效果,如果不实现拖拽效果,只实现旋转和拉伸可以采用和transform实现

因为,拖拽会导致拖拽中心的偏移导致无法计算新的旋转中心。

如果要同时实现这三个效果只能使用<polygon/>在旋转时候,直接根据旋转角度计算四个点的位置。在拉伸时候,计算拉伸的点在邻边的投影。保证矩形。

在拖拽后,直接根据四个点计算新的旋转中心

其实不使用svg来进行实现,更简单 

以下是我得出结果的过程

1、旋转中心点是以矩形中心旋转。

目前有一个项目。已经实现了矩形的拉伸和拖拽。只要求加上个旋转

从代码上看,项目是采用svg 的rect元素实现的。但是有几个问题

1、rect的元素。是以(x,y)为元素的左上角位置。width 和height 决定元素。这个就导致rect元素本身无法实现旋转角度的。首先想的用css实现

2、普通元素的旋转是默认几何中心的,svg内的元素默认的旋转是以<svg/>的左上角。svg有一个 transform="rotate(90, 150 120)",第一个参数是

角度,后面是旋转中心坐标。所以以矩形的x,y和宽高,去计算旋转中心。

 这样的话,确实能实现要求的以中心旋转的问题。

根据鼠标移动的位置和初始位置的角度差值,计算旋转角度得到transform的第一个参数,后面两个参数,直接计算矩形重心

计算鼠标角度的方法:

mouseX,mouseY是鼠标相对于视图的位置, 所以centerX,centerY是旋转中心相对于视图的位置所以可以用getBoundingClientRect()

    getAngle(mouseX, mouseY) {
      const { centerX, centerY } = this.rotateCenter
      const angle = Math.atan2(mouseY - centerY, mouseX - centerX) * (180 / Math.PI);
      return angle < 0 ? 360 + angle : angle
    },

但是在旋转的时候,很有可能会出界。这个限制范围要修改。

旋转矩形是否出界,不需要去计算四个角的位置,其实只要判断矩形的外接矩形是否出界。关于矩形的外接矩形使用getBBox()方法。

 

其实到这里,三个功能都已经有了。但是连续进行测试的时候有问题

如果矩形经过transform该方法返回的也是未旋转前的属性。在未进行旋转变化的时候,计算的位置确实是重心,但是在进行拖拽后。

有旋转的偏移量,但是这个时候重新计算,算出来的结果就不对了。导致了旋转后进行拖拽,矩形会突然跳位置,跳位置后再旋转就没问题了。

我自己其实到这里的时候也没想过换<polygon/>。新的旋转重心肯定跟老的旋转角度有一定关系,我尝试使用点旋转后位置去计算,但是还是不对。

也是因为这个旋转重心的问题,我才想用<polygon/>.因为这个元素本身根据四个点可以直接实现矩形旋转的效果,而且四个点的位置可以直接定旋转重心。

 

polygon实现矩形。

用polygon旋转中心的问题后,有其他问题

1、根据旋转角度去计算点关于角度偏移的位置

2、其中一个点拉伸后,为了保证矩形,其他点的计算(拉伸点在相邻两个边的投影就是拉伸后的坐标,即点在直线上的投影)

3、出界问题(既然能拿到四个点,我是直接计算的,用外接矩形方式也可)

图上123数字是点在拉伸方法中计算index的位置。

 

复制代码
/**
     * 根据旋转中心和旋转角度计算点旋转后的位置
     * @param {*} centerpPoint  旋转中心点
     * @param {*} angle 旋转角度
     * @param {*} point 旋转点
     * @returns 新的点坐标
     */
    rotatePoint(centerpPoint, angle, point) {
      const { cx, cy } = centerpPoint
      const { px, py } = point
      let radians = (Math.PI / 180) * angle;
      let cos = Math.cos(radians);
      let sin = Math.sin(radians);

      let nx = (cos * (px - cx)) + (sin * (py - cy)) + cx;
      let ny = (cos * (py - cy)) - (sin * (px - cx)) + cy;

      return { x: nx, y: ny };
    },
复制代码
复制代码
 /**
     * 计算四个角拉伸后的矩形四个点
     * @param {Array} points // 未拉伸前矩形四个点坐标数组[{x,y}]
     * @param {Number} pointIndex // 当前点在矩形8个点中的index 0,1,2,3,4,5,6,7,8 左上角开始计算
     * @param {Number} dx //鼠标横向偏移量
     * @param {Number} dy //鼠标纵向偏移量
     * @returns 
     */
    fixRectPoint(points, pointIndex, dx, dy) {
      let orgPoints = JSON.parse(JSON.stringify(points)) //深拷贝
      let result = []
      const index = pointIndex / 2
      let nowPoint = orgPoints[index]
      nowPoint.x += dx;
      nowPoint.y += dy;
      const prevPoint_index = (index - 1) < 0 ? 3 : index - 1 // 上一个点 的 index
      const nextPoint_index = (index + 1) % 4 // 下一个点 的 index
      const diagonalPoint_index = (index + 2) % 4 // 对角点 的 index
      const prevPoint = orgPoints[prevPoint_index]
      const nextPoint = orgPoints[nextPoint_index]
      const diagonalPoint = orgPoints[diagonalPoint_index]
      const point_prev = this.fixPointOntoLinePoint(nowPoint, diagonalPoint, prevPoint)
      const point_next = this.fixPointOntoLinePoint(nowPoint, diagonalPoint, nextPoint)
      result[index] = nowPoint
      result[prevPoint_index] = point_prev
      result[nextPoint_index] = point_next
      result[diagonalPoint_index] = diagonalPoint
      return result
    },
复制代码

 

复制代码
 /**
     * 计算矩形四个边中间点拉伸后的矩形四个点
     * @param {Array} points // 未拉伸前矩形四个点坐标数组[{x,y}]
     * @param {*} index  // 当前点在矩形8个点中的index 0,1,2,3,4,5,6,7,8 左上角开始计算
     * @param {*} nowPoint //当前点未拉伸前的坐标{x,y}
     * @param {*} dx  //鼠标横向偏移量
     * @param {*} dy //鼠标纵向偏移量
     * @returns 
     */
    fixCenterPoint(points, pointIndex, nowPoint, dx, dy) {
      let result = JSON.parse(JSON.stringify(points)) //深拷贝
      nowPoint.x += dx;
      nowPoint.y += dy;
      const prevPoint_index = ((pointIndex - 1) < 0 ? 7 : pointIndex - 1) / 2 // 上一个点 的 index
      const nextPoint_index = ((pointIndex + 1) % 8) / 2 // 下一个点 的 index
      const prev_prevPoint_index = prevPoint_index - 1 < 0 ? 3 : prevPoint_index - 1 // 下一个点 的 index
      const next_nextPoint_index = (nextPoint_index + 1) % 4
      const prev_prevPoint = points[prev_prevPoint_index]
      const prevPoint = points[prevPoint_index]
      const nextPoint = points[nextPoint_index]
      const next_nextPoint = points[next_nextPoint_index]
      const point_prev = this.fixPointOntoLinePoint(nowPoint, prev_prevPoint, prevPoint)
      const point_next = this.fixPointOntoLinePoint(nowPoint, next_nextPoint, nextPoint)
      result[prevPoint_index] = point_prev
      result[nextPoint_index] = point_next
      return result
    },
复制代码
复制代码
 /**
     * 计算p0在两点p1和p2所构成的直线方程的投影坐标
     */
    fixPointOntoLinePoint(p0, p1, p2) {
      const { x: x0, y: y0 } = p0 // 投影点
      const { x: x1, y: y1 } = p1 //所在直线上点1
      const { x: x2, y: y2 } = p2 // 所在直线上点2
      // (y - y1) / (y2 - y1) = (x - x1) / (x2 - x1) 两点直线方程化简可得
      // (y2 - y1)x - (x2 - x1)y + (x2 - x1)y1 - (y2 - y1)x1 = 0 ax + by + c = 0
      const a = (y2 - y1)
      const b = - (x2 - x1)
      const c = (x2 - x1) * y1 - (y2 - y1) * x1
      // 点 Q(x,y)是直线L(ax + by + c = 0)上离点P(x0,y0)最近的点,于是向量PQ与L垂直,
      // 则它们的点积为0  (x-x0)(a)+(y-y0)(b)=0 
      // const x = (b * b * x0 - a * b * y0 - a * c) / (a * a + b * b)
      // const y = (a * a * y0 - a * b * x0 - b * c) / (a * a + b * b)
      const x = (Math.pow(b, 2) * x0 - a * b * y0 - a * c) / (Math.pow(a, 2) + Math.pow(b, 2))
      const y = (Math.pow(a, 2) * y0 - a * b * x0 - b * c) / (Math.pow(a, 2) + Math.pow(b, 2))
      return { x, y }
    },
复制代码
复制代码
/**
     * 判断矩形元素是否出界
     * @param {*} element  html dom结构
     * @param {Boolean} isNeedData  是否出界数据 false 不需要 出界数据负数代表出界
     * @returns {Boolean||Object} isNeedData=true 返回Object 
     */
    isOutRange(points, isNeedData = false) {
      let left = 0, right = 0, top = 0, bottom = 0;
      for (let i = 0; i < points.length; i++) {
        const item = points[i]
        if (i === 0) {
          left = item.x
          right = item.x
          top = item.y
          bottom = item.y
        } else {
          left = Math.min(left, item.x)
          right = Math.max(right, item.x)
          top = Math.min(top, item.y)
          bottom = Math.max(bottom, item.y)
        }
      }
      const imgDom = document.getElementById("mark-img")
      const totalWidth = parseFloat(imgDom.getAttribute("width"))
      const totalHeight = parseFloat(imgDom.getAttribute("height"))
      let outRangeData = { left: 0, right: 0, top: 0, bottom: 0, width: 0, height: 0 }
      let outRangeFlag = false
      if (left < 0) { // 左边出界
        outRangeData.left = left
        outRangeFlag = true
      }
      if (top < 0) { //上边出界
        outRangeData.top = top
        outRangeFlag = true
      }
      if (right > totalWidth) { //右边出界,
        outRangeData.right = (totalWidth - right)
        outRangeFlag = true
      }
      if (bottom > totalHeight) { //下边出界,
        outRangeData.bottom = (totalHeight - bottom)
        outRangeFlag = true
      }
      if (right - left > totalWidth) { //宽度出界
        outRangeData.width = totalWidth - (right - left)
      }
      if (bottom - top > totalHeight) { //高度出界
        outRangeData.height = totalHeight - (bottom - top)
      }
      return isNeedData ? { outRangeData, outRangeFlag } : outRangeFlag
    },
复制代码

 具体代码实现,后续整理放出

 

关于getBoundingClientRect()和getBBox()区别

 

posted @   辛夷不改年年色  阅读(46)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示