马黑黑 发表于 2022-8-21 14:10

在canvas画布上用线条做频谱播放器

代码及解释如下:
<style>
#canv { position: absolute; cursor: pointer; }
</style>

<canvas id="canv"></canvas>

<script>
let ctx = canv.getContext('2d'), //画笔
        w = canv.width = 80, //画布宽度
        h = canv.height = 60, //画布高度
        lines = [], //线条数组
        total = 10, //线条总数
        lineWidth = (w - 2 * total) / total, //线条厚度
        aud = new Audio(); //播放器

aud.src = 'https://music.163.com/song/media/outer/url?id=16434054.mp3'; //音乐地址
aud.loop = true; //循环播放
aud.autoplay = true; //自动播放

aud.addEventListener('timeupdate', change); //监听播放进度 :驱动频谱
canv.onclick = () => aud.paused ? aud.play() : aud.pause(); //画布单击事件 :控制暂停、播放

//创建线条对象 :以便批量处理
function Line(x1,y1,x2,y2) {
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
        lines.push(this); //加入 lines 数组
}

//线条对象的绘制函数
Line.prototype.draw = function() {
        ctx.beginPath(); //开启路径
        ctx.strokeStyle = this.color; //绘制颜色
        ctx.lineWidth = lineWidth; //画笔粗细 :等于线条厚度
        ctx.moveTo(this.x1, this.y1); //移动画笔到 x1、y1位置
        ctx.lineTo(this.x2,this.y2); //绘制线条到 x2、y2
        ctx.stroke(); //上色
}

// 批量生成并绘制线条
for(let j = 0; j < total; j ++) { //按预设线条总数
        let line = new Line(); //建立Line对象实例 line
        line.x1 = j * lineWidth + j*2 + lineWidth / 2 + 1; //实例 line 的x1 :其算法令每个线条拉开一定距离
        line.y1 = h; //y1等于画布高度
        line.x2 = line.x1; //x2等于x1
        line.y2 = 50; //y2 :这个值后面会动态变化,这里是为不支持自动播放准备一个统一的高度
        line.color = '#' + Math.random().toString(16).substr(-6); //首次出现的随机颜色:后面还会变化
        line.draw(); //绘制线条
}

//线条动态变化函数
function change() {
        ctx.clearRect(0,0,canv.width,canv.height); //每次刷新清除一次画布
        for(let item of lines) { //线条数组每个元素
                item.color = '#' + Math.random().toString(16).substr(-6); //随机得到16进制颜色
                item.y2 = h - (Math.random() * h); //y2随机值 :高度减去(0至0.999...)*高度
                item.draw(); //绘制线条
        }
}
</script>

马黑黑 发表于 2022-8-21 14:13

本帖最后由 马黑黑 于 2022-8-22 21:43 编辑 <br /><br /><style>
#canv { position: absolute; cursor: pointer; }
</style>

<p>效果如下。说明:由于有些浏览器设置问题,不支持媒体的自动播放,若此,此播放器仅凭监听 timeupdate 事件驱动频谱会有问题,播放器不会出现。故此,我们需要首次运行一次函数 change() ,可以放在 JS 代码的最后一行: <br><br>change(); <br><br></p>
<canvas id="canv"></canvas>

<script>
let ctx = canv.getContext('2d'),
        w = canv.width = 80,
        h = canv.height = 60,
        lines = [],
        total = 10,
        lineWidth = (w - 2 * total) / total,
        aud = new Audio();

aud.src = 'https://music.163.com/song/media/outer/url?id=16434054.mp3'; //音乐地址
aud.loop = true;
aud.autoplay = true;
aud.addEventListener('timeupdate', change);
canv.onclick = () => aud.paused ? aud.play() : aud.pause();

function Line(x1,y1,x2,y2) {
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
        lines.push(this);
}

Line.prototype.draw = function() {
        ctx.beginPath();
        ctx.strokeStyle = this.color;
        ctx.lineWidth = lineWidth;
        ctx.moveTo(this.x1, this.y1);
        ctx.lineTo(this.x2,this.y2);
        ctx.stroke();
}

for(let j = 0; j < total; j ++) {
        let line = new Line();
        line.x1 = j * lineWidth + j*2 + lineWidth / 2 + 1;
        line.y1 = h;
        line.x2 = line.x1;
        line.y2 = 50;
        line.color = '#' + Math.random().toString(16).substr(-6);
        line.draw();
}

