微信小程序-canvas绘实现贴纸效果

在做微信小程序时,碰到一个需求,要求用户上传一张照片进行裁剪,选择贴纸后生成一张图片,这里来分享一下我实现的方法。

一.结构部分

1.首先是将原始图片放在movable-area组件内部,原始图片保持与movable-area 相同高宽,(说明:在这一步之前已经做过图片裁剪了,这一步这里的原始的图片的高宽都是一样的,即movable-area的高宽);类名sticker-box内部的就是贴纸图片以及取消贴纸的叉叉。

<movable-area class='img-box width-full' style="width:320px; height:178.133px">

<image class='original-img' mode="widthFix" src='{{imgUrl}}'></image> 

<!-- 贴图开始 -->

<movable-view wx:if="{{chosedImg}}" style="transform:translate({{stv.offsetX}}px, {{stv.offsetY}}px);width:{{stv.width}}px;height:{{stv.height}}px" x="{{x}}" y="{{y}}" direction="all">

<view class='sticker-box' style=' rotate({{Img.rotate}}deg)' catchtouchstart="touchstartCallback" catchtouchmove="touchmoveCallback" catchtouchend="touchendCallback" >

<image class='sticker width-full' mode="widthFix" src="{{chosedImg}}"></image>

</view>

<image class='cancel' bindtap='cancel' src='../../images/cancel.png'></image>

</movable-view>

<!-- 贴图结束 -->

</movable-area>

 

2.底部贴纸列表

<view class='bottom'>

<view class="sticker-lists-body">

<scroll-view class="recommend_scroll_x_box" scroll-x="true">

<view class="sticker-list" wx:for="{{stickers}}" data-url="{{item}}" bindtap='changeImg'>

<image src='{{item}}'></image>

</view>

</scroll-view>

</view>

<view class='tab'>

<view class='tab-list clearfix'>

<image class='active' mode="widthFix" src='../../images/icon05.png'></image>

</view>

<button class='color-white' bindtap='save'>下一步 </button>

<button bindtap='toImg' class='color-red'> 上一步 </button>

</view>

</view>

 

3.用于绘图的canvas

<canvas style="width: 640px;height: 356.266px;" canvas-id="mycanvas"/>

二.样式部分

page {
height: 100%;
}
.width-full {
width: 100%;
}
.color-white {
color: #fff;
}

