小程序开发教程栏目介绍使用canvas写一个图片
推荐(免费):小程序开发教程
应用展示
截图
需求
既然是小应用,那就希望最终成品是有 适用的场景 且是 有价值 的
需求来源
这个应用需求的灵感
在以前工作生活中,经常会无意中获得同事的 美照
这时我们想要把这张照片做成表情包
一般给图片添加几个说明文字
一个有意思的沟通工具(表情包)就完成了
需求分析基于以上需求的拆解
可以将要应用功能实现整理一下
用户需要上传一张图片可以添加文字文字可以作 样式调整 和 旋转缩放另外我们希望还可以插入一些贴图贴图可以做 旋转缩放用户导出图片到相册实现
github仓库 https://github.com/luosijie/f...
如果喜欢我的项目,欢迎给个星星鼓励一下这个应用是用小程序开发的
使用框架:mpx使用技术:小程序canvas状态管理
import { createstore } from '@mpxjs/core'const store = createstore({ state: { cavas: null, // cnavas实例 ctx: null, // canvas上下文实例 elements: [], // canvas元素 activeindex: null, // 当前编辑中的元素索引 mode: 'background', // 当前编辑模式:background, text, sticker fontstyle: { // 文字默认样式 opacity: 1, fillstyle: '#000000', strokestyle: '#000000' } }, mutations: { setcanvas (state, data) { state.canvas = data }, setctx (state, data) { state.ctx = data }, setelements (state, data) { state.elements = data }, setmode (state, data) { state.mode = data }, setactiveindex (state, data) { state.activeindex = data }, setfontstyle (state, { key, data }) { state.fontstyle[key] = data }, // 添加文字 addtext (state) { const size = 50 const string = '请输入文字' const text = { type: 'text', data: string, scale: 1, size, left: 100, top: 100, rotate: 0, opacity: state.fontstyle.opacity, fillstyle: state.fontstyle.fillstyle, strokestyle: state.fontstyle.strokestyle } state.elements.push(text) state.activeindex = state.elements.length - 1 }, // 添加贴图 addsticker (state, data) { state.elements.push(data) state.activeindex = state.elements.length - 1 }, // 删除当前选中 deleteactiveelement (state) { state.elements.splice(state.activeindex, 1) state.activeindex = null }, // 清空画布 clear (state) { state.elements = [] state.activeindex = null } }})export default store
画布初始化
// 初始化画布async initcanvas() { const query = this.createselectorquery() query .select('#canvas') .fields({ node: true, size: true }) .exec(async res => { const canvas = res[0].node const ctx = canvas.getcontext('2d') store.commit('setcanvas', canvas) store.commit('setctx', ctx) await this.loadimage('/images/icon-rotate.png').then(res => { this.image.rotate = res }) canvas.width = res[0].width * this.dpr canvas.height = res[0].height * this.dpr ctx.scale(this.dpr, this.dpr) this.drawgrid() })}
绘制图片
/** * 绘制图片 * @param { object } ele canvas元素 */drawimage(ele) { this.ctx.save() const width = ele.width const height = ele.height const centerx = ele.left + ele.width / 2 const centery = ele.top + ele.height / 2 this.ctx.translate(centerx, centery) this.ctx.rotate(ele.rotate) this.ctx.drawimage(ele.data, ele.left - centerx, ele.top - centery, width, height) this.ctx.restore()}
绘制文字
/** * 绘制文字 * @param { object } ele canvas元素 */drawtext(ele) { this.ctx.save() const width = ele.size * ele.data.length const height = ele.size const centerx = ele.left + width / 2 const centery = ele.top + height / 2 this.ctx.translate(centerx, centery) this.ctx.rotate(ele.rotate) this.ctx.font = `${ele.size}px bold sans-serif` this.ctx.globalalpha = ele.opacity this.ctx.fillstyle = ele.fillstyle this.ctx.strokestyle = ele.strokestyle // this.ctx.linewidth = 2 this.ctx.textbaseline = 'top' console.log('draw-text', ele) this.ctx.filltext(ele.data, ele.left - centerx, ele.top - centery) this.ctx.stroketext(ele.data, ele.left - centerx, ele.top - centery) this.ctx.restore()}
绘制控制元件
initcontroller(ele) { const cs = this.convert2controllersize(ele) this.ctx.save() this.ctx.strokestyle = '#eee' this.ctx.translate(cs.centerx, cs.centery) this.ctx.rotate(cs.rotate) // 绘制虚线边框 this.ctx.setlinedash([10, 5], 5) this.ctx.strokerect(cs.left - cs.centerx, cs.top - cs.centery, cs.width, cs.height) // 绘制控制点-旋转 this.ctx.drawimage(this.image.rotate, cs.left + cs.width - 10 - cs.centerx, cs.top + cs.height - 10 - cs.centery, 20, 20) this.ctx.restore()}
画布渲染函数
// 画布渲染函数rendercanvas() { this.ctx.clearrect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height) this.drawgrid() console.log('draw-background', this.background) if (this.background) this.drawimage(this.background) for (let i = 0; i < this.elements.length; i++) { const ele = this.elements[i] // 渲染背景 if (ele.type === 'background') { this.drawimage(ele) } if (ele.type === 'sticker') { this.drawimage(ele) } // 渲染文字 if (ele.type === 'text') { this.drawtext(ele) } // 选中元素添加控制元件 if (this.activeindex === i) { this.initcontroller(ele) } }}
事件监听
移动
// 移动事件绑定函数handlemove(e) { console.log('mouse-move', e) if (e.touches.length > 1) return const x = e.touches[0].x const y = e.touches[0].y const dx = this.starttouches[0].x - x const dy = this.starttouches[0].y - y const elements = this.elements.slice() elements[this.activeindex || 0].left = this.startselected.left - dx elements[this.activeindex || 0].top = this.startselected.top - dy store.commit('setelements', elements)}
旋转// 旋转绑定函数handlerotate(e) { console.log('handlerotate') const start = this.starttouches[0] const end = e.touches[0] const center = { x: this.startselected.centerx, y: this.startselected.centery } const startlength = math.sqrt((center.x - start.x) ** 2 + (center.y - start.y) ** 2) const endlength = math.sqrt((center.x - end.x) ** 2 + (center.y - end.y) ** 2) const radian = this.convert2radian(start, end, center) const scale = endlength / startlength const elements = this.elements.slice() const selected = elements[this.activeindex] // 旋转 selected.rotate = this.startselected.rotate - radian // 缩放 if (selected.type === 'text') { selected.left = this.startselected.centerx - this.startselected.size * this.startselected.data.length * scale / 2 selected.top = this.startselected.centery - this.startselected.size * scale / 2 selected.size = this.startselected.size * scale } if (selected.type === 'sticker') { selected.left = this.startselected.centerx - this.startselected.width * scale / 2 selected.top = this.startselected.centery - this.startselected.height * scale / 2 selected.width = this.startselected.width * scale selected.height = this.startselected.height * scale } store.commit('setelements', elements)}
缩放// 缩放事件绑定函数handlescale(e) { if (e.touches.length !== 2 || this.mode !== 'background') return const startlength = math.sqrt( (this.starttouches[0].x - this.starttouches[1].x) ** 2 + (this.starttouches[0].y - this.starttouches[1].y) ** 2 ) const endlength = math.sqrt( (e.touches[0].x - e.touches[1].x) ** 2 + (e.touches[0].y - e.touches[1].y) ** 2 ) const scale = endlength / startlength const elements = this.elements.slice() const selected = elements[this.activeindex || 0] selected.left = this.startselected.centerx - this.startselected.width * scale / 2 selected.top = this.startselected.centery - this.startselected.height * scale / 2 selected.width = this.startselected.width * scale selected.height = this.startselected.height * scale // elements[this.activeindex || 0].scale = this.startselected.scale * scale store.commit('setelements', elements)}
导出图片export() { if (!store.state.elements.length) { wx.showtoast({ title: '加点东西再导出吧', icon: 'none' }) return } wx.showmodal({ title: '提示', content: '图片将保存到手机相册', success(res) { if (res.confirm) { console.log('export-canvas', store.state.ctx) const canvas = store.state.canvas wx.canvastotempfilepath({ x: 0, y: 0, width: canvas.width, height: canvas.height, canvas, complete(res) { if (res.errmsg === 'canvastotempfilepath:ok') { wx.saveimagetophotosalbum({ filepath: res.tempfilepath, success(res) { wx.showtoast({ title: '图片保存成功', icon: 'none' }) } }) } } }) } } })}
以上就是使用小程序canvas写一个简单的图片应用的详细内容。