总结一下:
要进行海报绘制离不开canvas,我们是先进行图片,文字的拖拽、旋转等操作
最后再对canvas进行绘制,完成海报绘制。
- 背景区域设置为 position: relative,方便图片在当前区域中拖动等处理。
- 添加图片,监听图片在背景区域下的 touchstart touchmove touchend 事件
- 拖动图片,在touchmove中,对图片进行位置(后续坐标-初始坐标)、角度(勾股定理计算点到圆心距离,利用角度计算公式计算)、放缩比例(勾股定理计算点到圆心的半径距离,拖动停止的半径除以初始的半径,获得放缩比例scale)的计算
- 最终canvas绘制,利用dom中的空canvas,将图片依次绘制到canvas中,并获取链接
部分主要代码如下:
const ctx = uni.createCanvasContext("myCanvas", this);
ctx.drawImage(this.imageSrc, 0, 0, IMG_REAL_W, IMG_REAL_H);
const ctx = uni.createCanvasContext("myCanvas", this);
ctx.drawImage(this.imageSrc, 0, 0, IMG_REAL_W, IMG_REAL_H);
ctx.save();
ctx.beginPath();
// 画背景色(白色)
// ctx.setFillStyle('#fff');
// ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
for (let i=0; i<items.length; i++) {
const cur = items[i]
ctx.save();
ctx.translate(0, 0);
ctx.beginPath();
if(cur.image) {
ctx.translate(cur.x, cur.y); // 圆心坐标
ctx.rotate(cur.angle * Math.PI / 180); // 图片旋转的角度
ctx.translate(-(cur.width * cur.scale / 2), -(cur.height * cur.scale / 2)) // 图片的缩放
ctx.drawImage(cur.image, 0, 0, cur.width * cur.scale, cur.height * cur.scale); // 图片绘制
}
if (cur.text) {
ctx.font = `${cur.font}px arial`;
ctx.fillStyle = cur.fillStyle;
ctx.fillText(cur.text, cur.left, cur.top + Number(cur.font));
console.log(cur.left, cur.top + Number(cur.font))
}
ctx.restore();
}
ctx.draw(true, () => {
// 获取画布要裁剪的位置和宽度 均为百分比 * 画布中图片的宽度 保证了在微信小程序中裁剪的图片模糊 位置不对的问题 canvasT = (this.cutT / this.cropperH) * (this.imageH / pixelRatio)
var canvasW = ((this.cropperW - this.cutL - this.cutR) / this.cropperW) * IMG_REAL_W;
var canvasH = ((this.cropperH - this.cutT - this.cutB) / this.cropperH) * IMG_REAL_H;
var canvasL = (this.cutL / this.cropperW) * IMG_REAL_W;
var canvasT = (this.cutT / this.cropperH) * IMG_REAL_H;
uni.canvasToTempFilePath({
x: canvasL,
y: canvasT,
width: canvasW,
height: canvasH,
// destWidth: canvasW,
// destHeight: canvasH,
quality: +this.quality,
fileType: this.fileType,
canvasId: "myCanvas",
success: (res) => {
uni.hideLoading();
// 成功获得地址的地方
// this.$emit("getImg", res.tempFilePath);
this.saveImg(res.tempFilePath)
this.isShow = false;
},
fail: (err) => {
uni.hideLoading();
uni.showToast({
title: "图片截取失败!",
icon: "none",
});
},
},
this
);
});
<!-- 海报背景区域,采用style动态调整,cropperInitW,cropperInitH一般为满屏 -->
<view class="uni-corpper"
:style="'width:' + cropperInitW + 'px;height:' + cropperInitH + 'px;background:#000'">
<!-- 海报绘制区域,采用style动态调整,按照图片的长宽比例动态计算 cropperW等-->
<view class="uni-corpper-content" :style="
'width:' +
cropperW +
'px;height:' +
cropperH +
'px;left:' +
cropperL +
'px;top:' +
cropperT +
'px'
">
<!-- 背景图片区域 cropperW等同上 -->
<image :src="imageSrc" :style="'width:' + cropperW + 'px;height:' + cropperH + 'px;' + 'border: 3px solid #ff0000;'"></image>
<!-- 海报上其他图片处理,for循环,itemList通过点击添加 -->
<block v-for="item in itemList" :key="item.id">
<!-- 动态设置图片区域的缩放比例,还有pisition的左右位置,选中时z-index变大 -->
<view class='touchWrap' :style="{transform: 'scale(' + item.scale + ')', top: item.top + 'px', left: item.left + 'px', 'z-index':item.active ? 100 : 1}">
<view class='imgWrap' :class="item.active ? 'touchActive' : ''" :style="{transform: 'rotate(' + item.angle + 'deg)', border: item.active ? 4 * item.oScale : 0 + 'rpx #fff dashed'}">
<image
v-if="item.image"
:src='item.image'
:style="{width: item.width + 'px', height: item.height + 'px'}"
<!-- 图片点击时,记录点击图片当前位置 -->
@touchstart="(e) => WraptouchStart(e, item)"
<!-- 图片拖动时,记录图片当前位置,并实时计算图片大小、旋转角度等,并存储至itemList中 -->
@touchmove="(e) => WraptouchMove(e, item)"
<!--一般不做处理 -->
@touchend="(e) => WraptouchEnd(e, item)"
mode="widthFix"
>
</image>
<!-- 删除按钮 -->
<image
class='x'
src='/static/close.png'
:style="{transform: 'scale(' + item.oScale + ')', 'transform-origin': center}"
@click="(e) => deleteItem(e, item)"
>
</image>
<!-- 图片放缩按钮 -->
<image
v-if="item.image"
class='o'
src='/static/scale.png'
:style="{transform: 'scale(' + item.oScale + ')', 'transform-origin': center}"
<!-- 图片点击时,记录点击图片当前坐标,半径 -->
@touchstart="(e) => oTouchStart(e, item)"
<!-- 图片点击时,记录点击图片当前坐标,计算新的半径(得到scale缩放比例)计算角度差,获取当前角度 -->
@touchmove="(e) => oTouchMove(e, item)"
@touchend="(e) => WraptouchEnd(e, item)"
>
</image>
</view>
</view>
</block>
</view>
<canvas canvas-id="myCanvas" :style="
'position:absolute;border: 2px solid red; width:' +
imageW +
'px;height:' +
imageH +
'px;top:-9999px;left:-9999px;'
">
</canvas>
</view>
// 点击图片或文字
WraptouchStart(e, it) {
currentChoose = it
// 循环图片数组获取点击的图片信息
for (let i = 0; i < items.length; i++) {
items[i].active = false;
if (it.id == items[i].id) {
index = i;
items[index].active = true;
}
}
// this.setData({
// itemList: items
// })
this.setList(items, 'itemList')
// 获取点击的坐标值 lx ly是图片点击时的位置值
items[index].lx = e.touches[0].clientX;
items[index].ly = e.touches[0].clientY;
},
// 拖动图片
WraptouchMove(e) {
// 获取点击的坐标值 _lx _ly 是图片移动的位置值
items[index]._lx = e.touches[0].clientX;
items[index]._ly = e.touches[0].clientY;
// left 是_lx 减去 lx,_ly 减去 ly,也就是现在位置,减去原来的位置。
items[index].left += items[index]._lx - items[index].lx;
items[index].top += items[index]._ly - items[index].ly;
// 同理更新图片中心坐标点,用于旋转
items[index].x += items[index]._lx - items[index].lx;
items[index].y += items[index]._ly - items[index].ly;
// 停止了以后,把lx的值赋值为现在的位置
items[index].lx = e.touches[0].clientX;
items[index].ly = e.touches[0].clientY;
// this.setData({
// itemList: items
// })
this.setList(items, 'itemList')
},
// 放开图片
WraptouchEnd(e, it) {
touchNum ++
clearTimeout(timer)
timer = null
timer = setTimeout(this.timeSta, 250)
},
// 计算坐标点到圆心的距离
getDistancs(cx, cy, pointer_x, pointer_y) {
var ox = pointer_x - cx;
var oy = pointer_y - cy;
return Math.sqrt(
ox * ox + oy * oy
);
},
/*
*参数cx和cy为图片圆心坐标
*参数pointer_x和pointer_y为手点击的坐标
*返回值为手点击的坐标到圆心的角度
*/
countDeg(cx, cy, pointer_x, pointer_y) {
var ox = pointer_x - cx;
var oy = pointer_y - cy;
var to = Math.abs(ox / oy); // 勾股定理,计算当前点距离中心点的距离。
var angle = Math.atan(to) / (2 * Math.PI) * 360; // 计算当前角度
if (ox < 0 && oy < 0) //相对在左上角,第四象限,js中坐标系是从左上角开始的,这里的象限是正常坐标系
{
angle = -angle;
} else if (ox <= 0 && oy >= 0) //左下角,3象限
{
angle = -(180 - angle)
} else if (ox > 0 && oy < 0) //右上角,1象限
{
angle = angle;
} else if (ox > 0 && oy > 0) //右下角,2象限
{
angle = 180 - angle;
}
return angle; // 返回角度
},
体验: