uni-app图片旋转和镜像处理
图片编辑里面旋转和镜像看起来只是一个 CSS transform,但是要和拖拽、缩放、保存结合起来,就需要多考虑几层
图片编辑组件里面有两个比较常见的操作:
- 旋转
- 镜像
如果只是页面上看起来变了,确实很简单。
1 | transform: rotate(90deg) |
但是这个组件最后要保存成一张新的图片。
所以不能只考虑页面效果,还要考虑当前状态如何参与最终截图。
CSS只是视觉变化
在页面中对图片做旋转和镜像,本质上只是改变了元素的显示状态。
图片文件本身没有变化。
也就是说,用户看到的图片已经旋转了,但是原始图片还是原来的方向。
如果直接拿原图片去保存,保存出来肯定不是用户编辑后的效果。
所以组件里是先用 CSS transform 表现编辑状态,最后再通过 html2canvas 把编辑区域重新生成成图片。
这也是为什么图片编辑不能只写一行 rotate 就结束。
拆成两层transform
组件里面没有把所有 transform 都放在同一个元素上,而是拆成了两层。
外层负责缩放和旋转:
1 | this.wrapperStyle.transform = `scale(${this.currentScale}) rotate(${this.currentRotation}deg)` |
内层负责拖动和镜像:
1 | const mirrorScale = this.isMirrored ? -1 : 1 |
这样拆的好处是状态更清楚。
缩放旋转是一组,拖动镜像是一组。
后面处理手势的时候,也更容易知道哪个状态该改。
为什么镜像要单独处理
镜像不是单纯显示反过来。
当图片镜像以后,用户的操作方向也会跟着改变。
比如原来手指向右拖,图片向右移动。
镜像以后,如果还是按原来的 dx 去处理,视觉上就会有一种反着来的感觉。
所以组件里面在拖动时判断了镜像状态:
1 | if (this.isMirrored == 1) { |
旋转时也要处理:
1 | if (this.isMirrored == 1) { |
这个点很容易被忽略。
因为镜像不是换了一张图片,而是当前坐标系翻转了。
90度旋转
组件里还有一个方法是 rotateCounterClockwise。
这个方法不是自由旋转,而是每次逆时针转 90 度。
代码大概是这样:
1 | rotateCounterClockwise() { |
这里没有简单地 currentRotation -= 90,而是先把当前角度归到 90 度的节奏上。
因为用户可能已经通过双指或者旋转图标自由旋转过了。
如果当前是 37deg,再点一次 90 度旋转,最好是变成一个规整的角度,而不是 -53deg 这种不太直观的状态。
旋转图标
除了双指旋转,组件还做了一个右下角旋转图标。
这个图标本质上是根据手指和图片中心点的角度变化来旋转。
开始时记录触摸点到中心的角度:
1 | this.rotateIconStartAngle = this.getAngleFromCenter(x, y) |
移动时重新计算当前角度:
1 | const currentAngle = this.getAngleFromCenter(currentPos.x, currentPos.y) |
同时还会根据触摸点到中心点距离变化来缩放:
1 | this.currentScale = |
这样一个图标就可以同时控制旋转和缩放。
为什么保存时要忽略图标
旋转图标只是编辑工具,不应该出现在最终图片里。
所以保存时使用 html2canvas,需要忽略这个元素:
1 | ignoreElements: function(element) { |
这个细节也说明了一点,页面上用于交互的元素,不一定都是最终图片的一部分。
做截图保存时要主动过滤。
圆形裁剪区域
组件里还有一个圆形裁剪效果。
外层遮罩使用了径向渐变:
1 | background: radial-gradient( |
真正保存时,主要截取中间的 drop-center。
这样页面上看到的是一个圆形头像裁剪框,保存出来的也是用户当前框选区域。
总结
图片旋转和镜像的问题,不只是 CSS 怎么写。
更重要的是:
- 当前状态要能持续记录
- 手势计算要考虑旋转和镜像后的坐标变化
- 保存时要把视觉状态重新生成成图片
- 编辑辅助元素不能进入最终截图
所以这个组件里,旋转、镜像、拖动、缩放和保存其实是一套完整逻辑。
以上就是我对 uni-app 图片旋转和镜像处理的理解,如有错误,欢迎大佬指出。