前言 继写个福字送给新年的自己 后,为了让新春氛围更加浓厚,必不可少的就是烟花和 BGM 了,这篇文章我们就利用 p5.js 实现一个根据音乐来绽放烟花,为新春增色添彩~
效果预览
什么是 p5.js
官网:p5.js 是个 JavaScript 创意编程程式库,其专注在让编程更易于使用及更加广泛的包容艺术家、设计师、教育家、初学者以及任何其他人!p5.js 是个免费及开源的软件因为我们相信所有人都应该能自由使用软件及用于学习软件的工具。p5.js 使用绘图的比喻并有一副完整的绘画功能。除此之外,您也不单限于您的绘图画布。您可以将您整个浏览器页面当作您的绘图,这包括了 HTML5 物件如文字、输入框、视屏、摄像头及音频。
简单来说,p5.js 可以利用 canvas 实现许许多多炫酷的浏览器效果,以及在浏览器中进行绘图。
实现 首先我们先引用 p5 的 cdn,因为我们要利用音频相关功能,所以再引用它的扩展包 p5.sound
1 2 <script src ="https://cdn.bootcdn.net/ajax/libs/p5.js/1.4.0/p5.min.js" > </script > <script src ="https://cdn.bootcdn.net/ajax/libs/p5.js/1.4.0/addons/p5.sound.min.js" > </script >
接下来我们首先实现烟花的部分,然后再将音乐与烟花结合起来
创建画布 p5 的写法与一般情况不同,js 中直接定义 setup 函数即可实现初始化,draw 函数则用来对画布进行绘制:
1 2 3 4 5 6 function setup ( ) { createCanvas (window .innerWidth , window .innerHeight ) }function draw ( ) { background (0 , 0 , 20 ) }
在 vue 中如果想要定义 setup 函数则需要使用另一种声明方式:
1 2 3 4 5 6 7 8 const s = (sketch ) => { sketch.setup = () => { createCanvas (window .innerWidth , window .innerHeight ) } sketch.draw = () => { background (0 , 0 , 20 ) } }
在本文中我们直接在 html 中编写代码所以采用第一种方式。
创建烟花 这里我们模拟的烟花由一个中心点和围绕在周围的随机点构成。 具体代码作用和含义在注释中标注
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 function Fireworks (radius ) { var num = 64 var cp = new p5.Vector ( random (0 , width), random (height / 2 , height / 5 ), random (-100 , 100 ) ) var velocity = new p5.Vector (0 , -15 , 0 ) var accel = new p5.Vector (0 , 0.4 , 0 ) var img var fp = [] var cosTheta var sinTheta var phi var colorChange = random (0 , 5 ) for (var i = 0 ; i < num; i++) { cosTheta = random (0 , 1 ) * 2 - 1 sinTheta = sqrt (1 - cosTheta * cosTheta) phi = random (0 , 1 ) * 2 * PI fp[i] = new p5.Vector ( radius * sinTheta * cos (phi), radius * sinTheta * sin (phi), radius * cosTheta ) fp[i] = p5.Vector .mult (fp[i], 1.01 ) } var side = 64 var center = side / 2.0 img = createImage (side, side) img.loadPixels () const r = random (0.5 , 0.8 ) const g = random (0.5 , 0.8 ) const b = random (0.5 , 0.8 ) for (var y = 0 ; y < side; y++) { for (var x = 0 ; x < side; x++) { var distance = (sq (center - x) + sq (center - y)) / 10.0 var r = int ((255 * r) / distance) var g = int ((255 * g) / distance) var b = int ((255 * b) / distance) img.set (y, x, color (r, g, b)) } } img.updatePixels () this .display = function ( ) { for (var i = 0 ; i < num; i++) { push () translate (cp.x , cp.y , cp.z ) translate (fp[i].x , fp[i].y , fp[i].z ) blendMode (ADD ) image (img, 0 , 0 ) pop () fp[i] = p5.Vector .mult (fp[i], 1.015 ) } } this .update = function ( ) { radius = dist ( 0 , 0 , 0 , fp[0 ].x , fp[0 ].y , fp[0 ].z ) centerPosition.add (velocity) velocity.add (accel) } this .needRemove = function ( ) { if (centerPosition.y - radius > height) { return true } else { return false } } }
现在基本的烟花类已经被定义好了,我们可以给画布绑定一个键盘事件,并修改 draw 函数中相关逻辑来看一下效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function keyPressed ( ) { fireworks.push (new Fireworks (20 )) }function draw ( ) { background (0 , 0 , 20 ) for (var i = 0 ; i < fireworks.length ; i++) { fireworks[i].display () fireworks[i].update () if (fireworks[i].needRemove ()) { fireworks.splice (i, 1 ) } } }
相关 API
这里将上述用到的相关 api 和类 单列出来方便查看
API
描述
createCanvas
创建画布
background
设置背景色
p5.Vector
创建包含幅度和方向的新 p5 向量
random
返回在作为参数给出的范围之间的随机浮点数
sqrt
获取任何输入数字的平方根
createImage
创建图片
translate
指定在显示窗口内放置对象的数量 x 参数用于指定左/右平移 y 参数用于指定上/下平移
blendMode
根据给定的混合模式混合两个像素
dist
测量计算在 2D 或 3D 中两点之间的距离
获取音频参数 首先我们需要添加一个audio
标签来存放我们的音乐
1 <audio src ="./1.mp3" autoplay controls > 您的浏览器不支持 audio 标签。</audio >
接着改造我们的 setup 函数,让它能够获取到我们的音频资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var fireworks = []var highMap = [2 , 5 , 10 , 15 ]const ORI_NUM = 32 function setup ( ) { createCanvas (window .innerWidth , window .innerHeight ) frameRate (60 ) imageMode (CENTER ) audioContext_ = getAudioContext () let el = document .querySelector ('audio' ) const source = audioContext_.createMediaElementSource (el) source.connect (p5.soundOut ) fft = new p5.FFT (0.8 , ORI_NUM ) fft.setInput (source) }
这里我们获取了音频资源的频谱,未来用来控制我们烟花的大小以及高度。当然 p5.sound 还可以获取音频的音量等等,以实现更炫酷的可视化效果。
接下来我们改造 draw 绘制函数,解析本地音频的频谱并利用它来搞事情
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 function draw ( ) { audioContext_.resume () background (0 , 0 , 20 ) let spectrum = fft.analyze () spectrum.forEach ((item, j ) => { if (item !== 0 && j % 2 ) { let a = highMap[0 ] if (item >= 64 && item < 128 ) { a = highMap[1 ] } else if (item >= 128 && item < 192 ) { a = highMap[2 ] } else { a = highMap[3 ] } const fire = new Fireworks (item, a) fireworks.push (fire) if (fireworks.length > 64 ) { fireworks.shift () } } }) for (var i = 0 ; i < fireworks.length ; i++) { fireworks[i].display () fireworks[i].update () if (fireworks[i].needRemove ()) { fireworks.splice (i, 1 ) } } }
注意audioContext_.resume()
是必须要添加的,因为谷歌浏览器默认不会自动播放音频。
spectrum 就是我们分析得到的频谱数值组成的数组,ORI_NUM 作为频谱的个数,最少需要 32 个。我们理论上可以放更多的烟花,但是由于本人电脑性能一般,所以直接取 16 个来放烟花。
代码中我们可以看到,Fireworks 类我们传了两个参数:频谱的值和我们定义的高度值,现在就可以对烟花类改造,以实现根据音频来控制烟花的爆炸半径和发射高度。
1 2 3 4 5 6 7 8 9 10 11 12 13 function Fireworks (radius, h ) { var num = 64 var cp = new p5.Vector ( random (0 , width), random (height / 2 , height / 5 ), random (-100 , 100 ) ) var velocity = new p5.Vector (0 , -h, 0 ) var accel = new p5.Vector (0 , 0.4 , 0 ) …… }
为了效果更真实,我们优化一下烟花的随机颜色,让它更亮
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 if (colorChange >= 3.5 ) { img = makeColor (0.9 , random (0.2 , 0.5 ), random (0.2 , 0.5 )) } else if (colorChange > 3.2 ) { img = makeColor (random (0.2 , 0.5 ), 0.9 , random (0.2 , 0.5 )) } else if (colorChange > 2 ) { img = makeColor (random (0.2 , 0.5 ), random (0.2 , 0.5 ), 0.9 ) } else { img = makeColor (random (0.5 , 0.8 ), random (0.5 , 0.8 ), random (0.5 , 0.8 )) }function makeColor (rr, gg, bb ) { var side = 64 var center = side / 2.0 var img = createImage (side, side) img.loadPixels () for (var y = 0 ; y < side; y++) { for (var x = 0 ; x < side; x++) { var distance = (sq (center - x) + sq (center - y)) / 10.0 var r = int ((255 * rr) / distance) var g = int ((255 * gg) / distance) var b = int ((255 * bb) / distance) img.set (y, x, color (r, g, b)) } } img.updatePixels () return img }
效果就如文章开头那样绚烂多彩。
最后 至此音乐烟花就实现了,而 p5.js 也是我不经意间发现的一个库,发现用它可以做出各种强大的动画。本以为我会是第一个用 p5,js 来实现效果的人,而就在我把这个 demo 写出来的第二天,活动的第一天,就读到了北京哥的一场烟花盛宴 🎇,祝你新年快乐 🎉 ,实现思路不同,效果我觉得北京哥的也更美观真实~,心痛哈哈哈哈做不了第一那做个……第n也不错。