function change() {
        ctx.clearRect(0,0,canv.width,canv.height);
        for(let item of lines) {
                item.color = '#' + Math.random().toString(16).substr(-6);
                item.y2 = h - (Math.random() * h);
                item.draw();
        }
}

change();

</script>

马黑黑 发表于 2022-8-21 14:24

如果需要倒影,可在 #canv {} 加入:

-webkit-box-reflect: below 0 linear-gradient(transparent, transparent 50%, rgba(255,255,255,.3));

但火狐至今仍不支持 box-reflect

红影 发表于 2022-8-21 18:34

画布也能做频谱播放器,这个好{:4_187:}

马黑黑 发表于 2022-8-21 18:52

红影 发表于 2022-8-21 18:34
画布也能做频谱播放器,这个好

这个也不用担心背景问题

红影 发表于 2022-8-21 18:54

line.x1 = j * lineWidth + j*2 + lineWidth / 2 + 1; 这个没看懂,为什么是这样的算法?

马黑黑 发表于 2022-8-21 20:03

本帖最后由 马黑黑 于 2022-8-21 20:07 编辑

红影 发表于 2022-8-21 18:54
line.x1 = j * lineWidth + j*2 + lineWidth / 2 + 1; 这个没看懂,为什么是这样的算法?
这个算法,基于x1,就是画笔要画一根线条前移动到的位置,也是线条的起始{x,y}坐标值的x值。

线是横向排列的。线有厚度,所以,① 基于起点X坐标,第 j 根线必须在 j*lineWidth;② 我想每根线拉开一定距离,且头尾两根线不贴合边缘,那么,根据前面 lineWidth 的定义,即(画布宽度 - 2 * 线条总数)/线条总数 的定义,再加 j*2 + lineWidth / 2 + 1。如此,便能均匀地把 total 根线横向安置在画布里。不这样做处理,线条会挤在一起,甚至会在同一个地方重复画线条。

这是算法,有些算法是根据上下文进行具体应对的。

醉美水芙蓉 发表于 2022-8-21 20:09

马黑黑 发表于 2022-8-21 20:17

醉美水芙蓉 发表于 2022-8-21 20:09
欣赏学习黑黑老师新教材!

{:4_180:}

加林森 发表于 2022-8-22 09:18

学习学习

红影 发表于 2022-8-22 16:19

马黑黑 发表于 2022-8-21 20:03
这个算法,基于x1,就是画笔要画一根线条前移动到的位置,也是线条的起始{x,y}坐标值的x值。

线是横向 ...

嗯,怪不得线宽的取值也用了算法,而不是直接赋值。谢谢黑黑解答{:4_204:}

马黑黑 发表于 2022-8-22 17:03

红影 发表于 2022-8-22 16:19
嗯,怪不得线宽的取值也用了算法,而不是直接赋值。谢谢黑黑解答

不考虑画布尺寸的话,也可以直接赋值的

红影 发表于 2022-8-22 21:10

马黑黑 发表于 2022-8-22 17:03
不考虑画布尺寸的话,也可以直接赋值的

如果直接复制,后面的间隔的算法也要更改了吧?

马黑黑 发表于 2022-8-22 21:31

红影 发表于 2022-8-22 21:10
如果直接复制,后面的间隔的算法也要更改了吧?

一般来说总要有个算法,像x1,它是排列线条的依据,每根线条必须在前一根的右边不远处。

红影 发表于 2022-8-23 15:52

马黑黑 发表于 2022-8-22 21:31
一般来说总要有个算法,像x1,它是排列线条的依据,每根线条必须在前一根的右边不远处。

嗯嗯,否则就无法均匀排布了。

马黑黑 发表于 2022-8-23 17:24

红影 发表于 2022-8-23 15:52
嗯嗯,否则就无法均匀排布了。

是的

红影 发表于 2022-8-23 21:12

马黑黑 发表于 2022-8-23 17:24
是的

等消暑完了,我也来学做个{:4_187:}

马黑黑 发表于 2022-8-23 21:12

红影 发表于 2022-8-23 21:12
等消暑完了,我也来学做个

挺好
页: [1]
查看完整版本: 在canvas画布上用线条做频谱播放器