微信小程序利用Canvas绘制图片和竖排文字详解


Posted in Javascript onJune 25, 2019

前言

闲暇时间抽个空写了个三国杀武将手册的小程序,中间有个需求设计的是合成武将皮肤图、竖排的武将姓名、以及小程序码,然后提供保存图片到相册,最终让用户可以分享到朋友圈或其他平台。合成图片应该按照 Canvas 的文档来做都没什么问题,主要是有个竖排文字的需求,这里和大家分享一下。

正文

首先放一张最终保存到相册的图片吧~

微信小程序利用Canvas绘制图片和竖排文字详解

自我感觉良好,至少达到了我自己的预期吧~~~

下面让我们一步一步来看看如何实现的吧。

整个图片分为三个部分:

  • 武将图片
  • 小程序码
  • 武将文字信息

先来看一下 wxml 里面的代码,主要是放了一个 canvas 标签,控制了一下高度和宽度属性。

<view>
<canvas class='share-canvas' style="width:100%;height:{{canvasHeight}}px" canvas-id="share_canvas"></canvas>
</view>

武将图片

drawHeroImage: function (path) {
var that = this;
// 拿到canvas context
let ctx = wx.createCanvasContext('share_canvas');
// 为了保证图片比例以及绘制的位置,先要拿到图片的大小
wx.getImageInfo({
src: path,
success: function (res) {
// 计算图片占比信息 
let maxWidth = Math.min(res.width, that.data.canvasWidth * 0.65);
let radio = maxWidth / res.width;
let offsetY = (that.data.canvasHeight - res.height * radio) / 2;
console.log('offsetY=' + offsetY);
that.setData({
imageWidth: res.width * radio,
imageHeight: res.height * radio,
offsetY: offsetY,
});
// 绘制canvas背景,不属于绘制图片部分
ctx.setFillStyle('white')
ctx.fillRect(0, 0, that.data.canvasWidth, that.data.canvasHeight);
// 绘制武将图片,path是本地路径,不可以传网络url,如果是网络图片需要先下载
ctx.drawImage(path, 10, offsetY, res.width * radio, res.height * radio)
// 绘制小程序码
that.drawQrCodeImage(ctx);
// 绘制势力汉字:吴
that.drawInfluence(ctx, that.data.hero.HERO.INFLUENCE);
// 绘制武将姓名:陆逊
that.drawName(ctx, that.data.hero.HERO.NAME);
// 绘制武将称号:江陵侯
that.drawHorner(ctx, that.data.hero.HERO.HORNER);
// 最终调用draw函数,生成预览图
// 一个坑点:只能调用一次,否则后面的会覆盖前面的
ctx.draw();
}
});
}

小程序码

小程序码和武将图片是一个类型,无非就是需要计算绘制的位置,这里就不再展示相关代码了。

武将文字信息

从刚刚的代码可以看出,我分了3个部分来绘制,其实 吴 和 陆逊 应该是可以放到一起的,但是我在绘制的时候发现,空格在绘制的时候会引起异常,导致空格后面的文字无法绘制出来,所以我这里 吴 和 陆逊 中间的空白是靠位置偏移来做的。

这里就展示一下如何绘制武将称号的。

// 绘制武将称号:江陵侯
drawHorner: function (ctx, text) {
// 设置字号
ctx.setFontSize(26);
// 设置字体颜色
ctx.setFillStyle("#000000");
// 计算绘制起点
let x = this.data.offsetX + 35;
let y = this.data.offsetY + 10;
console.log('drawHorner' + text);
console.log(x);
console.log(y);
// 绘制竖排文字,这里是个Util函数,具体实现请继续看
Canvas.drawTextVertical(ctx, text, x, y);
}

绘制竖排文字从网上找了个开源的代码,需要看原理的请看这里

当然我这里为了适用小程序做了些改动,函数原型是这样子的:

CanvasRenderingContext2D.prototype.letterSpacingText = function (text, x, y, letterSpacing)

原谅我不是很会 js ,完全不懂这是个什么语法,看了一会没弄懂,感觉像是给类添加新的属性,不管他。

