动用canvas来画图片是因为有一些场景里存在着需要动态改变的东西,比如说随机取出一句话写到卡片上,或者根据当前日期动态改变登机牌等等。
设置画布背景
如果直接按照画布的大小来绘制图片的话会导致图片模糊,这是由于retina屏幕的原因,此时可以根据当前屏幕的设备像素比devicePixelRatio
来改变画布的大小。
以iPhone6来说,屏幕像素比是2,那么就可以画一个两倍大小的画布。
当然,一般设计师给我们的设计稿都是两倍实际大小,所以一种取巧的办法是把画布的长宽设置为图片的长宽,样式上在使元素自适应屏幕就好了。
图片是一张张画上去的,后面的图片会覆盖掉前面的图片,因此一定得安排好每张图的加载顺序
imgBg.onload = () => {
canvas.setAttribute('width', img.width)
canvas.setAttribute('height', img.height)
ctx.drawImage(imgBg, 0, 0, imgBg.width, imgBg.height)
imgAvatar.onload = () => {
ctx.drawImage(imgAvatar, 370, 380, imgAvatar.width, imgAvatar.height)
self.drawText(ctx)
self.createImg(cavas)
}
imgAvatar.src = `/url/avatar.png?t=05093`
}
imgBg.src = `/url/bg.jpg`
这是简单的只有两张图的情况,如果更多的话可以考虑把所有画图任务都保存在一个队列中,每次一张图load并执行后从队列中取出下一次需要画图的任务。
绘制文字
画完背景图了,怎么也得写几行文字。在处理多行文字中,可以将文字存于数组中,每写完一行移动画布坐标,写下一行。
如果有特殊的需求,比如针对名字做特殊的样式处理,可以保存画布状态后面再restore
来记住之前使用的样式。
同一行文字改变了样式所占用的空间宽度也不同,这里有个比较好用的方法measureText
ctx.measureText(strA).width
测量了特殊处理的文字宽度可以方便知道下一次的绘制的应该移动多大的距离
// 类似的一句话,把xxx替换为用户的昵称
function drawText(ctx) {
let msgArr = ['对于xxx来说,balabala', '星座balabalabala']
for(let i = 0, len = msgArr.length; i < len; i++) {
ctx.translate(0, 40)
if (msgArr[i].indexOf('xxx') !== -1) {
let strA = msgArr[i].split('xxx')[0]
let strB = msgArr[i].split('xxx')[1]
ctx.save()
if (strA) {
let textLen = ctx.measureText(strA).width
ctx.fillText(strA, 0, 20)
ctx.translate(textLen, 0)
}
// 名字做特殊处理
ctx.save()
ctx.font = "42px Aril"
ctx.fillStyle = '#c85d8a'
let nick = this.nickName ? this.nickName : 'TA'
ctx.fillText(nick, 0, 20)
let nameLen = ctx.measureText(nick).width
ctx.restore()
ctx.translate(nameLen, 20)
ctx.fillText(strB, 0, 0)
ctx.restore()
} else {
ctx.fillText(msgTopArr[i], 0, 20)
}
}
}
把画布转换为图片
使用canvas的toDataURL
方法转为base64格式,放到页面中一个img
的src
属性中就完成了。
这里可以根据自己页面设计,我是将图片设置为透明覆盖到了画布上,可以方便长按保存。
createImg(canvas) {
let imgData = canvas.toDataURL('image/jpg')
this.$refs['pic-shadow'].src = imgData
}
canvas图片跨域问题
canvas限制图片资源不能跨域,这里用的两种解决方式
- 如果图片资源服务器支持跨域访问,在创建img时候加一句
img.crossOrigin = "Anonymous"
就可以避免跨域问题。 - 如果不能去改服务器头部的话,而且其实用到的图片不是特别多,可以把图片转成
base64
格式保存在一个js
的文件中,直接拿来用就可以了。
模拟长按
在微信里面对img图片长按保存是没有问题的,但在Android版的自家APP中就不灵了。所以这里根据环境判断是否显示mask层,通过模拟长按来调出自定义的下载菜单,再使用bridge方法完成下载。
用到了touch的3个事件,对目标元素的touchstart、 touchmove
和touchend
分别绑定对应的函数,500毫秒后再调用下载方法,实现对长按模拟。
let timer = null
function touchStart() {
timer = setTimeout(savePhoto, 500)
}
function touchMove() {
clearTimeout(timer)
}
function touchEnd() {
clearTimeout(timer)
}
function savePhoto() {
// some bridge methods
}