.color-red{
color: #f56259;
}
.bg-white {
background-color: #fff;
}
.bg-red {
background-color: #f56259;
}
.flex {
display: box; /* OLD - Android 4.4- */
display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6 */
display: -moz-box; /* OLD - Firefox 19- (buggy but mostly works) */
display: -ms-flexbox; /* TWEENER - IE 10 */
display: -webkit-flex; /* NEW - Chrome */
display: flex;
}
.flex-hc {
-webkit-box-pack: center;
-webkit-justify-content: center;
-moz-justify-content: center;
-ms-justify-content: center;
-o-justify-content: center;
justify-content: center;
}
.flex-vc {
-webkit-box-align: center;
-webkit-align-items: center;
-moz-align-items: center;
-ms-align-items: center;
-o-align-items: center;
align-items: center;
}
.pull-right {
float: right;
}
.pull-left {
float: left;
}
.clearfix {
clear: both;
}
.clearfix:after {
content: ".";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
.top-box {
padding: 20rpx 30rpx;
height: calc(100% - 182px);
display: flex;
align-items: center;
justify-content: center;
}
.original-img {
width: 320px;
margin: 0 auto;
}
.bg-img {
position: absolute;
top: 0;
right: 0;
z-index: -1;
}
.bottom {
position: fixed;
bottom: 50px;
left: 0;
width: 100%;
background-color: rgba(230,225,225,0.8);
}
.bottom>view.tab {
padding-right: 30rpx;
padding-left: 30rpx;
}
.bottom>view.sticker-lists-body{
padding-left: 30rpx;
}
.recommend_scroll_x_box {
height: 100rpx;
padding-top: 30rpx;
padding-bottom: 40rpx;
width: 100%;
overflow: auto;
white-space: nowrap;
display: flex;
vertical-align: top;
}
::-webkit-scrollbar {
width: 0;
height: 0;
color: transparent;
}
.sticker-lists-body {
padding-left: 30rpx;
}
.sticker-lists-body .sticker-list {
width: 100rpx;
height: 100rpx;
margin-right: 24rpx;
display: inline-block;
vertical-align: top;
}
.sticker-lists-body .sticker-list image {
width: 100rpx;
height: 100rpx;
background-color: #ffffff;
}
.bottom image {
width: 50rpx;
height: 50rpx;
}
.bottom .tab image {
margin-right: 60rpx;
}
.bottom .tab {
padding: 25rpx 60rpx;
background-color: #f4f4f4;
height: 70rpx;
}
.bottom .tab .tab-list {
position: relative;
float: left;
display: flex;
align-items: center;
margin-top: 10rpx;
}
.bottom .tab button{
background-color: #d81e06;
float: right;
height: 70rpx;
line-height: 70rpx;
font-size: 30rpx;
}
.bottom .tab button.color-red {
background-color: #fff;
border: 1rpx solid #d81e06;
margin-right: 10rpx;
}
.bottom .tab .tab-list image.active::before {
content: '';
position: absolute;
top: -50rpx;
left: 5rpx;
border-right: 20rpx solid transparent;
border-left: 20rpx solid transparent;
border-bottom: 20rpx solid #f4f4f4;
}
movable-view {
height: 50px;
width: 50px;
}
movable-view .sticker-box {
position: relative;
width:100%;
height: 100%;
border: 1rpx dashed #ccc;
}
image.cancel {
position: absolute;
top: -15rpx;
left: -15rpx;
width:30rpx;
height: 30rpx;
z-index: 30;
}
/*canvas属于客户端创建的原生组件,级别很高,用z-index控制无效,设置display:none之后对绘图有影响,取巧让canvas定位在可视页面之外 */
.canvas-box {
opacity: 0;
position: fixed;
top: 150%;
left: 0;
z-index: -1;
}

三.js部分

  1. 设置data数据
data: {

imgUrl : '../../images/example.png',//实际项目中用的是上一个裁剪页面传来的图片

stickers: ['../../images/sticker/1.png',

'../../images/sticker/2.png',

'../../images/sticker/3.png',

'../../images/sticker/4.png',

'../../images/sticker/5.png',

'../../images/sticker/6.png',

'../../images/sticker/7.png',

'../../images/sticker/8.png',

'../../images/sticker/9.png',

'../../images/sticker/10.png',

'../../images/sticker/11.png'],

x: 160,

y: 50,

chosedImg: false,

stv: {

offsetX: 160,

offsetY: 50,

zoom: false, //是否缩放状态

distance: 0, //两指距离

scale: 1, //缩放倍数

width: 50,

height: 50,

},

 

  1. 贴图移动与双指缩放,通过offsetX和offsetY 来记录贴纸的位置,通过width和height来记录贴纸的高宽
// 贴图触摸开始

touchstartCallback: function (e) {

//console.log('touchstartCallback');

//console.log(e);

if (e.touches.length === 1) {

let { clientX, clientY } = e.touches[0];

this.startX = clientX;

this.startY = clientY;

this.touchStartEvent = e.touches;

} else {

let xMove = e.touches[1].clientX - e.touches[0].clientX;

let yMove = e.touches[1].clientY - e.touches[0].clientY;

let distance = Math.sqrt(xMove * xMove + yMove * yMove);

this.setData({

'stv.distance': distance,

'stv.zoom': true, //缩放状态

})

}

},



// 贴图触摸移动中

touchmoveCallback: function (e) {

//console.log('touchmoveCallback');

//console.log(e);

if (e.touches.length === 1) {

//单指移动

if (this.data.stv.zoom) {

//缩放状态,不处理单指

return;

}

let { clientX, clientY } = e.touches[0];

let offsetX = clientX - this.startX;

let offsetY = clientY - this.startY;

this.startX = clientX;

this.startY = clientY;

let { stv } = this.data;

stv.offsetX += offsetX;

stv.offsetY += offsetY;

stv.offsetLeftX = -stv.offsetX;

stv.offsetLeftY = -stv.offsetLeftY;

var nowWidth = this.data.stv.width;

var maxoffsetX = 320 - nowWidth;

var nowHeight = this.data.stv.height;

var maxoffsetY = 178.125 - nowHeight;



if (stv.offsetX > maxoffsetX) {

stv.offsetX = maxoffsetX;

} else if (stv.offsetX < 0) {

stv.offsetX = 0;

}

if (stv.offsetY > maxoffsetY) {

stv.offsetY = maxoffsetY;

} else if (stv.offsetY < 0) {

stv.offsetY = 0;

}

this.setData({

stv: stv

});



} else {

//双指缩放

let xMove = e.touches[1].clientX - e.touches[0].clientX;

let yMove = e.touches[1].clientY - e.touches[0].clientY;

let distance = Math.sqrt(xMove * xMove + yMove * yMove);



let distanceDiff = distance - this.data.stv.distance;

let newScale = this.data.stv.scale + 0.005 * distanceDiff;

if (newScale < 0.5) {

newScale = 0.5;

}

if (newScale > 4) {

newScale = 4;

}

let newWidth = newScale * 50;

let newHeight = newScale * 50;



this.setData({

'stv.distance': distance,

'stv.scale': newScale,

'stv.width': newWidth,

'stv.height': newWidth,

})

//console.log(this.data.stv.scale)

}

},



// 贴图触摸结束

touchendCallback: function (e) {

// console.log('touchendCallback');

//console.log(e);

if (e.touches.length === 0) {

this.setData({

'stv.zoom': false, //重置缩放状态

})

}

},

 

  1. 点击贴纸左上角的叉叉取消贴纸
//取消圣诞帽

cancel: function () {

this.setData({

chosedImg: false,

x: 150,

y: 75,

stv: {

offsetX: 75,

offsetY: 75,

zoom: false, //是否缩放状态

distance: 0, //两指距离

scale: 1, //缩放倍数

width: 50,

height: 50,

}

})

},
  1. 切换贴纸
changeImg: function (e) {

var $img = e.currentTarget.dataset.url;

var chosedImg = this.data.chosedImg;

var chosedImg1 = this.data.chosedImg1;

var chosedImg2 = this.data.chosedImg2;

this.setData({

chosedImg: false,

x: 160,

y: 50,

stv: {

offsetX: 160,

offsetY: 50,

zoom: false, //是否缩放状态

distance: 0, //两指距离

scale: 1, //缩放倍数

width: 50,

height: 50,

}

}),

this.setData({

chosedImg: $img,



})



},
  1. 接下来就是我们的canvas绘图部分
//将贴纸绘制到canvas的固定

setHat: function (context) {

var hat = this.data.chosedImg;

var newtop = this.data.stv.offsetX * 2;

var newleft = this.data.stv.offsetY * 2;

var newswidth = this.data.stv.width * 2;

var newheight = this.data.stv.height * 2;



context.drawImage(hat, newtop, newleft, newswidth, newheight)

context.save();

context.restore();

context.stroke();

},





//将canvas转换为图片保存到本地,然后将图片路径传给image图片的src

createNewImg: function (imgUrl) {

var that = this;



var chosedImg = this.data.chosedImg;

var formValue = that.data.formValue;

var path = imgUrl;

var context = wx.createCanvasContext('mycanvas');

//为了解决绘制出来的图片有锯齿,这里绘制图片时放大了一倍进行绘制

context.drawImage(path, 0, 0, 640, 356.266);

//若选择了贴纸就绘制贴纸

if(chosedImg){

this.setHat(context);

}

//绘制图片

context.draw();

//将生成好的图片保存到本地,需要延迟一会,绘制期间耗时

setTimeout(function () {

wx.canvasToTempFilePath({

canvasId: 'mycanvas',

success: function (res) {

var tempFilePath = res.tempFilePath;

console.log(tempFilePath);

formValue[0].imagePath = tempFilePath;

formValue[0].videoUrl = "";



//imagePath即生成的图片路径,正常项目中点击下一步会做图片上传,这里不做讲解,只给出了地址,可以在页面中调用地址查看图片

that.setData({

imagePath: tempFilePath,

})



},

fail: function (res) {

console.log(res);

}

});

}, 200);



},



//点击下一步保存按钮

save: function () {

console.log("1111")

var that = this;

wx.showLoading({

title: '创建中...',

})

setTimeout(function () {

var imgUrl = that.data.imgUrl

//wx.hideToast()

that.createNewImg(imgUrl);

that.setData({

maskHidden: true

});

console.log("canvas")

}, 1000)

},

 

github地址:https://github.com/sky-Aimee/sticker.git

发布了45 篇原创文章 · 获赞 15 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/qq_39364032/article/details/79743570