不管白猫黑猫,能抓到耗子就是好猫

改造后的函数像下面的样子:

canvas.js 
/**
* @author zhangxinxu(.com)
* @licence MIT
* @description http://www.zhangxinxu.com/wordpress/?p=7362
*/
function drawTextVertical(context, text, x, y) {
var arrText = text.split('');
var arrWidth = arrText.map(function (letter) {
return 26;
// 这里为了找到那个空格的 bug 做了许多努力,不过似乎是白费力了
// const metrics = context.measureText(letter);
// console.log(metrics);
// const width = metrics.width;
// return width;
});
var align = context.textAlign;
var baseline = context.textBaseline;
if (align == 'left') {
x = x + Math.max.apply(null, arrWidth) / 2;
} else if (align == 'right') {
x = x - Math.max.apply(null, arrWidth) / 2;
}
if (baseline == 'bottom' || baseline == 'alphabetic' || baseline == 'ideographic') {
y = y - arrWidth[0] / 2;
} else if (baseline == 'top' || baseline == 'hanging') {
y = y + arrWidth[0] / 2;
}
context.textAlign = 'center';
context.textBaseline = 'middle';
// 开始逐字绘制
arrText.forEach(function (letter, index) {
// 确定下一个字符的纵坐标位置
var letterWidth = arrWidth[index];
// 是否需要旋转判断
var code = letter.charCodeAt(0);
if (code <= 256) {
context.translate(x, y);
// 英文字符,旋转90°
context.rotate(90 * Math.PI / 180);
context.translate(-x, -y);
} else if (index > 0 && text.charCodeAt(index - 1) < 256) {
// y修正
y = y + arrWidth[index - 1] / 2;
}
context.fillText(letter, x, y);
// 旋转坐标系还原成初始态
context.setTransform(1, 0, 0, 1, 0, 0);
// 确定下一个字符的纵坐标位置
var letterWidth = arrWidth[index];
y = y + letterWidth;
});
// 水平垂直对齐方式还原
context.textAlign = align;
context.textBaseline = baseline;
}
module.exports = {
drawTextVertical: drawTextVertical
}

绘制网络图片

由于网络图片无法直接绘制,所以需要先下载到本地,然后再按住本地图片绘制的流程走一遍。

downloadHeroImage: function () {
// 微信不支持非https的图片下载,这里了个替换
let url = this.data.hero.HERO.ICON.replace(/http/, "https");
var that = this;
wx.downloadFile({
url: url,
success: function (res) {
// 下载成功后拿到图片的路径,然后开始绘制
var path = res.tempFilePath;
that.drawHeroImage(path);
}, fail: function (res) {
console.log(res)
}
});
}

保存图片

说了这么多,自然少不了最终的一步,将绘制到 canvas 的图片保存到手机相册,这里需要用户授权,你需要自己处理。
用的是微信给我们提供的接口 wx.canvasToTempFilePath 。需要我们传入起点坐标 (x, y)和画布大小 (width, height) 以及 canvasId 。

saveShareImage: function () {
wx.showLoading({
title: '正在保存图片..',
});
let that = this;
wx.canvasToTempFilePath({
x: 0,
y: 0,
width: that.data.canvasWidth,
height: that.data.canvasHeight,
canvasId: 'share_canvas',
success: function (res) {
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success(res) {
console.log(res);
wx.showToast({
title: '保存到相册成功',
duration: 1500,
})
},
fail(res) {
console.log(res)
wx.showToast({
title: '保存到相册失败',
icon: 'fail'
})
},
complete(res) {
console.log(res)
}
})
}
})
}

开源

