├── hello.mp4
├── Josefin_Sans.woff2
├── .gitattributes
├── README.md
├── .gitignore
├── index.html
└── av.js
/hello.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HTML50/audioVisualizer/HEAD/hello.mp4
--------------------------------------------------------------------------------
/Josefin_Sans.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HTML50/audioVisualizer/HEAD/Josefin_Sans.woff2
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 |
7 | # Standard to msysgit
8 | *.doc diff=astextplain
9 | *.DOC diff=astextplain
10 | *.docx diff=astextplain
11 | *.DOCX diff=astextplain
12 | *.dot diff=astextplain
13 | *.DOT diff=astextplain
14 | *.pdf diff=astextplain
15 | *.PDF diff=astextplain
16 | *.rtf diff=astextplain
17 | *.RTF diff=astextplain
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # audioVisualizer
2 |
3 | DEMO https://html50.github.io/audioVisualizer/
4 |
5 |
6 |
7 | a web version MV for the song [Hello by OMFG](https://www.youtube.com/watch?v=XRgiNUtJurk), bulit this for fun.
8 |
9 | I use canvas to draw the frequency spectrum, css3 animation for the OMFG.
10 |
11 |
12 |
13 | # how i build this
14 |
15 | if you are interested in how I build this, you can see this article [build a audio visualizer with html5 audio api](http://blog.csdn.net/twoByte/article/details/62043425) from my blog.
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Windows image file caches
2 | Thumbs.db
3 | ehthumbs.db
4 |
5 | # Folder config file
6 | Desktop.ini
7 |
8 | # Recycle Bin used on file shares
9 | $RECYCLE.BIN/
10 |
11 | # Windows Installer files
12 | *.cab
13 | *.msi
14 | *.msm
15 | *.msp
16 |
17 | # Windows shortcuts
18 | *.lnk
19 |
20 | # =========================
21 | # Operating System Files
22 | # =========================
23 |
24 | # OSX
25 | # =========================
26 |
27 | .DS_Store
28 | .AppleDouble
29 | .LSOverride
30 |
31 | # Thumbnails
32 | ._*
33 |
34 | # Files that might appear in the root of a volume
35 | .DocumentRevisions-V100
36 | .fseventsd
37 | .Spotlight-V100
38 | .TemporaryItems
39 | .Trashes
40 | .VolumeIcon.icns
41 |
42 | # Directories potentially created on remote AFP share
43 | .AppleDB
44 | .AppleDesktop
45 | Network Trash Folder
46 | Temporary Items
47 | .apdisk
48 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Hello - OMFG | Audio Visualizer
4 |
5 |
6 |
284 |
285 | loading...
286 |
287 |
288 |
289 |
290 |
291 | O
292 | M
293 | F
294 | G
295 |
296 |
297 |
298 | OH
299 | MY
300 | FUCKING
301 | GOD
302 |
303 |
304 | star me on my github
305 | @HTML50
306 |
307 | replay
308 | github
309 |
310 |
311 |
312 |
313 |
314 |
--------------------------------------------------------------------------------
/av.js:
--------------------------------------------------------------------------------
1 | var AudioContext,context,source,analyser,p,penBg,dataArray,gradient,gradientRight;
2 |
3 | //检测分辨率,获得缩放比例
4 | var screenHeight = document.body.clientHeight,
5 | screenWidth = document.body.clientWidth;
6 |
7 | //Canvas初始化
8 | var width = canvas.width,
9 | height = canvas.height;
10 |
11 | var audio = new Audio("hello.mp4");
12 |
13 | audio.oncanplaythrough = function() {
14 | if(screenWidth!=width || screenHeight!=height){
15 | zoomPage();
16 | loader.innerHTML = ''
17 | }
18 |
19 |
20 | document.addEventListener('click',function(){
21 | init();
22 | loader.style.display = 'none'
23 | audio.play();
24 | fontCSSAnimation();
25 | document.removeEventListener('click',arguments.callee);
26 | })
27 | };
28 |
29 |
30 | function init(){
31 | AudioContext = AudioContext || webkitAudioContext;
32 | context = new AudioContext;
33 | //加载媒体
34 |
35 | //创建节点
36 | source = context.createMediaElementSource(audio);
37 | analyser = context.createAnalyser();
38 | //连接:source → analyser → destination
39 | source.connect(analyser);
40 | analyser.connect(context.destination);
41 |
42 |
43 | p = canvas.getContext("2d");
44 | penBg = bg.getContext("2d");
45 |
46 | analyser.fftSize = 4096;
47 | var length = analyser.fftSize;
48 | //创建数据
49 | dataArray = new Uint8Array(length);
50 |
51 |
52 | gradient = p.createLinearGradient(0, 100, 480, 100);
53 | gradient.addColorStop("0", "#f500d8");
54 | gradient.addColorStop("1.0", "#ceaf11");
55 |
56 | //右边的填充渐变色
57 | gradientRight = p.createLinearGradient(886, 100, 1366, 100);
58 | gradientRight.addColorStop("0", "#0ee7f7");
59 | gradientRight.addColorStop("1.0", "#2ce672");
60 | }
61 |
62 | //左边的填充渐变色
63 |
64 |
65 | function zoomPage(){
66 | var scaleX = (screenWidth/width).toPrecision(5),
67 | scaleY = (screenHeight/height).toPrecision(5);
68 |
69 | var style = document.createElement("style");
70 | document.head.appendChild(style);
71 | sheet = style.sheet;
72 | sheet.insertRule('body{transform-origin:0% 0%;transform:scale('+scaleX+','+scaleY+');}', 0);
73 | console.log('执行了zoom操作:',scaleX,scaleY)
74 | }
75 |
76 |
77 |
78 | function fontCSSAnimation() {
79 | o.style.opacity = 1;
80 | o.classList.add('fly');
81 |
82 | setTimeout(function() {
83 | oval.style.opacity = 1;
84 | }, 200);
85 |
86 | setTimeout(function() {
87 | oval.classList.add('strech');
88 | }, 1000);
89 |
90 | //O G左右滚动
91 | setTimeout(function() {
92 | o.classList.add('rollLeft')
93 | g.style.opacity = 1;
94 | g.classList.add('rollRight')
95 | }, 1000);
96 |
97 | //OM出现
98 | setTimeout(function() {
99 | m.classList.remove('hidden');
100 | f.classList.remove('hidden');
101 | }, 1200);
102 |
103 | //字符跳跃动画 ,O
104 | setTimeout(function() {
105 | o.style.left = '470px';
106 | o.classList.add('textBounce');
107 | oh.classList.remove('hidden');
108 | oh.classList.add('show');
109 | }, 1800);
110 |
111 | //字符跳跃动画 ,M
112 | setTimeout(function() {
113 | m.style.left = '585px';
114 | m.classList.add('textBounce');
115 | my.classList.remove('hidden');
116 | my.classList.add('show');
117 | }, 2100);
118 |
119 | //字符跳跃动画 ,F
120 | setTimeout(function() {
121 | f.style.left = '715px';
122 | f.classList.add('textBounce');
123 | fucking.classList.remove('hidden');
124 | fucking.classList.add('show');
125 | }, 2400);
126 |
127 | //字符跳跃动画 ,G
128 | setTimeout(function() {
129 | g.style.left = '795px';
130 | g.classList.add('textBounce');
131 | god.classList.remove('hidden');
132 | god.classList.add('show');
133 | }, 2700);
134 |
135 | //频谱动画开始
136 | setTimeout(function() {
137 | group.classList.add('hidden');
138 | setTimeout(draw, 300);
139 | }, 4000);
140 |
141 |
142 | //中场的文字左右飞入
143 | setTimeout(function() {
144 | leftFlyin.classList.add('flyFromLeft');
145 | rightFlyin.classList.remove('none');
146 | rightFlyin.classList.add('flyFromRight');
147 | }, 60000);
148 |
149 | setTimeout(function() {
150 | leftFlyin.classList.remove('flyFromLeft');
151 | rightFlyin.classList.remove('flyFromRight');
152 | leftFlyin.innerText='follw me on my github';
153 | }, 69000);
154 |
155 | setTimeout(function() {
156 | leftFlyin.classList.add('flyFromLeft');
157 | rightFlyin.classList.add('flyFromRight');
158 | }, 70000);
159 |
160 | setTimeout(function() {
161 | leftFlyin.classList.remove('flyFromLeft');
162 | rightFlyin.classList.remove('flyFromRight');
163 | leftFlyin.innerText='star me on my github';
164 | }, 149000);
165 |
166 | setTimeout(function() {
167 | leftFlyin.classList.add('flyFromLeft');
168 | rightFlyin.classList.add('flyFromRight');
169 | }, 150000);
170 |
171 | setTimeout(function() {
172 | leftFlyin.classList.remove('flyFromLeft');
173 | rightFlyin.classList.remove('flyFromRight');
174 | leftFlyin.innerText='follw me on my github';
175 | }, 159000);
176 |
177 | setTimeout(function() {
178 | leftFlyin.classList.add('flyFromLeft');
179 | rightFlyin.classList.add('flyFromRight');
180 | }, 160000);
181 |
182 | //音乐结束,OMFG收缩
183 | setTimeout(function() {
184 | o.classList.add('zoomToZero');
185 | oval.classList.add('zoomToZero');
186 | }, 228000);
187 | setTimeout(function() {
188 | m.classList.add('zoomToZero');
189 | }, 228500);
190 | setTimeout(function() {
191 | f.classList.add('zoomToZero');
192 | }, 229000);
193 | setTimeout(function() {
194 | g.classList.add('zoomToZero');
195 | canvas.style.opacity=0;
196 | }, 229500);
197 |
198 | //背景变黑,重播
199 | setTimeout(function() {
200 | document.body.style.backgroundColor = '#000';
201 | bg.style.opacity=0;
202 | }, 230000);
203 |
204 | setTimeout(function() {
205 | document.body.style.backgroundColor = '#fff';
206 | replay.style.display ='block';
207 | github.style.display ='block';
208 | replay.classList.remove('hidden');
209 | github.classList.remove('hidden');
210 | }, 235000);
211 |
212 | //辐射层透明,这样才能显示后面的频谱
213 | penBg.globalAlpha = 0.2;
214 | }
215 |
216 | function draw() {
217 | requestAnimationFrame(draw)
218 | analyser.getByteFrequencyData(dataArray);
219 | p.clearRect(0, 0, width, height);
220 |
221 | //放射性背景
222 | var gradientBg = penBg.createRadialGradient(width / 2, height / 2, height - Math.floor(Math.random() * 150 + 100), width / 2, height / 2, width / 2);
223 | gradientBg.addColorStop(0, "white");
224 | gradientBg.addColorStop(1, '#000');
225 | penBg.clearRect(0, 0, width, height);
226 | penBg.fillStyle = gradientBg;
227 | penBg.fillRect(0, 0, width, height);
228 | //左
229 |
230 | //左填充
231 | p.beginPath();
232 | p.moveTo(0, height - 200);
233 | var x = 0;
234 | for (var i = 1; i < 42; i++) {
235 | var lineHeight = dataArray[i] / 256 * height / 3;
236 | if (i < 5) {
237 | p.lineTo(x, height - dataArray[i] / 256 * height / 2 - 200)
238 | } else if (i > 40) {
239 | p.lineTo(x - 13, height - 200)
240 | } else {
241 | p.lineTo(x, height - lineHeight - 200)
242 | }
243 | x += 12;
244 | }
245 | p.fillStyle = gradient;
246 | p.fill();
247 | p.closePath();
248 |
249 |
250 |
251 |
252 | //左线条
253 | p.beginPath();
254 | p.moveTo(0, height - 200);
255 | var x = 0;
256 | for (var i = 1; i < 42; i++) {
257 | var lineHeight = dataArray[i] / 256 * height / 3;
258 | if (i < 5) {
259 | p.lineTo(x, height - dataArray[i] / 256 * height / 2 - 210 - Math.floor(Math.random() * 30))
260 | } else if (i > 40) {
261 | p.lineTo(x - 13, height - 220)
262 | } else {
263 | p.lineTo(x, height - lineHeight - 210 - Math.floor(Math.random() * 30))
264 | }
265 | x += 12;
266 | }
267 | p.strokeStyle = gradient;
268 | p.stroke();
269 | p.closePath();
270 |
271 |
272 | //清除左侧底部部分频谱
273 | p.fillStyle = '#fff';
274 | p.fillRect(0, height - 300, 470, 101);
275 |
276 | //左倒影
277 | p.beginPath();
278 | p.moveTo(0, height - 299);
279 | var x = 0;
280 | for (var i = 1; i < 41; i++) {
281 | var lineHeight = dataArray[i] / 256 * height / 50;
282 | if (i < 5) {
283 | p.lineTo(x, dataArray[i] / 256 * height / 24 + 380)
284 | } else p.lineTo(x, lineHeight + 380)
285 | x += 12;
286 | }
287 | p.lineTo(x - 12, height - 299)
288 | p.fillStyle = '#21dd13';
289 |
290 | p.shadowBlur = 20;
291 | p.shadowColor = "#21dd13";
292 | p.fill();
293 | p.closePath();
294 | p.shadowBlur = 0;
295 |
296 |
297 |
298 | //右
299 |
300 | //右填充
301 | p.beginPath();
302 | p.fillStyle = gradientRight;
303 | p.moveTo(width, height - 200);
304 | var x = width;
305 | for (var i = 1; i < 42; i++) {
306 | var lineHeight = dataArray[i] / 256 * height / 3;
307 | if (i < 5) {
308 | p.lineTo(x, height - dataArray[i] / 256 * height / 2 - 200)
309 | } else if (i > 40) {
310 | p.lineTo(x + 12, height - 200)
311 | } else {
312 | p.lineTo(x, height - lineHeight - 200)
313 | }
314 | x -= 12;
315 | }
316 | p.fill();
317 | p.closePath();
318 |
319 | //右线条
320 | p.beginPath();
321 | p.moveTo(width, height - 200);
322 | var x = width;
323 | for (var i = 1; i < 42; i++) {
324 | var lineHeight = dataArray[i] / 256 * height / 3;
325 | if (i < 5) {
326 | p.lineTo(x, height - dataArray[i] / 256 * height / 2 - 210 - Math.floor(Math.random() * 30))
327 | } else if (i > 40) {
328 | p.lineTo(x + 12, height - 200)
329 | } else {
330 | p.lineTo(x, height - lineHeight - 210 - Math.floor(Math.random() * 30))
331 | }
332 | x -= 12;
333 | }
334 | p.strokeStyle = gradientRight;
335 | p.stroke();
336 | p.closePath();
337 |
338 | //清除右侧底部部分频谱
339 | p.fillStyle = '#fff';
340 | p.fillRect(width - 480, height - 300, 480, 100);
341 |
342 |
343 | //右倒影
344 | p.beginPath();
345 | p.moveTo(width, height - 299);
346 | var x = width;
347 | for (var i = 1; i < 41; i++) {
348 | var lineHeight = dataArray[i] / 256 * height / 50;
349 | if (i < 5) {
350 | p.lineTo(x, dataArray[i] / 256 * height / 24 + 380)
351 | } else p.lineTo(x, lineHeight + 380)
352 | x -= 12;
353 | }
354 | p.lineTo(x + 12, height - 299)
355 | p.fillStyle = '#21dd13';
356 |
357 | p.shadowBlur = 20;
358 | p.shadowColor = "#21dd13";
359 | p.fill();
360 | p.closePath();
361 | p.shadowBlur = 0;
362 |
363 | }
--------------------------------------------------------------------------------