# 小程序实现海报分享功能
小程序实现图片分享功能,主要是通过canvas画布把元素一个一个画上去,最后通过画布转图片方法把图片保持到相册中。这其中有几个比较关键的点需要去实现。
# canvas尺寸问题
canvas宽高可以在元素上直接设置,或者通过js设置,但是canvas的宽高和css设置的宽高是不一样的,css设置的大小是设置的画布窗口显示的大小,但是底层画布的大小是没有改变的,默认是300*150的,所以有时只设置的css宽高绘制是变形的,只有设置画布的大小和css设置的窗口大小一样才不会变形。
在小程序中需要根据dpr来设置,保证屏幕大小适配。
<canvas class="canvas" type="2d" id="myCanvas"></canvas>
Page({
data: {
canvasImg: '',
canvasDom: null,
context: null,
},
onReady() {
this.initCanvas()
},
//初始化画布
initCanvas() {
const query = this.createSelectorQuery()
query
.select('#myCanvas')
.fields({ node: true, size: true })
.exec((res) => {
const canvasDom = res[0].node
const context = canvasDom.getContext('2d')
// 设置真实宽高
const dpr = wx.getSystemInfoSync().pixelRatio
const canvasW = res[0].width * dpr
const canvasH = res[0].height * dpr
canvasDom.width = canvasW
canvasDom.height = canvasH
this.setData({
canvasDom,
context,
canvasW,
canvasH,
})
})
}
})
// CSS
.index-canvas {
width: 750rpx;
height: 1000rpx;
canvas {
width: 100%;
height: 100%;
}
}
# 绘制图片
把图像绘制到画布上,可以使用drawImage()方法,如果是本地图片可以通过createImage()方法创建一个图片,然后直接绘制就行了,如果是通过手机拍照或者获取手机相册可以通过临时路径tempImagePath进行绘制。
// 创建一个图片对象
const url = '../../assets/images/bg.png'
drawImage(url, x, y, w, h) {
const { context, canvasDom } = this.data
const image = canvasDom.createImage();
image.src = url;
image.onload = () => {
// 把获取图片大小不动的绘制到 (0, 0) 的位置上
// context.drawImage(image, 0, 0);
// 把图片绘制到画布位置(0, 0)大小为w, h的区域上
context.drawImage(image, x, y, w, h)
}
},
# 绘制网络图片
如果是网络图片(允许访问的)是没办法直接绘制,如果后端给我们一个动态图片,我们该如何绘制到canvas上呢,上面我们提到可以使用临时地址,那么如果是网络地址,我们可以使用downloadFile()方法把图片地址下载下来,获取到临时路径在绘制到canvas上。
// 下载图片
downloadFile(url) {
return new Promise((resolve, reject) => {
wx.downloadFile({
url,
success: (res) => {
console.log('下载成功', res.tempFilePath);
resolve(res.tempFilePath)
},
fail: (err) => {
console.log('下载失败', err);
reject(err)
}
})
})
},
// 保存海报
async saveCanvasImage() {
const imgUrl = await this.downloadFile(xxx)
this.drawImage(imgUrl, 0, 0, 100, 100)
}
# 文本进行换行
单行文本可以直接通过fillText()方法直接绘制,如果文本超多canvas宽度是不会自动换行的,我们需要借助measureText()方法计算文本的宽度,如果超过canvas宽度则截取文本进行换行绘制。
// 绘制文本
drawText(text, x, y, maxWidth, lineHeight) {
const { context } = this.data;
// 字符分隔为数组
const arrText = text.split('');
let line = '';
for (let n = 0; n < arrText.length; n++) {
const testLine = line + arrText[n];
const metrics = context.measureText(testLine);
const testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
// 设置字体样式
// context.font = "bold 30px";
context.fillText(line, x, y);
line = arrText[n];
y += lineHeight;
} else {
line = testLine;
}
}
context.fillText(line, x, y);
},
# 保存图片
将canvas转成图片需要使用canvasToTempFilePath()方法,然后通过wx.saveImageToPhotosAlbum()方法将图片保存到手机相册。
// 将canvas转为图片并保存
wx.canvasToTempFilePath({
canvas: canvasDom,
width: canvasW,
height: canvasH,
destWidth: canvasW, // 一定要设置 否则输出图片是原本的pixelRatio倍导致图片不全
destHeight: canvasH,
fileType: 'jpg', // 图片类型
// quality: 1, // 图片质量
success: (res) => {
console.log('生成成功', res.tempFilePath);
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath, // 临时文件路径或永久文件路径 (本地路径) ,不支持网络路径
success: (res) => {
console.log('保存海报成功', res)
},
fail: (err) => {
console.log('保存海报成功', err)
}
})
}
})
# 示例
// html
<view class="index">
<view class="index-canvas">
<canvas class="canvas" type="2d" id="myCanvas" width="750" heigth="1000"></canvas>
</view>
<button type="primary" disabled="{{saveLoad}}" bindtap="saveCanvasImage">生成海报</button>
</view>
// js
Page({
data: {
saveLoad: false,
canvasDom: null,
context: null,
canvasW: 0,
canvasH: 0,
textY: 0,
},
onLoad() {
// this.getCardList()
},
onReady() {
this.initCanvas()
},
//初始化画布
initCanvas() {
const query = this.createSelectorQuery()
query
.select('#myCanvas')
.fields({ node: true, size: true })
.exec((res) => {
const canvasDom = res[0].node
const context = canvasDom.getContext('2d')
const dpr = wx.getSystemInfoSync().pixelRatio
const canvasW = res[0].width * dpr
const canvasH = res[0].height * dpr
canvasDom.width = canvasW
canvasDom.height = canvasH
this.setData({
canvasDom,
context,
canvasW,
canvasH,
})
// this.drawImage()
// this.downloadFile()
})
},
// 创建一个图片对象
drawImage(url, x, y, w, h) {
const { context, canvasDom } = this.data
const image = canvasDom.createImage();
image.src = url;
image.onload = () => {
// 将图片绘制到canvas上
context.drawImage(image, x, y, w, h)
}
},
// 下载图片
downloadFile(url) {
return new Promise((resolve, reject) => {
wx.downloadFile({
url,
success: (res) => {
console.log('下载成功', res.tempFilePath);
resolve(res.tempFilePath)
},
fail: (err) => {
console.log('下载失败', err);
reject(err)
}
})
})
},
// 绘制文本
drawText(text, x, y, maxWidth, lineHeight) {
const { context } = this.data;
// 字符分隔为数组
const arrText = text.split('');
let line = '';
for (let n = 0; n < arrText.length; n++) {
const testLine = line + arrText[n];
const metrics = context.measureText(testLine);
const testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
// 设置字体样式
// context.font = "bold 30px";
context.fillText(line, x, y);
line = arrText[n];
y += lineHeight;
} else {
line = testLine;
}
}
// 绘制后的高度
this.setData({
textY: y
})
context.fillText(line, x, y);
},
// 保存海报
async saveCanvasImage() {
const that = this
const { context, canvasDom, canvasW, canvasH } = this.data
this.setData({
saveLoad: true
})
// 绘制分享图片
const imgUrl = await this.downloadFile('https://via.placeholder.com/200x300')
this.drawImage(imgUrl, 150, 30, 200, 300)
// 设置文字字号及字体
context.font = '30px'
// 设置画笔颜色
context.fillStyle = '#000'
context.fillText('我是分享标题', 50, 350)
const desc = '我是商品详情介绍,我是商品详情介绍,我是商品详情介绍,我是商品详情介绍。'
this.drawText(desc, 50, 380, canvasW - 50, 20)
context.fillText('分享得好礼', 50, this.data.textY + 30)
// 将canvas转为为图片
wx.canvasToTempFilePath({
canvas: canvasDom,
width: canvasW,
height: canvasH,
destWidth: canvasW, // 一定要设置 否则输出图片是原本的pixelRatio倍导致图片不全
destHeight: canvasH,
fileType: 'jpg', // 图片类型
// quality: 1, // 图片质量
success: (res) => {
that.setData({
saveLoad: false
})
console.log('生成成功', res.tempFilePath);
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath, // 临时文件路径或永久文件路径 (本地路径) ,不支持网络路径
success: (res) => {
console.log('保存海报成功', res)
},
fail: (err) => {
console.log('保存海报成功', err)
}
})
},
fail: (err) => {
that.setData({
saveLoad: false
})
console.log('生成错误', err)
}
})
},
})
// css
.index {
width: 100%;
height: 100vh;
padding-top: 20rpx;
background-color: #fff;
.index-canvas {
width: 600rpx;
height: 800rpx;
margin: 0 auto;
canvas {
width: 100%;
height: 100%;
}
}
}
← 小程序引入字体包和图标 小程序常见问题 →