×

canvas.drawImage基础用法是啥?

提问者:Terry2025.11.06浏览:33

在小程序开发里,canvas.drawImage是实现图片绘制、海报生成、涂鸦等功能的核心API,但不少开发者刚接触时总会碰到「图片咋不显示」「画出来咋模糊」这类问题,这篇文章用问答形式,把canvas.drawImage的用法、坑点、优化技巧一次性讲透,帮你少走弯路~

小程序里,drawImage是Canvas上下文(CanvasContext)的方法,作用是把图片画到canvas上,它的参数分两种场景设计:

  • 简单绘制(5个参数)ctx.drawImage(imageResource, x, y, width, height),这里imageResource是图片资源(支持本地路径、临时路径,或通过wx.getImageInfo/wx.downloadFile处理后的网络图片);xy是图片在canvas上的左上角坐标;widthheight是图片绘制后的宽高(会触发缩放)。

  • 裁剪+绘制(9个参数)ctx.drawImage(imageResource, sx, sy, sw, sh, dx, dy, dw, dh),前四个参数sxsy原图中要裁剪区域的起点坐标swsh是裁剪区域的宽高;后四个参数dxdy是裁剪后的图片画到canvas的起点坐标,dwdh是裁剪后图片的宽高。

实际开发中,“先准备合法图片资源,再执行绘制”是关键,比如绘制网络图片时,不能直接写远程URL,需先转成本地资源:

// 步骤1:将网络图片转成本地路径
wx.getImageInfo({
  src: 'https://example.com/bg.jpg', // 远程图片地址
  success(res) {
    const ctx = wx.createCanvasContext('myCanvas') // 创建canvas上下文
    // 步骤2:用处理后的本地路径绘制图片
    ctx.drawImage(res.path, 0, 0, 300, 200) 
    // 步骤3:调用draw()完成渲染(draw是异步操作,必须调用)
    ctx.draw()
  }
})

如果是项目内的本地图片(如/images/icon.png),可直接写相对路径,但要注意基础库版本的兼容性~

图片死活不显示,咋排查?

碰到图片画不出来,可从这5个方向逐一检查:

  1. 图片资源是否合法

    • 网络图片未处理:canvas不支持直接传入https://xxx格式的远程URL,必须通过wx.getImageInfowx.downloadFile转成tempFilePath/path

    • 本地图片路径错误:比如写成../../images但实际是/images,或图片未被小程序打包(需确保图片在项目目录内)。

  2. 坐标或尺寸是否为0
    drawImagewidthheight设为0,或xy超出canvas范围(比如canvas宽200px,却把x设为300px),图片会“消失”,需检查参数是否合理。

  3. 是否遗漏ctx.draw()
    drawImage仅将绘制指令加入队列,必须调用ctx.draw()才会真正渲染,若后续操作依赖绘制结果(如保存canvas为图片),需在draw的回调中处理:

    ctx.draw(false, () => { // false表示保留之前的绘制内容
      // 绘制完成后执行保存操作
      wx.canvasToTempFilePath({ canvasId: 'myCanvas', ... })
    })
  4. 图片加载时机是否错位
    若页面onLoad时就调用drawImage,但图片还在下载(异步问题),会导致绘制失败,可通过Promise封装图片加载,确保图片就绪后再绘制:

    function getImage(src) {
      return new Promise(resolve => {
        wx.getImageInfo({ src, success: resolve })
      })
    }
    async function draw() {
      const bg = await getImage('https://example.com/bg.jpg')
      const avatar = await getImage('https://example.com/avatar.png')
      const ctx = wx.createCanvasContext('myCanvas')
      ctx.drawImage(bg.path, 0, 0, 300, 200)
      ctx.drawImage(avatar.path, 50, 50, 80, 80)
      ctx.draw()
    }
    draw()
  5. canvas层级或组件上下文是否正确
    canvas是原生组件,层级高于普通组件;若在自定义组件内使用canvas,需确保创建上下文时传入组件实例:

    Component({
      methods: {
        draw() {
          // 第二个参数传入组件实例this,确保上下文指向正确
          const ctx = wx.createCanvasContext('myCanvas', this) 
          ctx.drawImage(...)
          ctx.draw()
        }
      }
    })

画出来的图片模糊,咋优化?

图片模糊多由设备像素比(dpr)canvas尺寸配置导致,手机屏幕分1x、2x、3x(如iPhone多数为3x),canvas默认按1x渲染,高清屏上易模糊,优化分两步:

  1. 配置canvas的物理尺寸与显示尺寸
    先通过wx.getSystemInfo获取设备dpr,再让canvas的物理宽高 = 显示宽高 × dpr,而样式宽高保持显示宽高,比如要让canvas显示300px宽:

    <canvas 
      type="2d" 
      canvas-id="myCanvas" 
      style="width: 300px; height: 200px;" 
      width="{{300 * dpr}}" 
      height="{{200 * dpr}}"
    ></canvas>
    Page({
      data: { dpr: 1 },
      onLoad() {
        wx.getSystemInfo({
          success: res => this.setData({ dpr: res.pixelRatio })
        })
      }
    })
  2. 绘制时适配dpr
    绘制图片、文字时,宽高需对应dpr,比如canvas物理宽为300 * dpr,绘制时drawImage的宽高仍用300(因样式宽为300,物理宽为300 * dpr,绘制的图片会自动适配dpr),若用9参数裁剪,sxsy等参数也需按原图实际像素计算~

