naseeihity / LearnToLearn

MIT License
28 stars 8 forks source link

Progressive Image Loading #46

Open naseeihity opened 6 years ago

naseeihity commented 6 years ago

类似Medium网站中的图片是我见过的最优雅的图片加载方式,记得第一次见到这种用模糊图片做placeholder的图片加载方式是在一个国外大神的博客里,当时就被惊艳到了,这种看起来很有逼格的交互其实实现起来并不复杂,这里就记录一下我从零开始一步步实现图片渐进式加载和懒加载的过程。

Intrinsic Placeholders——百分比Padding的妙用

在开始实现Progressive Image Loading之前,我们需要解决我们在加载图片是经常会遇见的一个问题:

一个优雅的解决这个问题的方案是Intrinsic Placeholders,其实现就是依靠将padding设置成百分比值。我们知道,当padding设置为百分比时,其值是相对于父元素的宽度的,假设我们的图片是一个宽高比为3:2的图片,我们希望他的的容器在图片加载出来之前能够正确地以3:2的比例占据那个位置,这时我们只需要设置一个子容器(可以用伪元素实现)的padding值为66.6%就可以了。

    .placeholder{
        display: block;
        margin: 0 auto 100px;
        width: 75%;
        border: 1px solid #f6f6f6;
        overflow: hidden;
            background-color: #E0CEB8;
            position: relative;
    }
    .placeholder:after {
           content: '';
           display: block;
           margin-bottom: 66.6%;
    }
    .placeholder img{
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
    }
要把图片放进来,我们只需要对容器进行相对定位,对图片绝对定位,设置图片宽度为100%就可以了。[See a Demo](http://codepen.io/anon/pen/PzVGrz)

实现模糊效果的的渐进加载

在实现了图片占位符后,我们就要去实现图片的渐进式加载,这里我们需要准备一大一小两张图片,然后对HTML代码做一点点小的改动。

<picture data-large="http://7xr09v.com1.z0.glb.clouddn.com/lion-wild-africa-african.jpg" class="placeholder">
    <img src="http://7xr09v.com1.z0.glb.clouddn.com/lion-wild-africa-african.jpg?imageView2/2/w/30/h/20/interlace/0/q/100" alt="" class="img_small">
</picture>

我们用img标签去直接加载小图片,并将大图片的地址存储在自定义属性data-large,方便以后js调用。实现的思路是先加载显示小图片,在大图完全加载完成后再将其添加进picture中覆盖小图,两个图片在加载过程中我们都让其完全透明,在加载完成后再逐渐显现出来,实现起来都很简单。唯一值得一提的是模糊效果的实现,有两个可选的方案,使用CSS3的blur属性或者使用canvas,第一种方法明显更加简单。下面是这一部分实现的主要代码:

    .placeholder img{
         position: absolute;
         opacity: 0;
         top: 0;
         left: 0;
         width: 100%;
         transition: opacity 1s linear; // 是模糊的渐变效果更平滑
    }

    .placeholder img.loaded{
        opacity: 1;
    }

    .img {
       -webkit-filter:blur(50px);
        filter: blur(50px); //设置50px的模糊
       transform: scale(1); //解决Safari 上的锯齿问题
    }

        const placeholder = document.querySelector('.placeholder'),
              small = placeholder.querySelector('.img_small');

        let ImgSmall = (function(){

            let smallImg = new Image();
            smallImg.src = small.src;
            //因为无法监听页面上的html元素的加载,需要将new一个smallImg
            smallImg.onload = function () {
             small.classList.add('loaded');
            };
            smallImg.addEventListener('load', () =&gt; smallImg.classList.add('loaded'));
        })();

        let imgLarge = new Image();
        imgLarge.src = placeholder.dataset.large;
        placeholder.appendChild(imgLarge);
        imgLarge.addEventListener('load', () =&gt; imgLarge.classList.add('loaded'));

filter属性可以支持Chrome18+,FireFox35+,Webkit6.0+所以,如果你想获得更好的兼容性,可以尝试用canvas实现,你可以在这里找到实现的方法。

Tips: 使用七牛云存储你可以直接获得不同大小的图片地址,例如我的小图就是来自于?imageView2/2/w/30/h/20/interlace/0/q/100这段带代码后缀,由于模糊效果的关系,我们所需要的小图比想象中小的多,我这里取了宽30px的图,质量未经压缩的情况下大小只有4kb

实现图片的Lazyloading 延迟加载

实际上,很多时候我们并不需要将页面中的图片全部加载出来,在打开网页时加载所有图片会使页面变得很卡。更好的办法是在用户想要看到那些图片时再去加载他们,这样即加快了页面的加载速度,又节省了流量,也能获得更好的用户体验,所以lazyloading是一项非常实用的技术。

要实现lazyloading的方法有很多,最原始的办法是监听scroll事件,判断`$(window).scrollTop() &gt;= $element.offset().top`(Jquery写法)来判断元素开始进入viewport,用原生Js写起来就更加麻烦了;或者你也可以用`getBoundingClientRect`方法来获取元素在viewport中的位置,在判断他进入viewport后设置img的src属性,这种方法虽然简单了不少,但判断还是很长而且性能并不好。这里我使用了Chrome51+开始支持的IntersectionObserver方法,来监视元素是否进入了了viewport,是的,他目前只支持Chrome51及以上的版本,但是其优雅便捷性实在是让人心动,而且你也可以用[polyfill](https://github.com/WICG/IntersectionObserver/tree/gh-pages/polyfill)去实现对其他浏览器的兼容,只是会损伤写性能。

`IntersectionObserver`的用法非常简单:
 const element = document.querySelector('.Observed');  // 拿到要被监视的元素

    let io = new IntersectionObserver((entries) =&gt; {
        //do something when the element come into/out the viewport  
    },option) // 实例化一个IntersectionObserver对象

    io.observe(element); //监听多个element的位置
    io.observe(element1); 
    io.observe(element1); 

参考资料

  1. How Medium does progressive image loading
  2. intrinsic-placeholders
  3. 使用 IntersectionObserver 和 registerElement 打造 Lazyload