总结:用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()区别
标签:拉伸,const,index,prevPoint,旋转,矩形,拖拽,Math From: https://www.cnblogs.com/cbb-web/p/18580072