本着开源的精神,源码已经放在 Github 上,大家可以去上面查看具体代码。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
用JS实现一个页面多个css样式实现
May 29 Javascript
js 获取页面高度和宽度兼容 ie firefox chrome等
May 14 Javascript
在JS方法中返回多个值的方法汇总
May 20 Javascript
jquery获取复选框checkbox的值的简单实现方法
May 26 Javascript
jQuery如何防止Ajax重复提交
Oct 14 Javascript
浅谈jquery fullpage 插件增加头部和版权的方法
Mar 20 jQuery
JavaScript变量提升和严格模式实例分析
Jan 27 Javascript
图文讲解vue的v-if使用方法
Feb 11 Javascript
Vue CLI 2.x搭建vue(目录最全分析)
Feb 27 Javascript
JQueryDOM之样式操作
Mar 27 jQuery
angular 实现同步验证器跨字段验证的方法
Apr 11 Javascript
OpenLayers3加载常用控件使用方法详解
Sep 25 Javascript
js+html实现周岁年龄计算器
Jun 25 #Javascript
Vue 列表上下过渡效果的实例代码
Jun 25 #Javascript
JS 封装父页面子页面交互接口的实例代码
Jun 25 #Javascript
微信小程序Echarts图表组件使用方法详解
Jun 25 #Javascript
ES6 Object方法扩展的应用实例分析
Jun 25 #Javascript
ES6 Object属性新的写法实例小结
Jun 25 #Javascript
ES6模板字符串和标签模板的应用实例分析
Jun 25 #Javascript
You might like
计数器详细设计
2006/10/09 PHP
PHP中通过语义URL防止网站被攻击的方法分享
2011/09/08 PHP
PHP 线程安全与非线程安全版本的区别深入解析
2013/08/06 PHP
php根据年月获取季度的方法
2014/03/31 PHP
PHP图像处理 imagestring添加图片水印与文字水印操作示例
2020/02/06 PHP
javascript中的delete使用详解
2013/04/11 Javascript
jQuery实现的图文高亮滚动切换特效实例
2015/08/10 Javascript
javascript学习笔记整理(概述、变量、数据类型简介)
2015/10/25 Javascript
解决jquery中动态新增的元素节点无法触发事件问题的两种方法
2015/10/30 Javascript
jQuery点击其他地方时菜单消失的实现方法
2016/04/22 Javascript
深入浅析JavaScript中数据共享和数据传递
2016/04/25 Javascript
老生常谈javascript的类型转换
2016/10/12 Javascript
js 获取图像缩放后的实际宽高,位置等信息
2017/03/07 Javascript
seajs模块压缩问题与解决方法实例分析
2017/10/10 Javascript
原生javascript AJAX 三级联动的实现代码
2018/05/04 Javascript
react 父子组件之间通讯props
2018/09/08 Javascript
如何基于原生javaScript生成带图片的二维码
2019/11/21 Javascript
[56:00]2018DOTA2亚洲邀请赛 4.6 淘汰赛 VP vs TNC 第二场
2018/04/10 DOTA
python中使用smtplib和email模块发送邮件实例
2014/04/22 Python
python读取目录下最新的文件夹方法
2018/12/24 Python
python三方库之requests的快速上手
2019/03/04 Python
django中账号密码验证登陆功能的实现方法
2019/07/15 Python
django配置app中的静态文件步骤
2020/03/27 Python
升级keras解决load_weights()中的未定义skip_mismatch关键字问题
2020/06/12 Python
python制作抽奖程序代码详解
2021/01/15 Python
基于ccs3的timeline时间线实现方法
2020/04/30 HTML / CSS
编写html5时调试发现脚本php等网页js、css等失效
2013/12/31 HTML / CSS
深入探究HTML5的History API
2015/07/09 HTML / CSS
HTML5为输入框添加语音输入功能的实现方法
2017/02/06 HTML / CSS
中国最大隐形眼镜网上商城:视客眼镜网
2016/10/30 全球购物
印度尼西亚电子产品购物网站:Kliknklik
2018/06/05 全球购物
可打印的优惠券、杂货和优惠券代码:Coupons.com
2018/06/12 全球购物
宝拉珍选官方旗舰店:2%水杨酸精华液,收缩毛孔粗大和祛痘
2018/07/01 全球购物
学校火灾防控方案
2014/06/09 职场文书
2015财务年度工作总结范文
2015/05/04 职场文书
python读取pdf格式文档的实现代码
2021/04/01 Python