×

小程序canvas背景色设置和显示有哪些门道?

提问者:Terry2025.11.09浏览:34

做小程序开发时,不少人在canvas背景色这块栽过跟头——设置了颜色却不显示,和其他元素叠加后层级乱套,不同设备显示效果还不一样……今天把小程序canvas背景色的「坑」和「解法」掰开了说,从基础设置到疑难杂症全聊透,帮你把背景色玩明白!

小程序canvas背景色基础:怎么给canvas加底色?

给canvas加背景色,核心有两种思路:用CanvasContext绘制填充,或者给外层容器加背景,两者适用场景不同,先看具体操作——

用CanvasContext画纯色背景(最可控的方式)

打开微信开发者工具,在页面的js文件里,通过wx.createCanvasContext拿到画布上下文后,用setFillStyle定颜色,再用fillRect把整个画布区域填满,举个简单例子:

Page({
  onReady() {
    // 创建canvas上下文(注意canvas的id要和wxml对应)
    const ctx = wx.createCanvasContext('myCanvas')
    // 设置背景色(十六进制、rgb、rgba都支持)
    ctx.setFillStyle('#f5f5f5') 
    // 填充整个canvas区域(参数是x,y,宽,高)
    ctx.fillRect(0, 0, 300, 200) 
    // 执行绘制(把指令同步到画布上)
    ctx.draw() 
  }
})

这种方法的优势很明显:背景和canvas内容“绑定”,后续导出图片(比如生成分享海报)时,背景会被一起包含;而且能精准控制背景绘制时机(比如先画背景再画其他元素,避免被覆盖)。

但要注意:如果canvas有多层绘制(比如先画了文字再画背景),背景会把之前的内容盖住,所以必须保证“先画背景,再画其他元素”的顺序!

给canvas外层容器加背景(最简单的方式)

如果只是想让canvas在视觉上有个底色,不需要背景被导出到图片里,可以给canvas套个<view>,用CSS给view加背景色。

wxml结构:

<view class="canvas-wrap">
  <canvas type="2d" id="myCanvas" style="width:300px;height:200px;"></canvas>
</view>

wxss样式:

.canvas-wrap {
  background-color: #eee;
  width: 300px;
  height: 200px;
}

这种方式的好处是简单无脑,适合“只需要视觉背景、canvas本身透明”的场景(比如涂鸦工具,用户画的线条在view背景上显示)。

但有个大坑:如果canvas本身用了第一种方法画了不透明背景,外层view的背景会被完全盖住!所以用这种方式时,要确保canvas的透明度(比如canvas里没画背景,或者用rgba设置透明背景)。

背景色设置后不显示?先排查这3个点

很多同学遇到“设置了背景色但看不到”,大概率是这几个细节没处理好——

canvas尺寸和绘制区域不匹配

小程序canvas有两个“尺寸”:标签上的width/height(逻辑像素)样式里的width/height(布局像素),如果两者不一致,绘制的背景会“错位”或“显示不全”。

canvas标签里width="300"height="200",但样式里style="width:600px;height:400px;"(适配高清屏),这时候用fillRect(0,0,300,200)画背景,视觉上只会显示一半。

解决方法:获取设备的devicePixelRatio(设备像素比),让canvas的逻辑像素 = 样式像素 × dpr,代码参考:

onReady() {
  const dpr = wx.getSystemInfoSync().pixelRatio
  const canvas = this.selectComponent('#myCanvas')
  // 把canvas的逻辑宽高设为 样式宽高 × dpr
  canvas.setData({
    width: 300 * dpr,
    height: 200 * dpr
  })
  const ctx = wx.createCanvasContext('myCanvas')
  // 填充时,宽高也要对应逻辑像素
  ctx.fillRect(0, 0, 300 * dpr, 200 * dpr)
  ctx.draw()
}

绘制顺序搞反了

canvas是“分层绘制”的,后画的内容会覆盖先画的,如果先画了文字/图片,再画背景,背景就会把之前的内容盖住吗?不!是之前的内容会把背景盖住

举个错误案例:

// 错误顺序:先画文字,再画背景
ctx.fillText('hello', 100, 100) 
ctx.setFillStyle('#f5f5f5')
ctx.fillRect(0, 0, 300, 200)
ctx.draw()

这时候文字会被背景盖住,视觉上只看到背景,看不到文字。正确顺序是:先画背景,再画其他内容

透明度和层级的“隐形干扰”

如果给canvas或外层view加了opacity属性,或者canvas作为原生组件(层级比普通组件高)和其他元素叠加,也会导致背景显示异常。

给canvas设opacity: 0.5,背景色会变浅;如果外层view有opacity,canvas的背景也会继承透明度。

