不知道大家有沒有看這段時間最火的一部電影《復仇者聯盟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 外掛(外掛可自行到官網下載,官網地址: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上都只有一些小點而且在向不同的方向擴散,所以感覺上就像沙化了。
四、注意事項
如果元素中有圖片的話,需要使用伺服器的方式載入,不能使用本地瀏覽器直接開啟,否則包含圖片的元素無法沙化。
想要了解更多前端方面的內容可以關注我的微信公眾號[前端隊長],我們一同成長,一同領略技術與生活“落霞與孤鶩齊飛,秋水共長天一色”的美好。
關注公眾號,後臺回覆“滅霸”獲取原始碼和素材。