箭头绘制参考了:https://blog.csdn.net/qq_45939676/article/details/127425426 这位大佬的文章
gitee地址: https://gitee.com/philippines-kisses-snow/uniapp-image-edit.git
组件预览
功能介绍
- 剪切
- 添加文本
- 涂鸦
- 绘制图形(支持箭头,圆形,矩形)
- 旋转
实现思路
- 将屏幕分为两部分:图片编辑部分、操作栏部分
- 图片编辑部分当中创建一个图片容器,容器大小为:图片大小*缩放比例,在容器当中使用
<image />
标签显示图片内容,<image />
的大小占满父容器 - 创建一个
<canvas />
,canvas的大小为图片容器的大小,创建后用于保存图片编辑后的内容,并导出图片,再将image上的图片替换为canvas导出的图片,利用css将这个仅用于保存的canvas标签设置为不可见
一、 剪切
这里只设置了四个夹角用于拖动:左上角、右上角、左下角、右下角
剪切蒙版使用定位,相当于图片容器进行定位,那么蒙版的大小就是可以通过left、top、right、bottom
的定位控制
- 设置蒙版颜色和边框,给蒙版的四个角设置拖动点,添加4个元素,定位到四个角,给这4个元素添加边框,使边框刚好包裹图片的菱角
- 给这四个元素添加拖动事件,并记录每次拖动的起始点,注:在touchmove当中需要更新起始点为当前位置的坐标font>
<view @touchmove.stop="cropTouchMove($event, CROP_TYPE.TOP_LEFT)" @touchstart.stop="cropTouchStart" class="top-left"></view>
<view @touchmove.stop="cropTouchMove($event, CROP_TYPE.TOP_RIGHT)" @touchstart.stop="cropTouchStart" class="top-right"></view>
<view @touchmove.stop="cropTouchMove($event, CROP_TYPE.BOTTOM_RIGHT)" @touchstart.stop="cropTouchStart" class="bottom-right"></view>
<view @touchmove.stop="cropTouchMove($event, CROP_TYPE.BOTTOM_LEFT)" @touchstart.stop="cropTouchStart" class="bottom-left"></view>
- 根据始末位置的坐标差计算定位
例如:移动左上角时:
起始点是(x = 0, y = 0)
当前点为(x = 2, y = 2)
移动差:(diffx = 2, diffy = 2)
由于移动点为左上角,蒙版的定位只有top、left会变化:
top变为:top + diffy
left变为: left + diffx,同理其他几个点类似:
右上角:top变为:top + diffy,right变为: right - diffx
左下角:bottom变为:bottom - diffy,left变为: left + diffx
右下角:bottom变为:bottom - diffy,right变为: right - diffx
出界判断:
0 <= top <= 图片容器高
0 <= left <= 图片容器宽
0 <= right <= 图片容器宽
0 <= bottom <= 图片容器高
最小剪切框大小限制:
图片宽 - right - left >= 最小宽
图片高 - bottom - top >= 最小高
- 导出,剪切完成后通过canvas的
drawImage
将图片渲染到canvas上,然后导出时,根据剪切蒙版的定位坐标计算出蒙版宽高,以蒙版左上角坐标开始导出蒙版宽高的图片即可
二、添加文本
文本添加与剪切类似,在图片容器内添加一个文本容器,并相对于图片容器定位,大小占满图片容器,创建一个文本数组用于保存文本框的属性:内容、位置,在文本容器内循环遍历文本数组,删除时只需删除对应索引的文本即可,保存时遍历数组,将文本循环绘制到canvas上,最后保存即可,双击图片可以新建一个文本框
// 绘制时的文本换行处理:
/**
* 绘制文本
* @param {Object} _this 组件实例
* @param {String} canvasID canvasID
* @param {String} text 文本字符串
* @param {Number} fontH 单行字体高度
* @param {Number} maxW 最大文本域宽
* @param {Number} startX 开始x坐标
* @param {Number} startY 开始y坐标
*/
export function drawText(_this, canvasID, textArea, fontSize, fontH, maxW, startX, startY) {
const ctx = uni.createCanvasContext(canvasID, _this)
const text = textArea.text
ctx.setFontSize(fontSize)
ctx.fillStyle = textArea.color
let rowW = 0
let rowTexts = []
let startIndex = 0
// 将一段长文本拆分为多段
for(let i = 0; i < text.length; i++) {
rowW += ctx.measureText(text[i]).width
if(rowW > maxW) {
rowTexts.push(text.substr(startIndex, i))
startIndex = i
rowW = 0
}
}
rowTexts.push(text.substr(startIndex))
// 分段绘制
rowTexts.forEach((str, index) => {
const y = index === 0 ? startY : index*fontH + startY
ctx.fillText(str, startX, y);
ctx.draw(true)
})
}
三、涂鸦
由于涂鸦实时性要求较高,图形不规则且连贯,需要使用canvas进行实时绘制
- 在图片容器内创建一个canvas,canvas大小为图片容器大小
- 涂鸦时直接在canvas上绘制即可(具体绘制过程的代码就不贴了,可在git当中查看)
- 保存时需导出该涂鸦图片,使用保存用的canvas绘制这两张图片,先绘制原图,再绘制涂鸦,导出即可
四、图形
在图片容器当中创建两个canvas(其中一个可与绘制涂鸦的canvas复用),获取起始点与当前点坐标在最外层的canvas上绘制相应图形,在滑动结束后将图形信息用数组保存起来,遍历数组绘制到下层的canvas上,清空最外层的canvas
箭头绘制需在箭头终点绘制两侧箭头边,具体计算可查看代码和参考的文章