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

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

一、结构部分

  1. 首先是将原始图片放在 movable-area 组件内部,原始图片保持与movable-area 相同高宽,(说明:在这一步之前已经做过图片裁剪了,这一步这里的原始的图片的高宽都是一样的,即movable-area的高宽);类名sticker-box内部的就是贴纸图片以及取消贴纸的叉叉。
html 复制代码
<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>
  1. 底部贴纸列表
html 复制代码
<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>
  1. 用于绘图的canvas
html 复制代码
<canvas style="width: 640px;height: 356.266px;" canvas-id="mycanvas"/>

二、样式部分

css 复制代码
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定位在可视页面之外

css 复制代码
.canvas-box {
	opacity: 0;
	position: fixed;
	top: 150%;
	left: 0;
	z-index: -1;
}

三、js部分

  1. 设置data数据
js 复制代码
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来记录贴纸的高宽
js 复制代码
// 贴图触摸开始
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. 点击贴纸左上角的叉叉取消贴纸
js 复制代码
//取消圣诞帽

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. 切换贴纸
js 复制代码
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绘图部分
js 复制代码
//将贴纸绘制到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

相关推荐
郭wes代码6 小时前
Cmd命令大全(万字详细版)
python·算法·小程序
.生产的驴11 小时前
SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken
java·spring boot·redis·后端·缓存·微信小程序·maven
汤姆yu16 小时前
基于微信小程序的乡村旅游系统
微信小程序·旅游·乡村旅游
计算机徐师兄17 小时前
基于TP5框架的家具购物小程序的设计与实现【附源码、文档】
小程序·php·家具购物小程序·家具购物微信小程序·家具购物
曲辒净17 小时前
微信小程序实现二维码海报保存分享功能
微信小程序·小程序
朽木成才19 小时前
小程序快速实现大模型聊天机器人
小程序·机器人
peachSoda719 小时前
随手记:小程序使用uni.createVideoContext视频无法触发播放
小程序
何极光19 小时前
uniapp小程序样式穿透
前端·小程序·uni-app
小墨&晓末19 小时前
【PythonGui实战】自动摇号小程序
python·算法·小程序·系统安全
oil欧哟1 天前
🤔认真投入一个月做的小程序,能做成什么样子?有人用吗?
前端·vue.js·微信小程序