小程序Canvas进阶:图片与竖排文字的绘制实践

小程序Canvas进阶:图片与竖排文字的绘制实践

Canvas作为小程序中实现自定义图形渲染的核心组件,在海报生成、动态图表等场景中发挥着关键作用。本文将深入探讨如何在小程序Canvas中高效加载图片并实现竖排文字的精准绘制,从基础API调用到性能优化策略,为开发者提供完整的解决方案。

一、Canvas基础环境准备

1.1 组件配置要点

在小程序页面中配置Canvas组件时,需重点关注以下属性:

  1. <canvas
  2. canvas-id="myCanvas"
  3. style="width: 300px; height: 400px;"
  4. disable-scroll="true"
  5. bindtouchstart="handleTouch"
  6. ></canvas>
  • canvas-id:必须与后续API调用中的标识符一致
  • 尺寸设置:建议通过style属性控制显示尺寸,在JS中动态设置canvas实际宽高(通过CanvasContext.setDimensions
  • 交互控制:disable-scroll可防止触摸时页面滚动

1.2 上下文获取与初始化

  1. Page({
  2. onReady() {
  3. const query = wx.createSelectorQuery()
  4. query.select('#myCanvas')
  5. .fields({ node: true, size: true })
  6. .exec((res) => {
  7. const canvas = res[0].node
  8. const ctx = canvas.getContext('2d')
  9. // 动态设置实际尺寸(避免模糊)
  10. const dpr = wx.getSystemInfoSync().pixelRatio
  11. canvas.width = 300 * dpr
  12. canvas.height = 400 * dpr
  13. ctx.scale(dpr, dpr)
  14. this.ctx = ctx
  15. })
  16. }
  17. })

关键注意事项:

  • 必须等待onReady生命周期执行
  • 高清屏适配需乘以设备像素比(dpr)
  • 推荐使用SelectorQuery获取节点而非直接操作DOM

二、图片绘制核心实现

2.1 图片资源加载流程

  1. loadImage(src) {
  2. return new Promise((resolve, reject) => {
  3. const img = canvas.createImage()
  4. img.src = src
  5. img.onload = () => resolve(img)
  6. img.onerror = (e) => reject(new Error('图片加载失败'))
  7. })
  8. }
  9. // 使用示例
  10. async drawImage() {
  11. try {
  12. const img = await this.loadImage('/assets/example.jpg')
  13. this.ctx.drawImage(img, 50, 50, 200, 150)
  14. this.ctx.draw() // 必须调用draw()触发渲染
  15. } catch (err) {
  16. console.error(err)
  17. }
  18. }

2.2 性能优化策略

  1. 图片预加载:在页面加载时提前加载所有可能用到的图片资源
  2. 缓存机制:使用Map对象缓存已加载的图片实例
    ```javascript
    // 图片缓存实现
    const imageCache = new Map()

async getCachedImage(src) {
if (imageCache.has(src)) {
return imageCache.get(src)
}
const img = await this.loadImage(src)
imageCache.set(src, img)
return img
}

  1. 3. **压缩处理**:对大图进行适当压缩,建议单张图片不超过2MB
  2. 4. **分步渲染**:复杂场景可拆分为多个draw调用,配合setTimeout分帧处理
  3. ## 三、竖排文字实现方案
  4. ### 3.1 基础竖排实现方法
  5. ```javascript
  6. drawVerticalText(text, x, y, options = {}) {
  7. const {
  8. fontSize = 16,
  9. color = '#000',
  10. maxWidth = 100,
  11. lineHeight = 24
  12. } = options
  13. this.ctx.setFontSize(fontSize)
  14. this.ctx.setFillStyle(color)
  15. // 简单分词处理(实际项目需更复杂的分词逻辑)
  16. const chars = text.split('')
  17. chars.forEach((char, index) => {
  18. const charY = y + index * lineHeight
  19. // 判断是否超出边界
  20. if (charY > y + maxWidth) return
  21. this.ctx.fillText(char, x, charY)
  22. })
  23. }

3.2 高级排版技巧

3.2.1 自动换行实现

  1. drawAutoWrapVerticalText(text, x, y, options) {
  2. const {
  3. fontSize = 16,
  4. maxWidth = 100,
  5. lineHeight = 24,
  6. containerHeight = 300
  7. } = options
  8. const ctx = this.ctx
  9. ctx.setFontSize(fontSize)
  10. const textWidth = ctx.measureText('测').width // 中文字符宽度参考
  11. let currentLine = 0
  12. let currentY = y
  13. for (let i = 0; i < text.length; i++) {
  14. const char = text[i]
  15. const nextChar = text[i+1]
  16. // 判断是否需要换行
  17. if (currentY + lineHeight > y + containerHeight) {
  18. break // 超出容器高度
  19. }
  20. // 标点符号特殊处理(可选)
  21. if (this.isPunctuation(char) && nextChar) {
  22. // 标点悬停处理逻辑
  23. }
  24. ctx.fillText(char, x, currentY)
  25. currentY += lineHeight
  26. }
  27. }

3.2.2 从右向左排版

  1. drawRightToLeftVertical(text, x, y, options) {
  2. const { lineHeight = 24 } = options
  3. const chars = text.split('')
  4. const totalChars = chars.length
  5. chars.forEach((char, index) => {
  6. const charX = x
  7. // 从下往上计算Y坐标(传统竖排从右向左)
  8. const charY = y + (totalChars - 1 - index) * lineHeight
  9. this.ctx.fillText(char, charX, charY)
  10. })
  11. }

四、完整实现示例

4.1 综合绘制函数

  1. async drawPoster() {
  2. const ctx = this.ctx
  3. // 清空画布
  4. ctx.clearRect(0, 0, 300, 400)
  5. // 1. 绘制背景
  6. ctx.setFillStyle('#f8f8f8')
  7. ctx.fillRect(0, 0, 300, 400)
  8. // 2. 绘制图片
  9. try {
  10. const bgImg = await this.getCachedImage('/assets/bg.jpg')
  11. ctx.drawImage(bgImg, 0, 0, 300, 200)
  12. } catch (err) {
  13. console.error('背景图加载失败', err)
  14. }
  15. // 3. 绘制竖排标题
  16. ctx.setFillStyle('#333')
  17. ctx.setFontSize(18)
  18. this.drawVerticalText('竖排标题示例', 250, 220, {
  19. lineHeight: 24,
  20. maxWidth: 40
  21. })
  22. // 4. 绘制正文(从右向左)
  23. ctx.setFillStyle('#666')
  24. ctx.setFontSize(14)
  25. this.drawRightToLeftVertical(
  26. '这是从右向左排列的竖排文本内容示例',
  27. 280, 260,
  28. { lineHeight: 20 }
  29. )
  30. // 触发渲染
  31. ctx.draw()
  32. }

五、常见问题解决方案

5.1 图片显示模糊问题

  • 原因:未考虑设备像素比(dpr)
  • 解决方案
    ```javascript
    // 在获取canvas上下文前执行
    const dpr = wx.getSystemInfoSync().pixelRatio
    const canvasWidth = 300
    const canvasHeight = 400

// 设置canvas实际尺寸
ctx.setDimensions({
width: canvasWidth dpr,
height: canvasHeight
dpr
})
// 缩放画布
ctx.scale(dpr, dpr)

  1. ### 5.2 文字排版错乱
  2. - **常见原因**:
  3. - 未正确处理中英文混合排版
  4. - 标点符号位置不当
  5. - 字体大小设置不一致
  6. - **优化建议**:
  7. - 使用`ctx.measureText()`获取精确文字宽度
  8. - 实现更完善的分词算法(可引入第三方分词库)
  9. - 统一字体族设置:`ctx.setFontSize('Microsoft YaHei')`
  10. ### 5.3 性能卡顿问题
  11. - **优化方向**:
  12. 1. 减少draw调用次数,合并多个绘制操作为一次draw
  13. 2. 对静态元素进行离屏渲染(offscreen canvas
  14. 3. 使用`wx.canvasToTempFilePath`quality参数控制输出质量
  15. 4. 复杂场景考虑使用Web Worker进行计算(需小程序基础库支持)
  16. ## 六、最佳实践建议
  17. 1. **分层渲染策略**:
  18. - 静态背景层:单独绘制并缓存
  19. - 动态内容层:按需更新
  20. - 交互层:单独处理点击事件
  21. 2. **错误处理机制**:
  22. ```javascript
  23. try {
  24. await this.drawPoster()
  25. } catch (error) {
  26. wx.showToast({
  27. title: '生成失败,请重试',
  28. icon: 'none'
  29. })
  30. console.error('绘制错误:', error)
  31. }
  1. 内存管理

    • 及时释放不再使用的图片资源
    • 避免在canvas中绘制过多隐藏元素
    • 定期调用ctx.clearRect()清理画布
  2. 跨平台兼容

    • 测试不同设备像素比下的显示效果
    • 处理不同小程序平台的API差异
    • 提供降级方案(如不支持竖排时改为横排)

通过系统掌握上述技术要点和优化策略,开发者可以高效实现小程序Canvas中的图片与竖排文字绘制功能,打造出性能优异、体验流畅的自定义图形界面。在实际项目开发中,建议结合具体业务场景进行功能扩展和性能调优,持续提升用户体验。