likes
comments
collection
share

切图仔,老板说不好看,再改一版呗

作者站长头像
站长
· 阅读数 28

背景

某天忽然被产品大佬ding了一下,发来一张和老板的对话图,说是那个曲线不够平滑,和App端比差点意思,能再优化吗。

赶紧打开小程序,定睛细看,嗯,确实有点点锯齿,于是查询了各种canvas画曲线抗锯齿的方法,迅速优化了一版。主要是把连点的方式从lineTo换成了arcTo,改完效果自我感觉不错,但是产品大佬还是不满意,和我说电脑上放大还是有锯齿。

原谅切图仔的眼睛不是尺,不放大根本看不到锯齿,所以老板就是单纯的不喜欢曲线是了吧。

下次感觉不喜欢就直说,请不要在细节上把俺PUA一遍,再说不喜欢。

期望

所以老板想要换个方式展示音频强度,找来了类似的小程序效果,发来了图。 切图仔,老板说不好看,再改一版呗

要从中心开始向两边类似正态分布的样子震动。而且要足够敏感,不说话的时候不动,说话的时候动的很明显。图给到,其他自由发挥。

任务拆解

依然是canvas,但是这种其实比曲线要简单多了,就是画一批圆角的渐变色矩形。仔细数了一下大概 65 个左右。主要的难点在如何根据音频强度值去动态生成正态分布的 65 个不同高度的圆角矩形。再加上一点点细节。所以具体要实现的如下:

1、实现动态的渐变色

2、实现动态圆角矩形

3、生成65个矩形的高度,大概根据正态分布排列

4、过滤掉音频强度很小的值,以实现不说话时不动

5、放大信号,使得音频强度足够敏感

coding

实现动态的渐变色

canvas实现渐变色的 Api 为 createLinearGradient(x0, y0, x1, y1) 和 addColorStop(start, color) 我们画从上到下填充渐变色的矩形,那么实际的样子是 切图仔,老板说不好看,再改一版呗

封装方法:


// x,y-填充图形的起点 height填充图形的高度 我们画从上到下的矩形所以 x 轴保持不变,y 轴变化

function getColor(x, y, height) {

    let color = ctx.createLinearGradient(x, y - height, x, y);

    // 添加颜色 颜色这里写死了 有需要可以提为参数

    color.addColorStop(0, "#30E4E6");

    color.addColorStop(1, "#30DA79");

    return color

}

由于我们的矩形都是从canvas底部向上变化的所以我们的起点是(x,y-height),终点是(x,y)

实现动态圆角矩形

动态圆角矩形这个网上和各个模型都有代码参考直接借来用用:


// x,y-圆角矩形的起点 width-宽 height-高 radius-圆角半径

// ctx 闭包画板

function roundRect(x, y, width, height, radius) {

    ctx.beginPath();

    ctx.moveTo(x + radius, y);

    ctx.lineTo(x + width - radius, y);

    ctx.quadraticCurveTo(x + width, y, x + width, y + radius);

    ctx.lineTo(x + width, y + height - radius);

    ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);

    ctx.lineTo(x + radius, y + height);

    ctx.quadraticCurveTo(x, y + height, x, y + height - radius);

    ctx.lineTo(x, y + radius);

    ctx.quadraticCurveTo(x, y, x + radius, y);

    ctx.closePath();

    // 这里需要填色 上面是画圆角矩形

    ctx.fill();

}

过滤掉音频强度很小的值,以实现不说话时不动

然后,进行低值过滤:

let source = audioCtx.createBufferSource()

source.buffer = buffer

source.connect(analyser)

source.start()

let n = new Uint8Array(analyser.frequencyBinCount)

analyser.getByteTimeDomainData(n)

let i = 0, r = 0, s = 0

r = Math.max.apply(null, n)

s = Math.min.apply(null, n)

i = (r - s) / 128

// 拿到音频值

i = Math.round(i * 100 / 2)

// 屏蔽低音杂音信号 这里发现ios可能杂音会大一点 安卓似乎少一点

if (i < (isIos ? 5 : 2)) {

i = 0

}

放大信号,使得音频强度足够敏感

比较简单,参考同上,拿到音频强度,进行信号放大:


// 放大音频信号 这里放大了 40 倍

i *= 40

生成65个矩形的高度,大概根据正态分布排列

这个需要从中间开始生成,中间是音频强度最高的地方,然后向两边递减,越靠近中心减的越慢,越靠近两边减的越狠就行,同时我们再加上一些随机性使得咱们的柱子看起来参差不齐,代码如下:


// 构造柱状序列 中间最大 两边逐步递减到 0 其中i为音频强度

const count = 65

let nn = new Array(count).fill(0)

for (let j = 0; j < count / 2; j++) {

    let left = ~~(count / 2 - j)

    let right = ~~(count / 2 + j)

    nn[left] = i - (j * ~~(Math.random() * (j / 2) + 1))

    if (nn[left] < 0) {

        nn[left] = 0

    }

    nn[right] = i - (j * ~~(Math.random() * (j / 2) + 1))

    if (nn[right] < 0) {

        nn[right] = 0

    }

}

最后画图

我们根据上一步生成的高度序列来画 65 个圆角渐变色即可,直接上代码,逻辑思路请参考注释:


let ctx = this.data.ctx

let c = this.data.canvas

draw();

function draw() {

    // 每次清空画板

    ctx.clearRect(0, 0, c.width, c.height);

    // 将canvas宽度评分为 count=65 块,再把每块分为两半,一半还矩形,一半是间隔 得到每个矩形的宽度

    // 取 ceil 以防曲线不能填满canvas区域

    let stepWidth = Math.ceil(c.width / count / 2)

    for (let k = 0; k < count; k++) {
        // 设置音频柱最低高度 *2 简单的放大信号 可以微调 保证每个矩形高度至少为2%的canvas高度,这里将信号进一步放大了 调试结果 也可以不放大 看实际情况 怎么好看怎么来
        let j = nn[k] * 2 + (~~(c.height * 0.02))

        // 防止顶到顶部
        if (j >= c.height) {
            j = c.height - 10
        }

        // 设置填充色
        ctx.fillStyle = getColor(k * stepWidth * 2, c.height, j);

        // 画圆角矩形
        roundRect(k * stepWidth * 2, c.height - j - 10, stepWidth, j, 3)
    }

    // 纵享丝滑
    requestAnimationFrame(draw);

}

总结

音频强度画柱状图对于canvs来说比较没有难度,主要是想好柱状图如何生成,网上也有其他思路,这里就不列了,照例放出代码片段,请享用:

音频强度柱状图

转载自:https://juejin.cn/post/7362506905674596367
评论
请登录