小程序里canvas是原生组件,层级默认很高,如果外层view的背景想“透过”canvas显示,必须让canvas本身透明(比如不画背景,或用rgba(245,245,245,0.5)这种透明色)。

不同场景下,选哪种背景设置方式?

背景色的设置方式没有“绝对正确”,得看场景需求——

生成带背景的分享图(必须用CanvasContext绘制)

如果要把canvas导出成图片(比如用户分享海报),外层view的背景不会被包含在导出内容里!这时候必须用CanvasContext画背景。

举个导出海报的流程:

  1. ctx.setFillStyle + ctx.fillRect画背景;

  2. 画产品图、文字等其他元素;

  3. 调用wx.canvasToTempFilePath导出图片;

  4. 保存到相册或分享。

导出的图片才会包含背景色!

实时交互的canvas(比如涂鸦、小游戏)

这类场景下,canvas内容频繁变化,用外层view加背景更灵活:

  • 涂鸦工具:canvas本身透明,用户画的线条颜色在view背景上显示,不需要每次重绘背景;

  • 小游戏:背景固定(比如地图),可以先用CanvasContext画一次背景,后续只更新角色、道具等动态元素,减少性能消耗。

多端兼容(微信、支付宝、抖音小程序)

不同平台的canvas API有差异(比如支付宝小程序创建上下文的方式和微信不同),这时候用外层view加背景更通用——因为样式设置(background-color)是跨平台的,而CanvasContext的绘制方法可能需要适配各平台API。

进阶玩法:渐变、图案背景怎么搞?

除了纯色,很多场景需要渐变、纹理背景,这时候得用canvas的高级绘制技巧——

线性渐变背景

createLinearGradient实现“从左到右、从上到下”的颜色过渡,步骤:

  1. 创建渐变对象:ctx.createLinearGradient(x1, y1, x2, y2)(x1,y1是渐变起点,x2,y2是终点);

  2. addColorStop(位置, 颜色)添加渐变节点;

  3. 把渐变对象设为fillStyle,填充画布。

代码示例(做一个“金黄到橙红”的渐变背景):

const ctx = wx.createCanvasContext('myCanvas')
// 从左上角(0,0)到右下角(300,200)的渐变
const grad = ctx.createLinearGradient(0, 0, 300, 200) 
grad.addColorStop(0, '#ffd700') // 起点颜色(0表示渐变开始)
grad.addColorStop(1, '#ff4500') // 终点颜色(1表示渐变结束)
ctx.setFillStyle(grad)
ctx.fillRect(0, 0, 300, 200)
ctx.draw()

径向渐变背景

createRadialGradient实现“从圆心向外扩散”的效果,适合做“光晕、 spotlight”类设计,步骤类似线性渐变,但参数是两个圆的圆心和半径

const ctx = wx.createCanvasContext('myCanvas')
// 内圆:圆心(150,100),半径20;外圆:圆心(150,100),半径100
const grad = ctx.createRadialGradient(150, 100, 20, 150, 100, 100) 
grad.addColorStop(0, '#ffffff') // 内圆颜色
grad.addColorStop(1, '#e6e6e6') // 外圆颜色
ctx.setFillStyle(grad)
ctx.fillRect(0, 0, 300, 200)
ctx.draw()

图案填充(重复图片背景)

想做“报纸纹理、布纹”这类效果?可以用createPattern把图片重复铺满canvas,但要注意:图片必须先加载完成,才能用来做图案

代码示例(用本地图片做重复背景):

Page({
  onReady() {
    const ctx = wx.createCanvasContext('myCanvas')
    // 先加载图片(本地或网络,网络需配置域名)
    wx.getImageInfo({
      src: '/images/pattern.png', 
      success: (res) => {
        // 创建图案:参数是图片路径、重复方式(repeat/repeat-x等)
        const pattern = ctx.createPattern(res.path, 'repeat') 
        ctx.setFillStyle(pattern)
        ctx.fillRect(0, 0, 300, 200)
        ctx.draw()
      }
    })
  }
})

关键点:图片加载是异步的,必须在success回调里处理绘制逻辑,否则会出现“图案没加载完就画,导致背景无效”的问题。

避坑指南:那些年踩过的“暗坑”

除了基础问题,这些细节也容易让背景色“翻车”,提前避坑——

设备像素比(dpr)导致背景错位

前面提过dpr的影响,但很多人忽略了:canvas的绘制坐标也要和dpr匹配

比如在dpr=2的手机上,canvas逻辑宽高是600×400(300×200 × 2),如果绘制图片时还用x:100, y:100,实际显示位置会是“逻辑像素100”,对应屏幕物理像素200,导致元素偏移。