咋实现图片裁剪或局部绘制?

借助drawImage的9参数版本即可实现裁剪,举个场景:把一张1000×1000的头像,裁出中间300×300的区域,再缩放到canvas上的200×200显示,步骤如下:

原图(1000×1000) → 裁剪区域(sx=350, sy=350, sw=300, sh=300) → 画到canvas(dx=50, dy=50, dw=200, dh=200

代码示例:

wx.getImageInfo({
  src: 'https://example.com/avatar.jpg',
  success(res) {
    const ctx = wx.createCanvasContext('myCanvas')
    // 参数:imageResource, sx, sy, sw, sh, dx, dy, dw, dh
    ctx.drawImage(
      res.path,   // 原图资源路径
      350, 350, 300, 300, // 原图裁剪起点(350,350)、裁剪宽高300×300
      50, 50, 200, 200   // 画到canvas的起点(50,50)、绘制宽高200×200
    )
    ctx.draw()
  }
})

通过调整这9个参数,就能灵活控制原图裁剪区域与canvas上的显示效果~

多个图片绘制,层级和顺序咋控制?

canvas的绘制逻辑是“先执行的代码画在底层,后执行的画在上层”,因此调整代码顺序即可控制层级,比如制作海报时,先画背景图,再画商品图,最后画文字:

ctx.drawImage(bgPath, 0, 0, 300, 500) // 背景(底层)
ctx.drawImage(goodsPath, 50, 100, 200, 200) // 商品图(中层)
ctx.setFontSize(16)
ctx.fillText('限时折扣', 80, 350) // 文字(上层)
ctx.draw()

若图片为异步加载(如背景和商品图均为网络图片),需确保“先加载完的图片先画”,否则会出现“后加载的图片覆盖先加载的,但代码顺序却在前面”的混乱,此时可用Promise.all统一管理加载顺序:

async function drawPoster() {
  // 同时加载背景和商品图,确保都完成后再绘制
  const [bg, goods] = await Promise.all([
    getImage('https://example.com/bg.jpg'),
    getImage('https://example.com/goods.jpg')
  ])
  const ctx = wx.createCanvasContext('myCanvas')
  // 按顺序绘制:背景 → 商品图 → 文字
  ctx.drawImage(bg.path, 0, 0, 300, 500)
  ctx.drawImage(goods.path, 50, 100, 200, 200)
  ctx.fillText('限时折扣', 80, 350)
  ctx.draw()
}
drawPoster()

自定义组件里用canvas.drawImage,要注意啥?

在自定义组件(Component)中,canvas的上下文管理与页面不同,易踩以下坑:

  1. 获取CanvasContext需传组件实例
    组件内创建上下文时,第二个参数必须传this(组件实例),否则canvas无法找到对应节点:

    Component({
      methods: {
        draw() {
          // 第二个参数传入组件实例this
          const ctx = wx.createCanvasContext('myCanvas', this) 
          ctx.drawImage(...)
          ctx.draw()
        }
      }
    })
  2. 图片资源的作用域要正确
    组件内的data变量需通过this.data获取,比如图片路径存在组件data中,绘制时需写this.data.bgPath

  3. 组件生命周期的执行时机
    组件的attached阶段,canvas节点可能尚未渲染完成,建议在ready生命周期初始化绘制,或用this.createSelectorQuery()确认节点存在:

    Component({
      ready() {
        this.createSelectorQuery()
          .select('#myCanvas')
          .fields({ node: true })
          .exec(res => {
            const canvas = res[0].node
            const ctx = canvas.getContext('2d') // 2d上下文写法(若用type="2d")
            // 在此处执行drawImage等操作
          })
      }
    })

    (注:若用旧版canvas-id+wx.createCanvasContext,与2d上下文写法有差异,需根据基础库版本选择~)

性能优化有啥技巧?

若做“频繁绘图”类功能(如涂鸦、实时生成海报),性能不佳会导致卡顿,这几个技巧能有效优化:

  1. 减少draw()调用次数
    ctx.draw()是异步渲染,每次调用都会触发重绘,若需绘制多张图片,应将所有drawImage写完后,只调用一次draw

    // 反例:多次调用draw,触发多次重绘
    ctx.drawImage(a)
    ctx.draw()
    ctx.drawImage(b)
    ctx.draw()
    // 正例:合并draw,仅触发一次重绘
    ctx.drawImage(a)
    ctx.drawImage(b)
    ctx.draw()
  2. 压缩图片尺寸
    绘制前用wx.compressImage将图片压缩到合适尺寸(如海报背景图无需2000px宽,压缩到750px足够),降低canvas处理大图片的压力。

  3. 缓存静态内容
    若有固定不变的元素(如海报边框、固定文案),可先画一次并保存为临时图片,后续只需绘制该缓存图,无需重复绘制元素。

  4. 使用离屏canvas预绘制
    对复杂绘制逻辑,可先在离屏canvas(offscreenCanvas)中绘制,再将结果画到显示canvas上,不过小程序对离屏canvas的支持需看基础库版本,且用法与Web略有不同,需测试兼容性~

总结来看,canvas.drawImage的核心是“资源合法、顺序清晰、尺寸适配、性能优化”,吃透这些问题,无论是做海报生成、图片编辑还是互动涂鸦,都能更顺畅~如果还有具体场景的疑问,评论区随时交流~

您的支持是我们创作的动力!

网友回答文明上网理性发言 已有0人参与

发表评论: