使用高斯模糊的效果逐步加載圖片(仿 Medium)
用過 Medium 的用戶不會不記得它的圖片加載方式——純色-高斯模糊-加載完成并顯示。
這是一種很優雅的圖片預加載的方式(因為 Medium 的圖片質量都很高,如果全部一下加載的話,需要的時間難以想象,所以,這是一種很棒的做法)。從***次打開 Medium 這個網站開始,我就被這種技術給吸引住了——好吧,直到今天才去研究它。
在 Medium 網站,打開任何一篇文章,然后,我們來 inspect 一下:
- <figure name="512a"
- id="512a"
- class="graf--figure graf--layoutCroppedHeightPreview graf-after--h3"
- >
- <div class="aspectRatioPlaceholder is-locked">
- <div class="aspectRatioPlaceholder-fill"
- style="padding-bottom: 30%;"
- ></div>
- <div class="progressiveMedia js-progressiveMedia graf-image is-canvasLoaded"
- data-image-id="1*dZnfeZiXxf2BgN3VSQuOlA.jpeg"
- data-width="3600"
- data-height="3600"
- data-scroll="native"
- >
- <img src="https://cdn-images-1.medium.com/freeze/fit/t/60/18/1*dZnfeZiXxf2BgN3VSQuOlA.jpeg?q=20"
- crossorigin="anonymous"
- class="progressiveMedia-thumbnail js-progressiveMedia-thumbnail"
- >
- <canvas class="progressiveMedia-canvas js-progressiveMedia-canvas"
- width="75"
- height="22"
- ></canvas>
- <img class="progressiveMedia-image js-progressiveMedia-image"
- data-src="https://cdn-images-1.medium.com/fit/t/1600/480/1*dZnfeZiXxf2BgN3VSQuOlA.jpeg"
- src="https://cdn-images-1.medium.com/fit/t/1600/480/1*dZnfeZiXxf2BgN3VSQuOlA.jpeg"
- >
- <noscript class="js-progressiveMedia-inner">
- <img class="progressiveMedia-noscript js-progressiveMedia-inner" src="https://cdn-images-1.medium.com/fit/t/1600/480/1*dZnfeZiXxf2BgN3VSQuOlA.jpeg">
- </noscript>
- </div>
- </div>
- </figure>
可以看到,Medium 為每一張圖片都設置了這么長的一段 HTML。這樣做的目的就是為了讓這個圖片的逐步加載過程能夠平滑如一,同時還能在一定程度上提升用戶體驗。就算圖片沒有加載出來,顯示給用戶的是一個高斯模糊的圖片,其實也不失美感。
那么,這個圖片的逐步加載過程具體是什么樣的呢?
- 渲染一個 div 容器,這個容器就是用來顯示最終展示給用戶的圖片的。通過對容器設置一個百分比的 padding-bottom 來讓其比例和大小與最終圖片的比例和大小相同,這樣,就能避免圖片加載出來的時候導致的頁面的重排;
- 使用 img 標簽來加載一張原圖質量的 10% ~ 20% 左右的圖片,這張圖片的質量很低,而且很小,所以可以馬上加載出來;
- 一旦小圖加載完成,就開始使用 canvas 來進行繪制,添加模糊效果,同時,開始請求最終要加載的大圖;
- 當最終的大圖也加載完成之后,顯示大圖,隱藏掉 canvas。
以上就是 Medium 的做法。
我們可以自己來實現這個效果,實現過程如下:
- 渲染一個容器,保持與原圖的比例和尺寸相同,填充一個較淺的背景色;
- 先加載小圖,同時使用模糊效果;
- 小圖加載完成,開始請求大圖;
- 大圖加載完成,顯示大圖,隱藏小圖。
所以,綜合來看,其實并不復雜。
首先,我們可以把大圖和小圖的 URL 和尺寸都存起來,通過標簽的 data 屬性去動態獲取。所以,我們的 HTML 可以像下面這樣寫:
- <figure name="blur"
- class="blur-img-container"
- data-real-width="1174"
- data-real-height="670"
- data-src="images/sm2.jpeg"
- src="https://cdn-images-1.medium.com/max/2000/1*0WwtDkE1q6HGZwD6Kn9SuQ.jpeg"
- ></figure>
其中各個參數代表的含義是:
- data-real-width: 大圖的寬度
- data-real-height: 大圖的高度
- data-src: 小圖的 URL
- src: 大圖的 URL
同時,我們需要定義一些 CSS 的 class 來對大圖和小圖進行處理:
- .blur-img-container {
- position: relative;
- background: #eeeeee;
- background-size: cover;
- overflow: hidden;
- }
- .blur-img-container img {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- opacity: 0;
- transition: all 0.4s ease-in-out;
- }
- .blur-img-container .thumb-loaded {
- opacity: 1;
- filter: blur(10px);
- transform: scale(1);
- }
- .blur-img-container .large-loaded {
- opacity: 1;
- }
- .blur-img-container .thumb-hidden {
- opacity: 0;
- }
然后,我們的重點在于 JavaScript 的處理。
- 需要動態的設置每個圖片的容器的 padding-bottom 以防止頁面發生重排;
- 通過 image 的 onload 事件來控制其樣式和進度
***點,動態設置我們的容器的 padding-bottom。可以通過計算寬高比然后換算成百分比:
- elem.style.paddingBottom = `${(realHeight / realWidth) * 100}%`;
第二點,使用圖像的 onload 事件來控制加載的進度:
- let thumb = new Image();
- thumb.src = thumbSrc;
- thumb.onload = () => {
- // 小圖加載完成,顯示小圖,設置樣式
- setStyle(thumb, 'thumb-loaded');
- };
- elem.appendChild(thumb);
- let realImg = new Image();
- realImg.src = lgSrc;
- realImg.onload = () => {
- // 大圖加載完成,顯示大圖,隱藏小圖
- setStyle(realImg, 'large-loaded');
- setStyle(thumb, 'thumb-hidden');
- };
- // 將大圖添加到頁面中
- elem.appendChild(realImg);
其實,只要把上面兩點主要的功能做好了,我們的這個效果基本上就實現了。
可以通過我的 GitHub Repo 來查看完整的源代碼和例子 blur-image。
同時,我將這個小功能封裝成了一個 package,需要的朋友可以通過 npm install blur-image 或者 bower install blur-image 進行安裝和使用。具體的安裝和使用方法可以查看文檔。