解决方法:所有绘制的坐标、宽高都要乘以dpr,比如画图片时:

const imgWidth = 200 * dpr
const imgHeight = 200 * dpr
const x = (300 * dpr - imgWidth) / 2 // 居中计算也要乘dpr
ctx.drawImage(res.path, x, y, imgWidth, imgHeight)

背景色和圆角的“视觉冲突”

如果给外层view设了border-radius,但canvas是直角,会出现“圆角外漏直角背景”的丑态。

  • 方案1:给canvas也设border-radius(但canvas是原生组件,样式支持有限,部分机型可能无效);

  • 方案2:用CanvasContext画圆角背景(用arcTobezierCurveTo画圆角矩形);

  • 方案3:外层view设圆角,canvas设透明,靠外层背景实现圆角(适合简单场景)。

页面跳转后背景消失

小程序页面跳转后,canvas可能被销毁重建,之前画的背景会消失。

解决方法:把绘制背景的逻辑封装成函数,在onShow生命周期里重新调用。

Page({
  onReady() {
    this.drawBackground()
  },
  onShow() {
    this.drawBackground() // 页面显示时重新画背景
  },
  drawBackground() {
    // 这里写绘制背景的逻辑
  }
})

实战:做一个带渐变背景的分享海报

光说不练假把式,结合前面的知识点,做个“带渐变背景+产品图+文字”的分享海报,步骤清晰到能直接抄——

布局和基础设置

wxml:

<view class="poster-wrap">
  <canvas type="2d" id="posterCanvas" style="width:375px;height:667px;"></canvas>
  <button bindtap="savePoster">保存海报</button>
</view>

wxss(简单布局,让canvas居中):

.poster-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-top: 20rpx;
}
button {
  margin-top: 20rpx;
}

绘制渐变背景+内容

js(核心逻辑):

Page({
  data: {
    productImg: '/images/product.png' // 产品图本地路径,需提前放到项目里
  },
  onReady() {
    this.drawPoster()
  },
  drawPoster() {
    // 1. 获取设备像素比,适配高清屏
    const dpr = wx.getSystemInfoSync().pixelRatio
    const canvas = this.selectComponent('#posterCanvas')
    // 设置canvas逻辑宽高 = 样式宽高 × dpr
    canvas.setData({
      width: 375 * dpr,
      height: 667 * dpr
    })
    const ctx = wx.createCanvasContext('posterCanvas')
    // 2. 画渐变背景(从粉到红)
    const grad = ctx.createLinearGradient(0, 0, 375 * dpr, 667 * dpr)
    grad.addColorStop(0, '#ffe4e1')
    grad.addColorStop(1, '#ffc0cb')
    ctx.setFillStyle(grad)
    ctx.fillRect(0, 0, 375 * dpr, 667 * dpr)
    // 3. 画产品图(先加载图片,再绘制)
    wx.getImageInfo({
      src: this.data.productImg,
      success: (res) => {
        const imgWidth = 200 * dpr
        const imgHeight = 200 * dpr
        // 计算图片居中的x坐标
        const x = (375 * dpr - imgWidth) / 2
        const y = 100 * dpr // 图片顶部距离canvas顶部的距离
        ctx.drawImage(res.path, x, y, imgWidth, imgHeight)
        // 4. 画文字(促销文案)
        ctx.setFillStyle('#333') // 文字颜色
        ctx.setFontSize(16 * dpr) // 字号适配dpr
        ctx.setTextAlign('center') // 文字居中
        // 文字y坐标 = 图片底部 + 20px(逻辑像素)× dpr
        const textY = y + imgHeight + 20 * dpr 
        ctx.fillText('爆款产品限时折扣', 375 * dpr / 2, textY)
        // 5. 执行绘制,把所有指令同步到canvas
        ctx.draw()
      }
    })
  },
  // 保存海报到相册
  savePoster() {
    wx.canvasToTempFilePath({
      canvasId: 'posterCanvas',
      success: (res) => {
        wx.saveImageToPhotosAlbum({
          filePath: res.tempFilePath,
          success: () => {
            wx.showToast({ title: '保存成功' })
          },
          fail: (err) => {
            wx.showToast({ title: '保存失败,请授权相册权限', icon: 'none' })
          }
        })
      }
    }, this)
  }
})

效果和拓展

运行代码后,canvas会显示“粉到红的渐变背景+居中的产品图+促销文字”,点击按钮能保存到相册。

如果要拓展,可以加更多元素(比如二维码、价格标签),或者把渐变改成径向渐变、图案填充,玩法非常灵活~

看完这些,再遇到canvas背景色问题,是不是心里有底了?其实核心就是“选对方法+避开水坑”——需要导出背景就用

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

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

发表评论: