用过 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 进行安装和使用。具体的安装和使用方法可以查看文档。