大家好,我是前端西瓜哥。图形编辑器的一个需求是通过属性面板的输入框来设置选中元素的属性值。项目地址,欢迎star:https://github.com/F-star/suika在线体验:https://blog.fstars.wang/app/suika/最终效果如下:元素对象的结构:接口IGraph{x:数字;y:数字;宽度:数字;高度:数字;旋转:数量;//旋转角度,单位为弧度}setx/yUI界面上所说的x/y是指旋转后的x(即rotatedX/rotatedY)。为什么不对应真实的x和y呢?因为它需要对应用户的视角。开发者理解底层,理解一个图有基本的物理信息(x,y,width,height),然后在绘制前进行变换(旋转,缩放等)得到新的坐标。用户看到的是一个直观绘制的图形,希望图形左上角的坐标能够和自己设置的坐标相匹配。旋转前的x和y不能直观的体现在canvas上,用户也不会关心。OK,我们先看看怎么修改rotatedX。图形对象上没有rotatedX属性,本质是修改x值。我们先看看rotatedX和rotatedY是怎么计算的。其实就是根据图的中点旋转计算x和y的结果://旋转坐标functiontransformRotate(x,y,radian,cx,cy){if(!radian){return[x,y];}constcos=Math.cos(弧度);constsin=Math.sin(弧度);return[(x-cx)*cos-(y-cy)*sin+cx,(x-cx)*sin+(y-cy)*cos+cy,];}//计算旋转后的x和yconst[rotatedX,rotatedY]=transformRotate(x,y,rotation,cx,cy);计算一个元素rotatedX/rotatedY://计算中点函数getRectCenterPoint({x,y,width,height}){return[x+width/2,y+height/2];}//计算rotatedX/rotatedYexportfunctiongetElementRotatedXY(element){const[cx,cy]=getRectCenterPoint(element);returntransformRotate(element.x,element.y,element.rotation||0,cx,cy);}所以,设置newrotatedX,其实就是在移动前后加上一个rotatedX的偏移值,加上它到x。类图{//...setRotatedX(rotatedX){const[prevRotatedX]=getElementRotatedXY(this);constdx=rotatedX-prevRotatedX;这个.x+=dx;}}rotatedY同样:classGraph{//...setRotatedY(rotatedY:number){const[,prevRotatedY]=getElementRotatedXY(this);constdy=rotatedY-prevRotatedY;这个.y+=dy;}}setwidth/height先修改宽高。但是这会导致rotatedX和rotatedY发生偏移,我们需要修复它。修正的方式有两种:思路一:计算修改宽度前后rotatedX/rotatedY的差值,对元素进行修正。const[preRotatedX,preRotatedY]=getElementRotatedXY(el);//修改前的el.widthwidth=width;const[rotatedX,rotatedY]=getElementRotatedXY(el);//const修改宽度后dx=rotatedX-preRotatedX;constdy=rotatedY-preRotatedY;el.x-=dx;//"-"是因为状态el.y-=dy需要恢复;思路二:确定最终的rotatedX/rotatedY,然后等待前面的transformRotate方法反推公式,通过rotatedX、rotatedY、弧度、width、height计算出对应的x和y。这个想法比前一个复杂一点。const[rotatedX,rotatedY]=getElementRotatedXY(el);埃尔。宽度=宽度;const[x,y]=getOriginXY(rotatedX,rotatedY,el.rotation||0,width,el.height);埃尔。x=x;el.y=y;/***旋转前计算x和y*transformRotate的逆函数*/functiongetOriginXY(rotatedX,rotatedY,radian,width,height){if(!radian){return[rotatedX,旋转Y];}constcos=Math.cos(弧度);constsin=Math.sin(弧度);常量halfWidth=宽度/2;consthalfHeight=height/2;return[rotatedX-halfWidth-halfHeight*sin+halfWidth*cos,rotatedY-halfHeight+halfHeight*cos+halfWidth*sin,];}我一开始是用思路2实现的。后面写这篇文章的时候,顺应了思路1的解决方案,因为比较简单,也比较好理解,所以改成思路1的实现。修改旋转修改旋转很简单,改一下就可以了它直接。但是需要注意将度数转换成弧度,并通过取余来限制弧度的范围。//角度转弧度functiondegree2Radian(degree:number){return(degree*Math.PI)/180;}/***归一化角度*/constPI_DOUBLE=2*Math.PI;exportconstnormalizeAngle=(angle)=>{returnangle%PI_DOUBLE;};element.rotation=normalizeAngle(degree2Radian(rotation));结束算法的实现并不复杂。
