小程序绘图篇:如何正确地给一个渐变背景按钮绘制阴影

1,233 阅读9分钟

最近在开发一个小程序项目中遇到了一个小的问题。那就是需要给一个背景是渐变色的按钮添加一层阴影,下图中红色线标记的地方。

file

目的是为了让这个按钮更有立体感。刚开始做的时候觉得没有什么难度,在开发工具上也能够很好的展示;但是到了真机测试的时候,才发现原来给按钮绘制阴影的方法在真机上显示不出来。于是就有了下面我寻找解决方案的过程,最终我顺利的解决了这个问题。我把我解决这个问题的思路记录下来,方便我日后回顾,也希望能够跟大家一起交流,我们一起进步。

当我在手机上发现绘制的按钮没有阴影的时候,我就知道接下来我将可能要付出几个小时的时间去解决这个问题。我也做好了心理准备,于是就踏上了寻找解决方案的道路。

第一步:搜索问题

当我遇到这个问题的时候,我的第一反应就是先上网搜索一下看看有没有同学也遇到过这种问题。于是我就搜索小程序canvas绘图与按钮阴影相关的一些问题和资料。我的确找到了一些相关的文章和问题,但是我发现这些资料要么跟自己遇到的问题不太符合,要么搜到的问题还没有回复。

我提取了这些文章和问题中有用的点,得出了下面的结论:

  • 小程序canvas可以绘制圆角阴影
  • 小程序绘制阴影的API有新旧两个版本
  • 没有说明渐变色背景的按钮是否可以直接添加阴影

第二步:分析对比

先来看一下我现在的代码是这样的:

// ... 此处省去部分代码
// 绘制渐变色
_ctxc.beginPath()
_ctxc.arc((SHARE_TO_CHAT_CANVAS_WIDTH - w) / 2, btn_h + r, r, 0.5 * Math.PI, 1.5 * Math.PI)
// 绘制右边圆弧
_ctxc.arc((SHARE_TO_CHAT_CANVAS_WIDTH + w) / 2, btn_h + r, r, -0.5 * Math.PI, 0.5 * Math.PI)
// 闭合路径
_ctxc.closePath()
// 添加渐变色
const grd = _ctxc.createLinearGradient((SHARE_TO_CHAT_CANVAS_WIDTH - w) / 2 - r, btn_h, (SHARE_TO_CHAT_CANVAS_WIDTH + w) / 2 + r, btn_h)
grd.addColorStop(0, first_color)
grd.addColorStop(1, second_color)
_ctxc.setFillStyle(grd)
// 设置阴影
_ctxc.shadowOffsetX = 2
_ctxc.shadowOffsetY = 2
_ctxc.shadowColor = '#43454c'
_ctxc.shadowBlur = 5
// 填充渐变色
_ctxc.fill()

// 取消阴影
_ctxc.shadowOffsetX = 0
_ctxc.shadowOffsetY = 0
_ctxc.shadowColor = '#ffffff'
_ctxc.shadowBlur = 0
// 绘制中间的文案
_ctxc.font = `normal normal bold 36px sans-serif`
_ctxc.fillStyle = '#ffffff'
_ctxc.setTextBaseline('middle')
_ctxc.setTextAlign('center')
_ctxc.fillText(`立即打卡`, SHARE_TO_CHAT_CANVAS_WIDTH / 2, btn_h + r)
// 绘制
_ctxc.draw()

现在的代码在模拟器上可以完美的绘制出我想要的结果,如下图

file

但是在真机上不行。我们可以看到在开发工具上是有阴影效果的,但是在真机上的效果却是没有阴影的。如下图

file

所以我要做的就是逐步排查,要依次确定下面几个问题的答案:

问题一:看一下官方文档给出的代码在真机上能不能够显示出来阴影。

官方文档给出的代码如下:

const ctx = wx.createCanvasContext('myCanvas')
ctx.setFillStyle('red')
ctx.setShadow(10, 50, 50, 'blue')
ctx.fillRect(10, 10, 150, 75)
ctx.draw()

我们直接测试一下在编辑器和手机上的效果,编辑器上的效果如下:

file

模拟器上是可以显示效果的,真机上的效果如下:

file

也是可以显示的,这说明在真机上的确是可以绘制出来阴影的。

但是我同时也注意到,这个实例的代码使用的是老的API。

file

所以我再次尝试把老的API替换成新的API进行测试

代码修改如下:


// ...省略部分代码
const ctx = wx.createCanvasContext('share-to-chat')
ctx.setFillStyle('red')
// ctx.setShadow(10, 50, 50, 'blue')
// 使用新的API
ctx.shadowOffsetX = 10
ctx.shadowOffsetY = 50
ctx.shadowColor = 'blue'
ctx.shadowBlur = 50

ctx.fillRect(10, 10, 150, 75)
ctx.draw()

这次修改之后,在编辑器上绘图没有发生什么变化。但是,在真机上却显示不出来了,只有一个红色的矩形,如下图所示:

file

所以到这里我们可以知道,使用新的绘制阴影的API在我这台真机上是绘制不出来阴影的。那我们就先使用老的API进行阴影的绘制,到这里这个问题已经有了一点进展了,我们继续进行排查。

问题二:是否能够绘制出来圆角的阴影

我们上面的测试绘制的阴影是矩形的阴影,如果我们绘制的是一个圆弧呢?还能绘制出来阴影吗?我们接着向下走。这次我们先绘制一个圆形,然后再给这个圆添加一个阴影。那么代码如下:

// ...省略部分代码
const ctx = wx.createCanvasContext('share-to-chat')
ctx.arc(100, 75, 50, 0, 2 * Math.PI)
ctx.setFillStyle('red')
ctx.setShadow(10, 50, 50, 'blue')
// ctx.shadowOffsetX = 10
// ctx.shadowOffsetY = 50
// ctx.shadowColor = 'blue'
// ctx.shadowBlur = 50
ctx.fill()
ctx.draw()

这次在编辑器上的效果如下:

file

在手机上的效果如下:

file

也是可以的,那么按道理来说,如果我们使用老版本绘制阴影的API的话,我们绘制的这个圆形按钮也应该能够显示出来阴影。来我们来试一试吧。

修改最初的代码如下:


// ...省略部分代码    
_ctxc.setFillStyle(grd)
// 设置阴影
// _ctxc.shadowOffsetX = 2
// _ctxc.shadowOffsetY = 2
// _ctxc.shadowColor = '#43454c'
// _ctxc.shadowBlur = 5
// 使用老的API
_ctxc.setShadow(2, 2, 5, '#43454c')
// 填充渐变色
_ctxc.fill()
// ...省略部分代码

在编辑器上还是可以正常绘制阴影的,但是在手机上还是不能够显示阴影。

那么问题出现在哪里呢?这时候就要找能够绘制出来阴影和不能够绘制出来阴影这两种情况我们改变了什么。

问题3:在绘制渐变色背景按钮的同时是否可以绘制阴影

想到这里,我就猜想会不会是因为我绘制的圆形按钮使用的是渐变色背景的原因,导致我们没有办法绘制出来阴影呢?还是要实践一下,继续修改代码。这次我不使用渐变色背景,而改为使用纯色背景来测试一下。修改代码如下:

// ...省略部分代码
// 添加渐变色
// const grd = _ctxc.createLinearGradient((SHARE_TO_CHAT_CANVAS_WIDTH - w) / 2 - r, btn_h, (SHARE_TO_CHAT_CANVAS_WIDTH + w) / 2 + r, btn_h)
// grd.addColorStop(0, first_color)
// grd.addColorStop(1, second_color)
// _ctxc.setFillStyle(grd)

// 使用纯色背景
_ctxc.setFillStyle('#4DAEFE')

// 设置阴影
// _ctxc.shadowOffsetX = 2
// _ctxc.shadowOffsetY = 2
// _ctxc.shadowColor = '#43454c'
// _ctxc.shadowBlur = 5
// 使用老的API
_ctxc.setShadow(2, 2, 5, '#43454c')
// 填充渐变色
_ctxc.fill()
// ...省略部分代码

这次在编辑器的显示如下图:

file

是有阴影的,那么在手机上效果如何呢?我们看一下:

file

很棒,这次终于在真机上也绘制出来的阴影了;距离我们成功也不远啦。

那么我们接下来要解决的问题就是,如何能够把阴影和渐变色背景都添加上去,这一次我们鱼和熊掌都想得到。

那怎么办呢,办法总比问题多,我们只需要在绘制好阴影的基础上再绘制一次渐变的背景就可以了。

就是先绘制一个纯色的带有阴影的按钮,然后在同样的位置我们再次绘制一个有渐变背景色的按钮,最后绘制文字就好啦。真是机智如我呀。最终的代码如下:

// ...省略部分代码
// 开始绘制
_ctxc.beginPath()
// 绘制左边圆弧
_ctxc.arc((SHARE_TO_CHAT_CANVAS_WIDTH - w) / 2, btn_h + r, r, 0.5 * Math.PI, 1.5 * Math.PI)
// 绘制右边圆弧
_ctxc.arc((SHARE_TO_CHAT_CANVAS_WIDTH + w) / 2, btn_h + r, r, -0.5 * Math.PI, 0.5 * Math.PI)
// 绘制中间的渐变矩形
_ctxc.closePath()
_ctxc.setFillStyle('#000')

// 设置阴影
// 新版本添加阴影
_ctxc.shadowOffsetX = 5
_ctxc.shadowOffsetY = 5
_ctxc.shadowColor = '#a5a6a7'
_ctxc.shadowBlur = 5
// 旧版本添加阴影
_ctxc.setShadow(5, 5, 5, '#a5a6a7')

_ctxc.fill()

// 重置取消阴影
// 新版本重置阴影
_ctxc.shadowOffsetX = 0
_ctxc.shadowOffsetY = 0
_ctxc.shadowColor = '#ffffff'
_ctxc.shadowBlur = 0
// 旧版本重置阴影
_ctxc.setShadow(0, 0, 0, '#ffffff')

// 绘制渐变色
_ctxc.beginPath()
_ctxc.arc((SHARE_TO_CHAT_CANVAS_WIDTH - w) / 2, btn_h + r, r, 0.5 * Math.PI, 1.5 * Math.PI)
// 绘制右边圆弧
_ctxc.arc((SHARE_TO_CHAT_CANVAS_WIDTH + w) / 2, btn_h + r, r, -0.5 * Math.PI, 0.5 * Math.PI)
// 关闭路径
_ctxc.closePath()
const grd = _ctxc.createLinearGradient((SHARE_TO_CHAT_CANVAS_WIDTH - w) / 2 - r, btn_h, (SHARE_TO_CHAT_CANVAS_WIDTH + w) / 2 + r, btn_h)
grd.addColorStop(0, first_color)
grd.addColorStop(1, second_color)
_ctxc.setFillStyle(grd)
_ctxc.fill()

// 立即打卡的文案字体大小
_ctxc.font = `normal normal bold 40px sans-serif`
_ctxc.fillStyle = '#ffffff'
_ctxc.setTextBaseline('middle')
_ctxc.setTextAlign('center')
_ctxc.fillText(`立即打卡`, SHARE_TO_CHAT_CANVAS_WIDTH / 2, btn_h + r)

// 绘制
_ctxc.draw()

// ...省略部分代码

让我们来看一下在手机上的效果吧:

file

这一次终于完美地解决了这个问题,开心。

还有,一些同学可能注意到,我上面的代码在添加阴影的时候新版本和老版本的API我都使用了,主要是因为我能够测试的手机比较少,不知道在别的品牌的手机上表现如何。所以为了保险起见,我两种方法都使用了。

还有一点需要注意的是,我本次实践的环境是:基础调试库 2.3.0手机型号 iPhone 8

最终这个带阴影的有渐变背景色的按钮就完成啦。

第三步:总结与思考

关于小程序绘图这次遇到的问题我们可以总结一下:遇到在编辑器和真机表现不一样的情况,我们首先应该先上网查一下看看小伙伴们有没有遇到类似的问题,如果人家已经有比较好的解决方案,我们就可以参考别人的解决方案去解决我们自己遇到的问题,这样会比较省时省力。如果没有的话,我们就需要自己去逐步排查问题,不断地缩小出现问题的范围,最终定位出出现问题的地方;然后再去想办法解决。

其实,如果想达到这样的效果,我们不一定非得通过代码绘制去实现这么一个功能;也可以直接让设计师把按钮这一部分导出一张小的图片来,我们直接把这个包含阴影的按钮图片绘制在图上就好了。如果我们这个按钮是不经常发生变化的话,那么直接把包含阴影按钮的图片绘制在画布上也许是挺好的一个解决方法。所以,我们首先要考虑的是我们的代码想要达到什么样的效果,那么达到这样的效果有哪些途径,各有什么优缺点。然后我们选择一个最佳的方案去开发就可以啦。

这篇文章到这里就结束啦,希望我的分享能够帮助到一些遇到同样绘图问题的小伙伴。最后一点私心,推荐一下我们开发的这一款小程序主线程

file

一个可以帮助大家规划时间和任务,并且可以组队学习找伙伴一起打卡的小程序,如果你觉得的不错的话,记得帮我们分享一下哟。