彈指間,網頁灰飛煙滅——Google滅霸彩蛋實現
不知道大家有沒有看這段時間最火的一部電影《復仇者聯盟4:終局之戰》,作為漫威迷的我還沒看,為什麽呢?因為太貴了,剛上映的那周,一張IMAX廳的票價已經達到了299的天價,作為搬磚民工是舍不得花這麽高的錢來看一場電影的,太奢侈了,當然也可能我是個假漫威迷吧,哈哈哈哈逃~
我剛看下現在的票價,IMAX廳是89元,已經接近正常,雖然還是些許偏高,但是已經可以接受了。對於看電影,我並不是那麽崇尚看首映,或者非要第一時間看到,但是對於喜歡的電影我一定會找個最佳的位置觀看,現在票價合理,最佳觀影區充足,正是看電影的好時機。從這方面看,我可能只算是一個漫威愛好者,而絕不是一個狂熱者。
而今天要說的主題自然和漫威有關,是一個Google的小彩蛋。想必大家已經知道了,在Google中搜索“滅霸”,然後在右側點擊的“無限手套”,頁面的一些搜索項就會隨機性像沙子一樣的消失(後面統稱沙化效果),特別的炫酷。有不知道的可以看下面的動圖:
我覺得特別有意思,就參考了一些文章,實現了類似上面的沙化效果。
首先我制作了一個模板如下,點擊按鈕後,列表隨機沙化(手套的效果是很多圖片的合成,這裏就不處理了)。
模板代碼如下:
<div class="box"> <div class="bomb">啪嗒!</div> <ul> <li class="item"> <h3>襟三江而帶五湖,控蠻荊而引甌越。</h3> </li> <li class="item"> <h3>潦水盡而寒潭清,煙光凝而暮山紫。</h3> </li> <li class="item"> <h3>落霞與孤鶩齊飛,秋水共長天一色。</h3> </li> <li class="item"> <img src="./1.jpg" /> </li> </ul> </div>
樣式就不貼了,後面會給出源碼。
然後我們一步步說明如何實現沙化效果。
首先,我們將每一個li元素的沙化封裝成一個函數 disintegrate ,這個函數參數就是要沙化的目標元素,這裏是li元素。
一、實現原理
簡單來說就是將頁面的元素先轉化為canvas,然後提取出所有的像素點分別按照規律排布在32個canvas上面,,再將這些canvas轉換為和原始元素大小一樣的dom元素堆疊在一起,看起來就和原始元素一樣的,然後將原始元素隱藏。最後將這些堆疊在一起的元素散開,就形成“沙化”的效果。
二、實現步驟
首先引入 html2canvas 插件。
由於需要將頁面的元素轉換成 canvas 圖像,所以要用到 html2canvas 插件(插件可自行到官網下載,官網地址:https://html2canvas.hertzen.com/)。
<script src="./html2canvas.js"></script>
接著將元素轉化為32個canvas。
創建32個canvas(當然,個數越多,沙子就越細),把元素的每一個像素復制到這32個canvas上面,這個每個canvas上面都會有一部分元素的像素點,加起來就是整個元素所有的像素點。(具體的代碼作用,參考每句代碼的註釋)
html2canvas(ele).then(dom => {
const { width, height } = dom; // canvas寬高
let ctx = dom.getContext('2d'); // canvas繪圖對象
// 返回一個ImageData對象,用來描述canvas區域隱含的像素數據,這個區域通過矩形表示,起始點為(sx, sy)、寬為sw、高為sh。
let originalFrame = ctx.getImageData(0, 0, width, height);
// 創建一個32個新的、空白的、指定大小的 ImageData 對象。 所有的像素在新對象中都是透明的。
let frames = [];
for (let i = 0; i < COUNT; i++) {
frames[i] = ctx.createImageData(width, height);
}
// 將canvas所有的數據隨機復制到32個frames上面
for (x = 0; x < width; ++x) {
for (y = 0; y < height; ++y) {
// frames 的下表索引值。
// 不是一般的(從0到COUNT的)隨機值,而是遞增的隨機數,為了將像素點先集中在前幾個frame,然後再往後集中,否則32個frames鐘的像素太分散。
var frameIndex = Math.floor((COUNT * (Math.random() + (2 * x) / width)) / 3);
// imageData.data:描述一個一維數組,包含以 RGBA 順序的數據,數據使用 0 至 255(包含)的整數表示。
// 數組的個數為 width*height*4,所以除了寬乘高以外還要乘以4
var pixelIndex = 4 * (y * width + x);
// 之所以要循環4次是因為上面乘了4,得到的 pixelIndex 在 width*height*4 範圍內會有一些空缺,所以要補上這些空缺,保證所有的canvas像素全部復制到32個frames上面
for (offset = 0; offset < 4; offset++) {
frames[frameIndex].data[pixelIndex + offset] = originalFrame.data[pixelIndex + offset];
}
}
}
});
然後將這32個分布了不同像素點的 ImageData 對象轉換成原始li元素大小的dom元素,用一個容器container來容納,然後將容器覆蓋到原始li元素的位置,現在就相當於每個li元素的位置是一個container元素,這個container元素內容是32個dom元素,這32個dom重疊起來的樣子和原始li元素是一樣的。
// 創建一個div容納frames
let container = document.createElement('div');
container.classList.add('container');
container.style.width = `${width}px`;
container.style.height = `${height}px`;
// 將所有包含RGBA數據的frames繪制到繪圖中,生成32份和原始dom一樣的元素,只是內容不同,最後將這些元素放入container中。
let frames2doms = frames.map((frameData, i) => {
let domCopy = dom.cloneNode(true);
domCopy.getContext('2d').putImageData(frameData, 0, 0); // 將數據從已有的 ImageData 對象繪制到位圖的方法。
domCopy.style.transitionDelay = `${(1.35 * i) / frames.length}s`; //過渡效果開始前的delay時間(可自行調整),使得frames先從下標小的開始運動。
container.appendChild(domCopy);
return domCopy;
});
現在我們看到的效果和原始的是一樣的,但是所有的li元素被隱藏了起來,顯示的是由許許多多零散的元素拼湊出來的假象。目前所有的零散元素是聚集在一起的,我們只需要有規律的讓他們動起來,動到一定位置後再讓它們不可見,感覺就像沙化的效果一般。
// 讓所有的canvas動起來
// 原始dom相對定位,container絕對定位
ele.classList.add('disintegrated');
ele.appendChild(container);
ele.style.border = '0';
container.offsetLeft; // 沒有該句,則無法實現動畫效果
// 為32份不同內容的dom元素添加過渡效果(可自行調整)
frames2doms.map(item => {
let random = 2 * Math.PI * (Math.random() - 0.5);
item.style.transform = `
rotate(${15 * (Math.random() - 0.5)}deg)
translate(${60 * Math.cos(random)}px, ${30 * Math.sin(random)}px)
rotate(${-15 * (Math.random() - 0.5)}deg)
`;
item.style.opacity = 0;
});
三、實驗效果
點擊按鈕之後,每個li元素位置的32個dom旋轉跳躍並閉上眼,哦不是逐漸消失。因為每個dom上都只有一些小點而且在向不同的方向擴散,所以感覺上就像沙化了。
四、註意事項
如果元素中有圖片的話,需要使用服務器的方式加載,不能使用本地瀏覽器直接打開,否則包含圖片的元素無法沙化。
想要了解更多前端方面的內容可以關註我的微信公眾號[前端隊長],我們一同成長,一同領略技術與生活“落霞與孤鶩齊飛,秋水共長天一色”的美好。
關註公眾號,後臺回復“滅霸”獲取源碼和素材。
參考鏈接:https://www.weibo.com/1727858283/HrxYFq0fG?type=comment
彈指間,網頁灰飛煙滅——Google滅霸彩蛋實現