zoukankan      html  css  js  c++  java
  • 图片优化

    前面的话

      本文将详细介绍前端项目中的图片相关的优化方案

    图片格式

      目前在前端的开发中常用的图片格式有jpg、png、gif,png8、png24、png32、svg和webp

    【gif】

      gif是无损的,具有文件小、支持动画及透明的优点。但gif无法支持半透明,且仅支持8bit的索引色,即在整个图片中,只能存在256种不同的颜色

      但实际上,gif是一种逐渐被抛弃的图片格式。png格式的出现就是为了替代它

      由于gif支持动画的这个“一招鲜”的本领,在网络中仍然占有一席之地,主要用于一些小图标

    【jpg】

      jpg又称为jpeg,是有损的,但采用了直接色,保证了色彩的丰富性。jpg图片支持透明和半透明,所有空白区域填充白色

      jpg格式主要用于高清图、摄影图等大图

    【png8】

      png8是无损的,是png的索引色版本

      前面提到过,png是gif格式的替代者,在相同图片效果下,png8具有更小的文件体积,且支持透明度的调节

      但png8不支持半透明,也不支持动画

    png

    【png24】

      png24是无损的,是png的直接色版本。 

      png24支持透明,也支持半透明,但png有文件体积较大的缺点

      png24的目标是替换jpg。但一般而言,png24的文件大小是jpg的5倍之多,但显示效果却只有一点点提升

    【png32】

      png32是在png24的基础上,添加了8位的alpha通道信息,可以支持透明和半透明,且支持图层,辅助线等复杂数据的保存

      使用ps导出的透明的png24图片,实际上是阉割版的png32,因为只有32位的png图片才支持透明,阉割版是说导出的图片不支持图层

    【SVG】

      svg是无损的矢量图。svg与上面这些图片格式最大的不同是,上面的图片格式都是位图,而svg是矢量图,具有无论如何缩放都不会失真的优点

      svg格式非常适用于绘制logo、图标等  

      但由于低版本浏览器支持不足,应用不广泛

    【webp】

      WebP 格式是 Google 于2010年发布的一种支持有损压缩和无损压缩的图片文件格式,派生自图像编码格式 VP8。它具有较优的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量,同时具备了无损和有损的压缩模式、Alpha 透明以及动画的特性,在 JPEG 和 PNG 上的转化效果都非常优秀、稳定和统一。目前,知名网站 Youtube 、Facebook、Ebay 等均有使用 WebP格式。

      WebP 集合了多种图片文件格式的特点,JPEG 适合压缩照片和其他细节丰富的图片,GIF 可以显示动态图片,PNG 支持透明图像,图片色彩非常丰富,而 WebP 则兼具上述优点,且较于它们还有更出色的地方。

      无损压缩后的 WebP 比 PNG 文件少了 45% 的文件大小,即使 PNG 文件经过其他压缩工具压缩后,WebP 还是可以减少 28% 的文件大小。此外,与 JPEG 相比,在质量相同的情况下,WebP 格式图像的体积要比 JPEG 格式图像小 40%,而 WebP 在压缩方面比 JPEG 格式更优越

      但目前为止,webp只能在安卓系统下使用

    PS保存

      一般地,在对设计图进行修改前,首先要保留一份psd源文本,然后再在其副本上进行修改

      通过photoshop将设计图切成需要的素材时,涉及到图片格式的设置问题,应注意以下几点:

      1、当图片色彩丰富且无透明要求时,建议保存为jpg格式并选择合适的品质,一般为60-80

      2、当图片色彩不太丰富时无论有无透明要求,保存为PNG8格式(特点是只有256种颜色,文件本身比较小),保存时选择无仿色,无杂边

      3、当图片有半透明要求时,保存PNG24格式(对图片不进行压缩,所有相对比较大)

    懒加载

      图片延迟加载也称为懒加载,延迟加载图片或符合某些条件时才加载某些图片,通常用于图片比较多的网页。可以减少请求数或者延迟请求数,优化性能

    【呈现形式】

      一般而言,有以下三种呈现形式

      1、延时加载,使用setTimeout或setInterval进行加载延迟,如果用户在加载前就离开,自然就不会进行加载

      2、条件加载,符合某些条件或者触发了某些条件才开始异步加载

      3、可视区域加载,仅仅加载用户可以看到的区域,这个主要监控滚动条实现,一般距离用户看到的底边很近的时候开始加载,这样能保证用户下拉时图片正好接上,不会有太长时间的停顿

    【基本步骤】

      1、待加载的图片默认加载一张占位图

      2、使用data-src属性保存真正地址

      3、当触发某些条件时,自动改变该区域的图片的src属性为真实的地址

    【可视区域加载】

      可视区域加载,是图片懒加载最常用的一种形式,涉及到的细节如下所示:

      1、判断可视区域

      图片顶部距离页面顶部的高度小于页面高度

      2、保存图片路径

      待加载的图片默认加载一张占位图,使用data-src属性保存真正的地址

      3、判断加载时机

      监听页面的scroll事件,收集当前进入页面的图片元素,给src赋值为真正的地址,给已加载的图片添加标记

      4、滚动性能提升

      使用函数节流优化滚动性能

      代码如下所示:

    const oList = document.getElementById('list')
    const viewHeight = oList.clientHeight
    const eles = document.querySelectorAll('img[data-src]')
    const lazyLoad = () => {
      Array.prototype.forEach.call(eles, item => {
        const rect = item.getBoundingClientRect()
        if (rect.top <= viewHeight && !item.isLoaded) {
          item.isLoaded = true
          const oImg = new Image()
          oImg.onload = () => { item.src = oImg.src }
          oImg.src = item.getAttribute('data-src')
        }
      })
    }
    const throttle = (fn, wait=100) =>{
      return function() {
        if(fn.timer) return
        fn.timer = setTimeout(() => {
          fn.apply(this, arguments)
          fn.timer = null
        }, wait)
      }
    }
    lazyLoad()
    oList.addEventListener('scroll', throttle(lazyLoad))

      效果如下

    懒加载进阶

      上面代码的问题在于,每次调用getBoundingClientRect()方法时,都会触发回流,严重地影响性能

      可以使用Intersection Observer这一API来解决问题,可以异步观察目标元素与祖先元素或顶层文件的交集变化

      创建一个 IntersectionObserver对象并传入相应参数和回调用函数,该回调函数将会在target 元素和root的交集大小超过threshold规定的大小时候被执行

    var options = {
        root: document.querySelector('#scrollArea'), 
        rootMargin: '0px', 
        threshold: 1.0
    }
    var callback = function(entries, observer) { 
        /* Content excerpted, show below */ 
    };
    var observer = new IntersectionObserver(callback, options);

      如果root参数指定为null或者不指定的时候默认使用浏览器视口做为root

      rootMargin表示root元素的外边距。该属性值是用作root元素和target发生交集时的计算交集的区域范围,使用该属性可以控制root元素每一边的收缩或者扩张。默认值为0

      threshold可以是单一的number也可以是number数组,target元素和root元素相交程度达到该值的时候,将会被执行

      如果只是想要探测当target元素的在root元素中的可见性超过50%的时候,可以指定该属性值为0.5。如果想要target元素在root元素的可见程度每多25%就执行一次回调,那么可以指定一个数组[0, 0.25, 0.5, 0.75, 1]。默认值是0(意味着只要有一个target像素出现在root元素中,回调函数将会被执行)。该值为1.0含义是当target完全出现在root元素时回调才会被执行

      为每个观察者配置一个目标

    var target = document.querySelector('#listItem')
    observer.observe(target)

      当目标满足该IntersectionObserver指定的threshold值,回调被调用

    var callback = function(entries, observer) { 
        entries.forEach(entry => {
            entry.time;             
            entry.rootBounds;       
            entry.boundingClientRect;
            entry.intersectionRect;   
            entry.intersectionRatio;  
            entry.target;           
        });
    };

      time: 可见性发生变化的时间,是一个高精度时间戳,单位为毫秒

      intersectionRatio: 目标元素的可见比例,即 intersectionRect 占 boundingClientRect 的比例,完全可见时为 1 ,完全不可见时小于等于 0

      boundingClientRect: 目标元素的矩形区域的信息

      intersectionRect: 目标元素与视口(或根元素)的交叉区域的信息

      rootBounds: 根元素的矩形区域的信息,getBoundingClientRect() 方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回 null

      isIntersecting: 是否进入了视口,boolean 值

      target: 被观察的目标元素,是一个 DOM 节点对象

      代码如下所示:

    const eles = document.querySelectorAll('img[data-src]')
    const observer = new IntersectionObserver( entries => {
      entries.forEach(entry => {
        if (entry.intersectionRatio > 0) {
          let oImg = entry.target
          oImg.src = oImg.getAttribute('data-src')
          observer.unobserve(oImg)
        }
      })
    }, {
      root: document.getElementById('list')
    })
    eles.forEach(item => { observer.observe(item) })

    预加载

      预加载图片是提升用户体验的一个好办法,提前加载用户所需的图片,保证图片快速、无缝发布,使用户在浏览器网站时获得更好用户体验。常用于图片画廊等应用中

    【使用场景】

      以下几个场景中,可以使用图片预加载

      1、在首屏加载之前,缩短白屏时间

      2、在空闲时间为SPA的下一屏预加载

      3、预测用户操作,预先加载数据

    【三种思路】

      一般来说,实现预加载有三种思路:

      1、使用页面无用元素的背景图片来进行图片预加载

    <button>载入图片</button>
    <img src="img/test.png" alt="测试">
    <ul class="list">
        <li id="preload1"></li>
        <li id="preload2"></li>
        <li id="preload3"></li>
        <li id="preload4"></li>
    </ul>
    <script>
    var oBtn = document.getElementsByTagName('button')[0];
    var oImg0 = document.images[0];
    var array = ["img/img1.gif","img/img2.gif","img/img3.gif","img/img4.gif"]
    var iNow = -1;
    oBtn.onclick = function(){
        iNow++;
        iNow = iNow%4;
        oImg0.src = array[iNow];
    }
    function preLoadImg(){
        preload1.style.background = "url('img/img1.gif')";
        preload2.style.background = "url('img/img2.gif')";
        preload3.style.background = "url('img/img3.gif')";
        preload4.style.background = "url('img/img4.gif')";
    }
    window.onload = function(){
        preLoadImg();    
    }
    </script>

      2、通过new Image()或document.createElement('img')创建img标签,然后通过img的src属性来加载图片

    <button>载入图片</button>
    <img src="img/test.png" alt="测试">
    <script>
    var oBtn = document.getElementsByTagName('button')[0];
    var oImg0 = document.images[0];
    var array = ["img/img1.gif","img/img2.gif","img/img3.gif","img/img4.gif"]
    var iNow = -1;
    oBtn.onclick = function(){
        iNow++;
        iNow = iNow%4;
        oImg0.src = array[iNow];
    }
    var aImages = [];
    function preLoadImg(array){
        for(var i = 0, len = preLoadImg.arguments[0].length; i < len; i++){
            aImages[i] = new Image();
            aImages[i].src = preLoadImg.arguments[0][i];
        }
    }
    window.onload = function(){
        preLoadImg(array);    
    }
    </script>

      3、通过XHR对象发送ajax请求来获取图片,但只能获取同域图片

    【onload和onerror】

      通过添加onload和onerror这两个事件钩子,可以实现图片在加载完成和加载失败时的函数回调。多个资源加载可以计算出大体进度,如3/10

    <button>载入图片</button>
    <img src="img/test.png" alt="测试">
    <script>
    var oBtn = document.getElementsByTagName('button')[0];
    var oImg0 = document.images[0];
    var array = ["img/img1.gif","img/img2.gif","img/img3.gif","img/img4.gif"]
    var iNow = -1;
    oBtn.onclick = function(){
        iNow++;
        iNow = iNow%4;
        oImg0.src = array[iNow];
    }
    var iDown = 0;
    var oImage = new Image();
    function preLoadImg(arr){
        function loadImgTest(arr){
            iDown++;
            if(iDown < arr.length){
                preLoadImg(arr);
            }else{
                alert('ok');
                oImg.onload = null;
                oImg = null;            
            }
        }
        oImage.onload = function(){
            loadImgTest(arr);
        };
        oImage.onerror = function(){
            loadImgTest(arr);
        };    
        oImage.src = arr[iDown];
    }
    preLoadImg(array);
    </script>

      将预加载写成一个通用的资源加载器,代码如下

    let isFunc = function(f){
      return typeof f === 'function'
    }
    function resLoader(config){
      this.option = {
        resourceType: 'image',
        baseUrl: '',
        resources: [],
        onStart: null,
        onProgress: null,
        onComplete: null
      }
      if(config){
        for(i in config){
          this.options[i] = config[i]
        }
      } else {
        alert('参数错误')
        return 
      }
      // 加载器状态 0:未启动 1:正在加载 2:加载完毕
      this.status = 0
      this.total = this.option.resources.length || 0
      this.currentIndex = 0
    }
    resLoader.prototype.start = function(){
      this.status = 1
      let _this = this
      let baseUrl = this.option.baseUrl
      for(var i = 0, l = this.option.resources.length; i < l; i++){
        let r = this.option.resources[i],
            url = ''
        if(r.indexOf('http://) === 0 || r.indexOf('https://') === 0){
          url = r
        } else {
          url = baseUrl + r
        }
        let image = new Image()
        image.onload = function(){_this.loaded()}
        image.onerror = function(){_this.loaded()}
        image.src = url
      }
      if(isFunc(this.option.onStart)){
        this.option.onStart(this.total)
      }
    }
    resloader.prototype.loaded = funtion(){
      if(isFunc(this.option.onProgress)){
        this.option.onProgress(++this.currentIndex, this.total)
      }
      if(this.currentIndex === this.total){
        if(isFunc(this.option.onComplete)){
          this.option.onComplete(this.total)
        }
      }
    } 
    let loader = new resLoader({
      resources: ['img1.png','img2.png','img3.png'],
      onStart: function(total){
        console.log('start:' + total)
      },
      onProgress: function(current, total){
        console.log(current+ '/' + total)
        let percent = current/total*100
      },
      onComplete: function(total){
        console.log('加载完毕:' + total + '个资源')
      }
    })
    loader.start()

    Webp

      在安卓下可以使用webp格式的图片,它具有更优的图像数据压缩算法,能带来更小的图片体积,同等画面质量下,体积比jpg、png少了25%以上,而且同时具备了无损和有损的压缩模式、Alpha 透明以及动画的特性

    【检测】

      是否支持webp格式的图片的检测方法如下

    const isSupportWebp = !![].map && document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp') === 0

    【七牛自动转换】

      七牛支持自动将其他格式的图片转换成webp格式的图片,只需添加在图片地址之后添加?imageView2/2/format/webp

      下面是详细代码

    /**
     * 若该浏览器支持webp格式,则将返回webp图片的url,否则返回原url
     * @param {string} 'https://static.xiaohuochai.site/20180612030117.png'
     * @return {string} 'https://static.xiaohuochai.site/20180612030117.png?imageView2/1/format/webp'
     */
    export const getUrlWithWebp = url => {
      const isSupportWebp = !![].map && document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp') === 0
      if (isSupportWebp) {
        return `${url}?imageView2/2/format/webp`
      }
      return url
    }

    【pageSpeed】

      Google开发的PageSpeed模块有一个功能,会自动将图像转换成WebP格式或者是浏览器所支持的其它格式

      以nginx为例,它的设置很简单

      1、在http模块开启pagespeed属性

    pagespeed on;
    pagespeed FileCachePath "/var/cache/ngx_pagespeed/";

      2、在主机配置添加如下一行代码,就能启用这个特性

    pagespeed EnableFilters convert_png_to_jpeg,convert_jpeg_to_webp;

    CDN

      图片性能的最后一步就是分发了。所有资源都可以从使用 CDN 中受益

      CDN 可以降低从图片站点提供自适应和高性能图片的复杂度。大多数CDN都可以根据设备和浏览器进行尺寸调整、裁剪和确定最合适的格式,甚至更多 —— 压缩、检测像素密度、水印、人脸识别和允许后期处理。借助这些强大的功能和能够将参数附到 URL 中,使得提供以用户为中心的图片变得轻而易举了

      以七牛云为例,imageView2 提供简单快捷的图片格式转换、缩略、剪裁功能。只需要填写几个参数,即可对图片进行缩略操作,生成各种缩略图

    // 裁剪正中部分,等比缩小生成200x200缩略图
    http://odum9helk.qnssl.com/resource/gogopher.jpg?imageView2/1/w/200/h/200
    
    // 宽度固定为200px,高度等比缩小,生成200x133缩略图
    http://odum9helk.qnssl.com/resource/gogopher.jpg?imageView2/2/w/200

    Vue图片优化

      下面来介绍一个VUE下的插件vue-lazyload,可以实现图片或背景图片的懒加载、使用webp图片等效果

      首先,使用npm安装

    npm install vue-lazyload -D

    【基础使用】

      在main.js中,使用该插件

    import Vue from 'vue'
    import App from './App.vue'
    import VueLazyload from 'vue-lazyload'
    
    Vue.use(VueLazyload)
    
    // or with options
    Vue.use(VueLazyload, {
      preLoad: 1.3,
      error: 'dist/error.png',
      loading: 'dist/loading.gif',
      attempt: 1
    })
    
    new Vue({
      el: 'body',
      components: {
        App
      }
    })

      在模板中使用v-lazy来保存图片的真实地址

    <ul>
      <li v-for="img in list">
        <img v-lazy="img.src" >
      </li>
    </ul>

      或者使用v-lazy-container配合图片的data-src属性

    <div v-lazy-container="{ selector: 'img', error: 'xxx.jpg', loading: 'xxx.jpg' }">
      <img data-src="//domain.com/img1.jpg">
      <img data-src="//domain.com/img2.jpg">
      <img data-src="//domain.com/img3.jpg">  
    </div>
    <div v-lazy-container="{ selector: 'img' }">
      <img data-src="//domain.com/img1.jpg" data-error="xxx.jpg">
      <img data-src="//domain.com/img2.jpg" data-loading="xxx.jpg">
      <img data-src="//domain.com/img3.jpg">  
    </div>

    【参数说明】

      vue-lazyload相关配置的参数说明

    key       描述    默认值    类型
    preLoad    预加载的宽高比    1.3    Number
    error      图片加载失败时使用的图片源    'data-src'    String
    loading    图片加载的路径    'data-src'    String
    attempt    尝试加载次数    3    Number
    listenEvents    想让vue监听的事件    ['scroll', 'wheel', 'mousewheel', 'resize', 'animationend', 'transitionend', 'touchmove']    
    adapter    动态修改元素属性    { }    
    filter     图像的SRC过滤器    { }    
    lazyComponent    懒加载组件    false    

      比如,可以使用如下的配置

    Vue.use(VueLazyload, {
      preLoad: 1.3,
      error: 'dist/error.png',
      loading: 'dist/loading.gif',
      attempt: 1,
      listenEvents: [ 'scroll' ]
    })

    【动态修改图片的URL】

    Vue.use(vueLazy, {
        filter: {
          progressive (listener, options) {
              const isCDN = /qiniudn.com/
              if (isCDN.test(listener.src)) {
                  listener.el.setAttribute('lazy-progressive', 'true')
                  listener.loading = listener.src + '?imageView2/1/w/10/h/10'
              }
          },
          webp (listener, options) {
              if (!options.supportWebp) return
              const isCDN = /qiniudn.com/
              if (isCDN.test(listener.src)) {
                  listener.src += '?imageView2/2/format/webp'
              }
          }
        }
    })

    【设置事件钩子】

    Vue.use(vueLazy, {
        adapter: {
            loaded ({ bindType, el, naturalHeight, naturalWidth, $parent, src, loading, error, Init }) {
                // do something here
                // example for call LoadedHandler
                LoadedHandler(el)
            },
            loading (listender, Init) {
                console.log('loading')
            },
            error (listender, Init) {
                console.log('error')
            }
        }
    })

    【使用IntersectionObserver】

    Vue.use(vueLazy, {
      // set observer to true
      observer: true,
    
      // optional
      observerOptions: {
        rootMargin: '0px',
        threshold: 0.1
      }
    })

    【懒加载组件】

    Vue.use(VueLazyload, {
      lazyComponent: true
    });
    <lazy-component @show="handler">
      <img class="mini-cover" :src="img.src" width="100%" height="400">
    </lazy-component>
    
    <script>
      {
        ...
        methods: {
          handler (component) {
            console.log('this component is showing')
          }
        }
    
      }
    </script>

    【组件中为图片或背景图片使用懒加载】

    <script>
    export default {
      data () {
        return {
          imgObj: {
            src: 'http://xx.com/logo.png',
            error: 'http://xx.com/error.png',
            loading: 'http://xx.com/loading-spin.svg'
          },
          imgUrl: 'http://xx.com/logo.png' // String
        }
      }
    }
    </script>
    
    <template>
      <div ref="container">
         <img v-lazy="imgUrl"/>
         <div v-lazy:background-image="imgUrl"></div>
    
         <!-- with customer error and loading -->
         <img v-lazy="imgObj"/>
         <div v-lazy:background-image="imgObj"></div>
    
         <!-- Customer scrollable element -->
         <img v-lazy.container ="imgUrl"/>
         <div v-lazy:background-image.container="img"></div>
    
        <!-- srcset -->
        <img v-lazy="'img.400px.jpg'" data-srcset="img.400px.jpg 400w, img.800px.jpg 800w, img.1200px.jpg 1200w">
        <img v-lazy="imgUrl" :data-srcset="imgUrl' + '?size=400 400w, ' + imgUrl + ' ?size=800 800w, ' + imgUrl +'/1200.jpg 1200w'" />
      </div>
    </template>

    【CSS状态】

    <img src="imgUrl" lazy="loading">
    <img src="imgUrl" lazy="loaded">
    <img src="imgUrl" lazy="error">
    <style>
      img[lazy=loading] {
        /*your style here*/
      }
      img[lazy=error] {
        /*your style here*/
      }
      img[lazy=loaded] {
        /*your style here*/
      }
      /*
      or background-image
      */
      .yourclass[lazy=loading] {
        /*your style here*/
      }
      .yourclass[lazy=error] {
        /*your style here*/
      }
      .yourclass[lazy=loaded] {
        /*your style here*/
      }
    </style>

      下面是前端小站中vue-lazyload插件的使用

    // main.js
    import VueLazyload from 'vue-lazyload'
    
    Vue.use(VueLazyload, {
      loading: require('./assets/imgs/loading.gif'),
      listenEvents: ['scroll'],
      filter: {
        webp(listener, options) {
          if (!options.supportWebp) return
          const isCDN = /xiaohuochai.site/
          if (isCDN.test(listener.src)) {
            listener.src += '?imageView2/2/format/webp'
          }
        }
      }
    })
    // homeCategory.vue
    <ul v-lazy:background-image="require('@/assets/imgs/match-bg.jpg')">
  • 相关阅读:
    用欧拉公式推导三角函数所有公式包括 倍角公式-半角公式-和差化积-积化和差
    20161006-git学习笔记
    十五的学习日记20160925
    十五的学习日记20160926-你不知道的JS笔记/
    十五的学习日记20160927-ES6学习/中间变量/数组坑/
    十五的学习日记20160928-扑克牌/目标/Apache外网访问
    十五的学习日记20160929-click300毫秒延迟/requestAnimationFrame/帧率
    十五的学习日记20160930-jquery/ajax/JS引擎/job queue
    十五的学习日记20161001-insertAdjacentHTML与insertAdjacentText
    传入评分 返回整数和小数 页面拼接 --------------20160705
  • 原文地址:https://www.cnblogs.com/xiaohuochai/p/9183454.html
Copyright © 2011-2022 走看看