十分鐘實現滅霸打響指灰飛煙滅的效果
看過復仇者聯盟的都知道,滅霸作為計劃生育政策的堅定支持者和執行者,一個響指清除了宇宙中二分之一的生命。電影中被清除的生命灰飛煙滅的鏡頭很是酷炫,所以在復聯4上映後,那個不存在的網站google,推出了一個彩蛋,如果在搜尋框搜尋滅霸,會出現一個手套的按鈕,點選後會讓網頁搜尋結果消失一半。

恩~這很谷歌。
效果雖然酷炫但其實並不複雜,這裡帶著大家一起來實現一下,網上也已經有了一些教程( Thanos Snap Effect JavaScript Tutorial),這裡稍微加點東西,滅霸的響指畢竟是真的打而不是用滑鼠點按鈕,所以我會加上音量檢測,當檢測到響指的聲音(其實是超過某個音量預值)就觸發效果,然後就可以愉快的向朋友裝逼了。
原始碼地址
準備開始
我們這裡只用一個圖片元素,HTML結構如下
<body> <div class="content"> <div id="image"> <!-- 圖片為網路地址才可在本地通過直接開啟html除錯 --> <img src="https://i.loli.net/2019/05/06/5ccffa469ec52.jpg" width="400" /> </div> </div> </body> 複製程式碼
動畫的實現
- 將html轉成canvas,為後面處理做準備。這裡我們使用html2canvas庫,可以將html中的dom節點繪製成canvas,相當於對這個dom節點進行截圖,使用很簡單,程式碼如下(網頁生成海報圖片就可以用這個庫來做)。
const imageBox = document.querySelector('#image') html2canvas(imageBox, { backgroundColor: 'transparent' //背景設定為透明 }).then(canvas=>{ //處理canvas的程式碼(注意.then這種寫法只有在新版本的html2canvas可用) }); 複製程式碼
- 這裡我們拿到了包含了圖片資訊的canvas後,要通過canvas的getImageData來獲取到canvas畫布上的每個畫素的資訊。
//處理canvas的程式碼 const ctx = canvas.getContext('2d'); const imageData = ctx.getImageData( 0, 0, canvas.width, canvas.height ); const pixelArr = imageData.data; //畫素資訊 複製程式碼
影象資訊被儲存在一個Uint8ClampedArray(8位無符號整型固定陣列)中,這個陣列中的值為0到255的整數,按圖片中畫素從左到右從上到下的順序,每4個數字表示一個畫素資訊,這4個數字分別表示rgba(r-紅色,g-綠,b-藍色,a-透明度)的四個值。 比如這樣一個圖片

它的畫素資訊即為
[0,0,0,255,255,255,255,255,255,0,0,255,0,255,0,255] 複製程式碼
- 接下來是最關鍵的一步,建立多個同等長度的陣列,填充上透明畫素資訊(陣列中的值全是0即可),然後將之前拿到的影象畫素資訊“隨機”分佈到這些陣列中,這些陣列就包含了原影象的部分內容,原教程裡面用到了一個隨機數生成的庫chance來控制隨機數出現概率,直接使用完全隨機數也沒有問題。
//建立一個和影象資訊陣列長度相同的陣列並填充0(相當於一個和原影象尺寸相同的透明影象) const data = pixelArr.slice(0).fill(0); //建立透明影象陣列的個數,不能太小也不能太大。 const canvasCount = 30; //將透明影象陣列複製多個 const imageDataArray = Array.from({ length: canvasCount }, () => data.slice(0) ); //將原影象上的畫素資訊隨機分配進不同的透明圖象上,位置保持不變 for (let i = 0; i < pixelArr.length; i += 4) { const p = Math.floor((i / pixelArr.length) * canvasCount); //a為隨機選出要放入畫素資訊的陣列 const a = imageDataArray[Math.floor(Math.random() * canvasCount]; //將畫素資訊放入隨機到的透明影象陣列中覆蓋 a[i] = pixelArr[i]; a[i + 1] = pixelArr[i + 1]; a[i + 2] = pixelArr[i + 2]; a[i + 3] = pixelArr[i + 3]; } 複製程式碼
當canvasCount為3時效果如下:
原始圖片canvas

生成的包含原圖部分畫素的3個canvas



canvasCount越大,生成的canvas越多,分配到每個canvas上面的畫素就越少,飄的就越散。
4. 接下來就非常簡單了,隱藏掉原始影象,為生成的canvas新增飄散動畫就可以了,飄散動畫主要組成就是高斯模糊,位移,旋轉,透明度變化,具體程式碼這裡就不寫了,可以在demo原始碼中看到,最終效果如下。

響指觸發
灰飛煙滅的動畫已經完成,接下來是如何觸發這段動畫,文章開始就說過谷歌搜尋上的原始效果是通過點選按鈕觸發,而我們通過麥克風實時檢測輸入音量,當打響指時(音量達到一定大小)觸發動畫。
- 首先要確保你的裝置有麥克風等聲音輸入裝置,然後監測並獲取裝置,如果有麥克風裝置,網頁上會彈出授權提示。
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { // 獲取使用者的 media 資訊 navigator.mediaDevices .getUserMedia({ audio: true }) .then(stream => { //音訊處理程式碼 }) .catch(error => { mystatus.innerHTML = '獲取音訊時好像出了點問題。' + error; }); } else { mystatus.innerHTML = '不支援獲取媒體介面'; } 複製程式碼
- 音訊處理程式碼
// 當輸入音量超過此值時,表示檢測大音量輸入(響指聲) const TRIGGER_VALUE = 0.9; // 將麥克風的聲音輸入這個物件 mediaStreamSource = audioContext.createMediaStreamSource( stream ); // 建立一個音訊分析物件,取樣的緩衝區大小為4096,輸入和輸出都是單聲道 scriptProcessor = audioContext.createScriptProcessor( 4096, 1, 1 ); // 將該分析物件與麥克風音訊進行連線 mediaStreamSource.connect(scriptProcessor); // 此舉無甚效果,僅僅是因為解決 Chrome 自身的 bug scriptProcessor.connect(audioContext.destination); // 開始處理音訊 scriptProcessor.onaudioprocess = function(e) { // 獲得緩衝區的輸入音訊,轉換為包含了PCM通道資料的32位浮點陣列 let buffer = e.inputBuffer.getChannelData(0); // 獲取緩衝區中最大的音量值 let maxVal = Math.max.apply(Math, buffer); // 顯示音量值 if (maxVal > TRIGGER_VALUE) { //灰飛煙滅動畫 start(); } }; 複製程式碼