├── .gitattributes ├── .gitignore ├── Josefin_Sans.woff2 ├── README.md ├── av.js ├── hello.mp4 └── index.html /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /Josefin_Sans.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HTML50/audioVisualizer/b259c97b40f23b20981f5867271e423cfbdb0fc7/Josefin_Sans.woff2 -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /hello.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HTML50/audioVisualizer/b259c97b40f23b20981f5867271e423cfbdb0fc7/hello.mp4 -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello - OMFG | Audio Visualizer 4 | 5 | 6 | 284 | 285 |
loading...
286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | star me on my github 305 | @HTML50 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | --------------------------------------------------------------------------------