uni-app图片旋转和镜像处理


图片编辑里面旋转和镜像看起来只是一个 CSS transform,但是要和拖拽、缩放、保存结合起来,就需要多考虑几层

图片编辑组件里面有两个比较常见的操作:

  1. 旋转
  2. 镜像

如果只是页面上看起来变了,确实很简单。

1
2
transform: rotate(90deg)
transform: scale(-1, 1)

但是这个组件最后要保存成一张新的图片。

所以不能只考虑页面效果,还要考虑当前状态如何参与最终截图。

CSS只是视觉变化

在页面中对图片做旋转和镜像,本质上只是改变了元素的显示状态。

图片文件本身没有变化。

也就是说,用户看到的图片已经旋转了,但是原始图片还是原来的方向。

如果直接拿原图片去保存,保存出来肯定不是用户编辑后的效果。

所以组件里是先用 CSS transform 表现编辑状态,最后再通过 html2canvas 把编辑区域重新生成成图片。

这也是为什么图片编辑不能只写一行 rotate 就结束。

拆成两层transform

组件里面没有把所有 transform 都放在同一个元素上,而是拆成了两层。

外层负责缩放和旋转:

1
this.wrapperStyle.transform = `scale(${this.currentScale}) rotate(${this.currentRotation}deg)`

内层负责拖动和镜像:

1
2
const mirrorScale = this.isMirrored ? -1 : 1
this.imageStyle.transform = `translate(${this.currentTranslateX}px, ${this.currentTranslateY}px) scale(${mirrorScale}, 1)`

这样拆的好处是状态更清楚。

缩放旋转是一组,拖动镜像是一组。

后面处理手势的时候,也更容易知道哪个状态该改。

为什么镜像要单独处理

镜像不是单纯显示反过来。

当图片镜像以后,用户的操作方向也会跟着改变。

比如原来手指向右拖,图片向右移动。

镜像以后,如果还是按原来的 dx 去处理,视觉上就会有一种反着来的感觉。

所以组件里面在拖动时判断了镜像状态:

1
2
3
if (this.isMirrored == 1) {
dx = -dx
}

旋转时也要处理:

1
2
3
if (this.isMirrored == 1) {
angleDelta = -angleDelta
}

这个点很容易被忽略。

因为镜像不是换了一张图片,而是当前坐标系翻转了。

90度旋转

组件里还有一个方法是 rotateCounterClockwise

这个方法不是自由旋转,而是每次逆时针转 90 度。

代码大概是这样:

1
2
3
4
5
6
7
8
9
10
11
rotateCounterClockwise() {
const currentRotation = this.currentRotation % 360
let targetRotation = currentRotation - (currentRotation % 90) - 90

if (targetRotation < -360) {
targetRotation += 360
}

this.currentRotation = targetRotation
this.updateTransform()
}

这里没有简单地 currentRotation -= 90,而是先把当前角度归到 90 度的节奏上。

因为用户可能已经通过双指或者旋转图标自由旋转过了。

如果当前是 37deg,再点一次 90 度旋转,最好是变成一个规整的角度,而不是 -53deg 这种不太直观的状态。

旋转图标

除了双指旋转,组件还做了一个右下角旋转图标。

这个图标本质上是根据手指和图片中心点的角度变化来旋转。

开始时记录触摸点到中心的角度:

1
this.rotateIconStartAngle = this.getAngleFromCenter(x, y)

移动时重新计算当前角度:

1
2
3
const currentAngle = this.getAngleFromCenter(currentPos.x, currentPos.y)
let angleDelta = currentAngle - this.rotateIconStartAngle
this.currentRotation = this.initialRotation + angleDelta

同时还会根据触摸点到中心点距离变化来缩放:

1
2
this.currentScale =
(currentDistance / this.rotateIconStartDistance) * this.initialScale

这样一个图标就可以同时控制旋转和缩放。

为什么保存时要忽略图标

旋转图标只是编辑工具,不应该出现在最终图片里。

所以保存时使用 html2canvas,需要忽略这个元素:

1
2
3
ignoreElements: function(element) {
return element.classList.contains('rotate-icon')
}

这个细节也说明了一点,页面上用于交互的元素,不一定都是最终图片的一部分。

做截图保存时要主动过滤。

圆形裁剪区域

组件里还有一个圆形裁剪效果。

外层遮罩使用了径向渐变:

1
2
3
4
5
background: radial-gradient(
circle at center,
transparent 345rpx,
rgba(0, 0, 0, 0.4) 345rpx
)

真正保存时,主要截取中间的 drop-center

这样页面上看到的是一个圆形头像裁剪框,保存出来的也是用户当前框选区域。

总结

图片旋转和镜像的问题,不只是 CSS 怎么写。

更重要的是:

  1. 当前状态要能持续记录
  2. 手势计算要考虑旋转和镜像后的坐标变化
  3. 保存时要把视觉状态重新生成成图片
  4. 编辑辅助元素不能进入最终截图

所以这个组件里,旋转、镜像、拖动、缩放和保存其实是一套完整逻辑。

以上就是我对 uni-app 图片旋转和镜像处理的理解,如有错误,欢迎大佬指出。

-------------本文结束感谢您的阅读-------------