简介:本文详细解析小程序Canvas组件在图片绘制与竖排文字渲染中的核心技巧,包含坐标计算、样式控制及性能优化策略,助力开发者实现复杂视觉效果。
小程序Canvas组件作为底层绘图API,提供了与HTML5 Canvas高度相似的接口,但针对移动端场景进行了性能优化。其核心特性包括:
CanvasContext实例中执行,需通过draw()方法触发实际渲染,此过程为异步操作。在图片与文字混合绘制的场景中,需特别注意:
@load事件获取图片尺寸后再进行绘制measureText()获取宽度,但竖排文字需自行实现坐标转换
// 获取Canvas上下文const ctx = wx.createCanvasContext('myCanvas')// 创建Image对象const img = wx.createImage()img.src = 'https://example.com/image.jpg'img.onload = () => {// 绘制图片(x,y为左上角坐标,w,h为宽高)ctx.drawImage(img, 50, 50, 200, 150)ctx.draw() // 触发渲染}
关键参数说明:
drawImage()支持三种重载形式:drawImage(image, dx, dy)drawImage(image, dx, dy, dWidth, dHeight)drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)(裁剪绘制)
wx.chooseImage({success: (res) => {const tempFilePath = res.tempFilePaths[0]const img = wx.createImage()img.src = tempFilePathimg.onload = () => {// 保持宽高比绘制const scale = Math.min(300/img.width, 200/img.height)ctx.drawImage(img, 50, 50, img.width*scale, img.height*scale)ctx.draw()}}})
// 绘制背景ctx.setFillStyle('#f0f0f0')ctx.fillRect(0, 0, 400, 300)// 绘制主图ctx.drawImage(mainImg, 50, 50, 200, 200)// 应用圆形遮罩ctx.save()ctx.beginPath()ctx.arc(150, 150, 100, 0, Math.PI*2)ctx.clip()ctx.drawImage(overlayImg, 50, 50, 200, 200)ctx.restore()
竖排文字需解决两个核心问题:
function drawVerticalText(ctx, text, x, y, options = {}) {const {fontSize = 16,color = '#000',direction = 'right' // 'right'或'left'} = optionsctx.setFontSize(fontSize)ctx.setFillStyle(color)for (let i = 0; i < text.length; i++) {ctx.save()ctx.translate(x, y + i * fontSize)if (direction === 'right') {ctx.rotate(Math.PI / 2) // 顺时针90度} else {ctx.rotate(-Math.PI / 2) // 逆时针90度}ctx.fillText(text[i], 0, 0)ctx.restore()}}// 使用示例drawVerticalText(ctx, '竖排文字', 100, 50, {fontSize: 18,direction: 'right'})
function drawVerticalTextBlock(ctx, text, x, y, width, options) {const lines = []let currentLine = ''// 简单分词(实际应用需更复杂的分词逻辑)for (const char of text) {const testLine = currentLine + charconst metrics = ctx.measureText(testLine)if (metrics.width <= width) {currentLine = testLine} else {lines.push(currentLine)currentLine = char}}lines.push(currentLine)// 绘制lines.forEach((line, i) => {ctx.save()ctx.translate(x, y + i * (options.fontSize || 16))ctx.rotate(Math.PI / 2)ctx.fillText(line, 0, 0)ctx.restore()})}
// 预先绘制竖排文本
function preRenderVerticalText(text) {
const ctx = offscreenCtx.getContext(‘2d’)
// …绘制逻辑
return offscreenCtx.toTempFilePath()
}
2. **批量绘制**:合并多个绘制操作为一次draw调用```javascript// 错误示例:多次draw导致闪烁text.split('').forEach(char => {ctx.fillText(char, x, y++)ctx.draw() // 每次调用都触发渲染})// 正确示例:批量处理const drawCommands = []text.split('').forEach((char, i) => {drawCommands.push({type: 'text',char,x,y: y + i * fontSize,rotate: Math.PI/2})})// 执行批量绘制drawCommands.forEach(cmd => {ctx.save()ctx.translate(cmd.x, cmd.y)ctx.rotate(cmd.rotate)ctx.fillText(cmd.char, 0, 0)ctx.restore()})ctx.draw()
// 海报配置const posterConfig = {width: 750,height: 1000,bgColor: '#ffffff',title: '活动海报',subTitle: '2024年春季特惠',mainImage: 'https://example.com/main.jpg',qrCode: 'https://example.com/qr.jpg',verticalText: '限时抢购\n机会难得\n不容错过'}function generatePoster() {const ctx = wx.createCanvasContext('posterCanvas')// 1. 绘制背景ctx.setFillStyle(posterConfig.bgColor)ctx.fillRect(0, 0, posterConfig.width, posterConfig.height)// 2. 绘制主图const mainImg = wx.createImage()mainImg.src = posterConfig.mainImagemainImg.onload = () => {ctx.drawImage(mainImg, 50, 100, 650, 400)// 3. 绘制标题ctx.setFontSize(36)ctx.setFillStyle('#333333')ctx.setTextAlign('center')ctx.fillText(posterConfig.title, posterConfig.width/2, 550)// 4. 绘制竖排文字drawVerticalText(ctx, posterConfig.verticalText,posterConfig.width - 80, 600, {fontSize: 24,color: '#e74c3c',direction: 'right'})// 5. 绘制二维码const qrImg = wx.createImage()qrImg.src = posterConfig.qrCodeqrImg.onload = () => {ctx.drawImage(qrImg, posterConfig.width - 120, 850, 100, 100)ctx.draw()}}}// 改进版:使用Promise确保加载顺序function generatePosterPro() {return new Promise((resolve) => {const ctx = wx.createCanvasContext('posterCanvas')const loadTasks = []// 主图加载const mainImg = wx.createImage()mainImg.src = posterConfig.mainImageloadTasks.push(new Promise(r => {mainImg.onload = () => {ctx.drawImage(mainImg, 50, 100, 650, 400)r()}}))// 二维码加载const qrImg = wx.createImage()qrImg.src = posterConfig.qrCodeloadTasks.push(new Promise(r => {qrImg.onload = () => {r()}}))Promise.all(loadTasks).then(() => {// 绘制静态内容ctx.setFillStyle(posterConfig.bgColor)ctx.fillRect(0, 0, posterConfig.width, posterConfig.height)ctx.setFontSize(36)ctx.setFillStyle('#333333')ctx.setTextAlign('center')ctx.fillText(posterConfig.title, posterConfig.width/2, 550)drawVerticalText(ctx, posterConfig.verticalText,posterConfig.width - 80, 600, {fontSize: 24,color: '#e74c3c',direction: 'right'})ctx.drawImage(qrImg, posterConfig.width - 120, 850, 100, 100)ctx.draw()resolve()})})}
img.onload回调中执行绘制操作扩展:对于网络图片,建议设置超时机制
function loadImageWithTimeout(url, timeout = 5000) {return new Promise((resolve, reject) => {const img = wx.createImage()img.src = urlconst timer = setTimeout(() => {reject(new Error('Image load timeout'))}, timeout)img.onload = () => {clearTimeout(timer)resolve(img)}img.onerror = () => {clearTimeout(timer)reject(new Error('Image load failed'))}})}
解决:调整字符绘制位置
// 改进后的字符绘制function drawChar(ctx, char, x, y, fontSize) {ctx.save()ctx.translate(x, y)ctx.rotate(Math.PI / 2)// 测量文本宽度(竖排时实际是高度)const metrics = ctx.measureText(char)// 调整基线位置(根据字体特性可能需要微调)const baselineOffset = fontSize * 0.2ctx.fillText(char, 0, baselineOffset)ctx.restore()}
canvas-id对应的元素style="position: fixed; z-index: 999;"wx.createOffscreenCanvas()进行离屏渲染减少draw调用次数:
ctx.beginPath()和ctx.closePath()明确绘图范围合理使用离屏Canvas:
图片优化:
内存管理:
动画优化:
requestAnimationFrame替代setTimeout通过系统掌握上述技术要点,开发者可以高效实现小程序中复杂的图片处理与竖排文字渲染需求,创建出专业级的视觉效果。实际开发中,建议结合小程序官方文档的Canvas组件说明进行调试,特别注意不同基库版本的API差异。