《前端之路》之 前端圖片 類型 & 優化 & 預加載 & 懶加載 & 骨架屏

09: 前端圖片 類型 & 優化 & 預加載 & 懶加載 & 骨架屏

這是一篇關於在前端開發中 與圖片相關的一些常見問題,回想一下,咱們在平常的開發過程當中前端與圖片打交道的次數能夠說是比全部開發職位都要多吧。特別是在 nodeJs 盛行之後。

從咱們最開始學習前端的那一天,咱們是否是認識了 一個叫 <img /> 的 標籤,這個標籤的 src 屬性能夠引用對應路徑的圖片,而後手動刷新頁面,咱們的圖片就顯示在了頁面上了, 哇~ 大學的教師裏你們都不約而同的發出了哇的聲音,回想起來仍是歷歷在目啊~

那麼 從業前端 這個崗位也這麼多年了,總結一下在前端中與圖片打交道的一些經驗或者總結吧

1、 前端圖片的類型

jpg、png、gif、base6四、字體圖標。貌似平常開發中,咱們經常會用到的就說這些了。

那咱們來縱向的來統計一下圖片的類型 和 這些類型的圖片在不一樣場景下有哪些優缺點。

( 由於自己對於圖片的理解度仍是不夠的,因此詢問了公司的 UI設計師的小姐姐們來幫忙答疑解惑 )
1.一、矢量圖 和 位圖
先上和UI部門的小姐姐的聊天~

其實我以爲 小姐姐的回答 能夠說是很是容易懂了

矢量圖: 通常來講矢量圖表示的是幾何圖形,文件相對較小,而且放大縮小不會失真。css

用途:SVG,圖標字體font-awesome

位 圖: 位圖又叫像素圖或柵格圖,它是經過記錄圖像中每個點的顏色、深度、透明度等信息來存儲和顯示圖像。 放大會失真(變模糊)html

用途:png,gif,jpg,canvas
1.二、有損壓縮 和 無損壓縮

  • 有損壓縮是對圖像數據進行處理,去掉那些圖像上會被人眼忽略的細節,而後使用附件的顏色經過漸變或其餘形式進行填充。適用於: JPG。 從字面意義上理解就是 對圖片會有必定的損傷。像素的損傷,從而壓縮了 圖片的體積。前端

  • 無損壓縮是先判斷圖像上哪些區域的顏色是相同的,哪些是不一樣的,而後把這些相同的數據信息進行壓縮記錄,而把不一樣的數據另外保存。適用於: PNG。對於圖片的壓縮也會形成必定的損傷,可是相對有限。
  • 看完上面的 對比,彷彿發現了上帝是公平的 開了一扇門,也關上了一扇窗。vue

1.三、透明度
  • 索引透明java

    即布爾透明,相似於GIF,某一個像素只有全透和全不透明二者效果,不能對透明度進行設置。node

  • Alpha透明react

    半透明,能夠設置 0~100 的透明度。git

1.四、經常使用圖片格式
  • JPN 、PNG、 GIF
1.五、新圖片格式 - WebP
  • 出自於谷歌,是一種支持有損壓縮和無損壓縮的圖片文件格式,派生自圖像編碼格式VP8。github

    具備更優的圖像數據壓縮算法,能帶來更小的圖片體積,並且擁有肉眼識別無差別的圖像質量。

    具有了無損和有損的壓縮模式

    支持Alpha透明以及動畫的特性

    在JPEG和PNG的轉化效果都很是優秀,穩定和統一。

1.六、Base64 圖片格式
如何生成 Base64 格式的圖片
var reader = new FileReader(),htmlImage;
reader.onload = function(e){
    //e.target.result 就是base64編碼
    htmlImage = '<img src="' + e.target.result + '"/>';
}
reader.readAsDataURL(file);
優缺點:

優勢
減小HTTP請求
沒有圖片更新要從新上傳,清理緩存的問

缺點
增長了CSS文件的尺寸
編碼成本

2、 前端中圖片相關的優化處理

2.1 常常會用到的方法:
  • 圖片大小與展現區一致 (圖片大小合適不過多浪費下載資源)
  • GIF轉爲PNG8 (減小gif 體積)
  • 縮略圖(大圖片,先加載一張縮略圖)(避免頁面中圖片的位置出現長時間的空白,影響用戶體驗)

3、預加載

3.一、原理

經過CSS或者JavaScript,先請求圖片到本地,再利用瀏覽器的緩存機制,當要使用圖片時(圖片路徑一致),瀏覽器直接從本地緩存獲取到圖片,加快圖片的加載速度。

3.二、場景

背景,幻燈片,相冊等,將要展現的前一張和後一張優先下載

3.三、優缺點

若是都在首頁進行預加載確定會加長首頁加載時間,首屏加載變慢,影響體驗。
可是在 http2 來臨的時候這個問題,應該能夠頗有效的進行一個解決。

3.四、實現方法

CSS

#preload{ backgroud: url(./01.png) no-repeat -9999px -9999px;}

使用這個方法加載圖片會同頁面的其餘內容一塊兒加載,增長了頁面的總體加載時間

JS

let img = document.createElement('img')
img.src = './02.png'
let img = new Image()
img.src = './01.png'

// 可是這種方法是沒法添加的 DOM 樹中去的。

4、 懶加載

4.一、原理

當要使用到圖片時,再加載圖片,而不是一會兒加載完全部的圖片的方式,來提升頁面其餘圖片的加載速度。

4.二、場景

當前頁面的圖片數量過多,且頁面長度很長。

4.三、JS 實現

思路很簡單,通常都是在頁面上添加一個滾動條事件,判斷圖片位置與瀏覽器頂部的距離是否小於(可視高度+滾動距離),若是小於則優先加載。

下面咱們就基於 react 來進行懶加載的實現。

或者能夠查看 github 地址:

github傳送門

代碼以下:

componentDidMount() {

        const lazyload = (options) => {
            // 獲取圖片外部dom
            let doc = options.id ? document.getElementById(options.id) : document
            if (doc === null) return
            // 獲取當前dom 內,全部的圖片標籤
            let tmp = doc.getElementsByTagName('img')
            let tmplen = tmp.length
            let imgobj = []

            // 判斷當前 元素是否到了應該顯示的 位置
            const isLoad = (ele) => {
                let scrollTop = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop
                if (typeof ele === 'undefined') return false
                let edit = ~~ele.getAttribute('data-range') || options.lazyRange
                let clientHeight = scrollTop + document.documentElement.clientHeight + edit
                let offsetTop = 0
    
                while (ele.tagName.toUpperCase() !== 'BODY') {
                    offsetTop += ele.offsetTop
                    ele = ele.offsetParent
                }
                return (clientHeight > offsetTop)
            }

            // 給已經到了能夠顯示圖片位置的 img 標籤添加 src 值
            const setimg = (ele) => {
                ele.src = ele.getAttribute('data-src')
            }

            // 遍歷當前 dom 內全部要顯示的 img 標籤
            for (let i = 0; i < tmplen; i++) {
                var _tmpobj = tmp[i]
                if (_tmpobj.getAttribute('data-src') !== null) {
                    if (isLoad(_tmpobj)) {
                        setimg(_tmpobj)
                    } else {
                        imgobj.push(_tmpobj)
                    }
                }
            }

            // 滾動的時候動態 判斷當前 元素的是否 能夠賦值
            let len = imgobj.length
            const handler = () => {
                for (let i = 0, end = len; i < end; i++) {
                    let obj = imgobj[i]
                    if (isLoad(obj)) {
                        _setimg(obj)
                        imgobj.splice(i, 1)
                        len--
                        if (len === 0) {
                            loadstop()
                        }
                    }
                }
            }
    
            // 根據上下文要求動態低進行 圖片 src 賦值
            const _setimg = (ele) => {
                if (options.lazyTime) {
                    setTimeout(function () {
                        setimg(ele)
                    },
                    options.lazyTime + ~~ele.getAttribute('data-time'))
                } else {
                    setimg(ele)
                }
            }
            
            // 去除 滾動事件監聽
            const loadstop = () => {
                window.removeEventListener ? window.removeEventListener('scroll', handler, false) : window.detachEvent('onscroll', handler)
            }
    
            loadstop()
            // 添加滾動事件監聽
            window.addEventListener ? window.addEventListener('scroll', handler, false) : window.attachEvent('onscroll', handler)
        }

        lazyload({
            id: 'imgs',
            lazyTime: 200,
            lazyRange: 100
        })
    }

在以上的基礎上,其實能夠進行很好的 組件化 的操做。 是一個 很好的面向對象的一個 JS 代碼的實現的例子。後面的文章當中。咱們也會加大 JS 中 OOP 相關文章的篇幅,敬請期待~

5、 骨架屏(首屏加載優化)

今天終於要講到咱們的主角了, 骨架屏。 終於不是首頁進去就是加載一個 菊花了,讓你成爲 菊外人了,而是一個 科技感滿滿的的 骨架屏 ,好了,話很少說,開始今天的討論吧!

5.一、原理

在 H5 中,骨架屏已經不是什麼新奇的概念了,在咱們經常使用的不少網站中都有關於這方面的介紹,並且咱們在實際的應用中也能查找到一些案例,好比說: 餓了麼、小米、掘金等 他們的 H5 端都有作骨架屏,讓咱們的體驗不會顯得那麼的單薄,而是滿滿都科技感。

至於實現的原理的話,其實也很簡單,就是在頁面還未加載渲染出來以前,在頁面的空白處先展現出來一個簡單的相似頁面原型的html。(小程序除外,後面也會介紹到小程序的骨架屏)

5.二、場景

先上圖:

圖片來源網絡,侵刪

在對前端技術比較依賴的大小廠當中,都已經使用骨架屏來改善自家的首屏加載、模塊加載,那咱們是否是也應該折騰的搞起來!

5.三、JS實現

目前前端大的框架、模式大體能夠分爲三類: Vue、 React、 小程序。(爲何沒有 Angular ? 你能夠去問大漠窮秋撒~😄)

那麼咱們今天就來說講這三個方向的 骨架屏 優化!

5.3.1 React

React 實現方式1、

<div id="root">
        <div class="skeleton page">
            <div class="skeleton-nav"></div>
            <div class="skeleton-swiper"></div>
            <ul class="skeleton-tabs">
              <li class="skeleton-tabs-item"><span></span></li>
              <li class="skeleton-tabs-item"><span></span></li>
              <li class="skeleton-tabs-item"><span></span></li>
              <li class="skeleton-tabs-item"><span></span></li>
              <li class="skeleton-tabs-item"><span></span></li>
              <li class="skeleton-tabs-item"><span></span></li>
              <li class="skeleton-tabs-item"><span></span></li>
              <li class="skeleton-tabs-item"><span></span></li>
            </ul>
            <div class="skeleton-banner"></div>
            <div class="skeleton-productions"></div>
            <div class="skeleton-productions"></div>
            <div class="skeleton-productions"></div>
            <div class="skeleton-productions"></div>
            <div class="skeleton-productions"></div>
            <div class="skeleton-productions"></div>
          </div>
    </div>
<style>
        .skeleton {
          position: relative;
          height: 100%;
          overflow: hidden;
          padding: 15px;
          box-sizing: border-box;
          background: #fff;
        }
        .skeleton-nav {
          height: 45px;
          background: #eee;
          margin-bottom: 15px;
        }
        .skeleton-swiper {
          height: 160px;
          background: #eee;
          margin-bottom: 15px;
        }
        .skeleton-tabs {
          list-style: none;
          padding: 0;
          margin: 0 -15px;
          display: flex;
          flex-wrap: wrap;
        }
        .skeleton-tabs-item {
          width: 25%;
          height: 55px;
          box-sizing: border-box;
          text-align: center;
          margin-bottom: 15px;
        }
        .skeleton-tabs-item span {
          display: inline-block;
          width: 55px;
          height: 55px;
          border-radius: 55px;
          background: #eee;
        }
        .skeleton-banner {
          height: 60px;
          background: #eee;
          margin-bottom: 15px;
        }
        .skeleton-productions {
          height: 20px;
          margin-bottom: 15px;
          background: #eee;
        }
    </style>

在咱們 最初建立的html文件中的 id = #root Dom 內,寫入咱們的骨架屏的 html 和 css 的代碼。

id = #root Dom 的 VD 還未被渲染出來以前, 就會先渲染出寫死在骨架屏中的html代碼。

這個就是 骨架屏 最原始的原理。

可是!!! 這麼寫前端的代碼是否是特別LOW吶?前端早已過了刀耕火種的年代了,再這麼不科學的寫代碼就
會被作 code review 的同窗砍死的吧。

那麼咱們就來寫一些高級一點,且適合維護的前端代碼。就是下面介紹到的第二種實現的方式。

React 實現方式2、

上面已經介紹到了,骨架屏實際最爲真實的原理,那麼咱們如今就須要讓 這個真實的原理穿上盔甲,變得強硬起來。

一、 先定義一個 skeleton 的組件。
二、 經過 react-dom 的 renderToStaticMarkup 方法獲取到當前組件的 html 代碼。
三、 在 build 構建 項目的時候 經過 fs 讀取到 index.html 文件的所有內容,並將提早寫好在html 的註釋 進行替換。

5.3.2 Vue

Vue 實現骨架屏的第一種方式也是上面那樣。

可是第二種純粹的代碼實現就 相對於 react 的要困難一些,須要用到 node服務去作 服務端渲染,而後在輸出index.html 的時候 先顯示 骨架屏組件的內容,等到 app.js 中的內容加載完畢之後便自動替換了html 中的文件。

可是!

vue 有一點優勢就是 任何組件均可以來進行骨架屏的優化。

具體以下:

const AsyncComponent = () => ({
  // 須要加載的組件 (應該是一個 `Promise` 對象)
  component: import('./MyComponent.vue'),
  // 異步組件加載時使用的組件
  loading: LoadingComponent,
  // 加載失敗時使用的組件
  error: ErrorComponent,
  // 展現加載時組件的延時時間。默認值是 200 (毫秒)
  delay: 200,
  // 若是提供了超時時間且組件加載也超時了,
  // 則使用加載失敗時使用的組件。默認值是:`Infinity`
  timeout: 3000
})

經過異步組件加載的機制,來變相的實現 骨架屏也是一種不錯的思路。時間有限,你們能夠本身下去思考下。

5.3.2 小程序

這裏的原理能夠同 上面 H5的方法同樣,文章中也是簡單介紹2中方法。

方法一: 用原生的api 寫的小程序
先寫一個 skeleton 的組件。

放在首頁的第一渲染的位置。經過 wx-if 來控制 skeleton 組件的顯示和 小程序 index 渲染。
方法二: 用 wepy 框架寫的小程序
方法二: 其實也是經過方法一來一樣實現的。可是小程序首頁加載的問題,並非經過 骨架屏就能來解決的。

其實  首頁分包 + 骨架屏 同時來使用,首頁加載的問題應該能夠獲得相應的解決。

5.四、尾聲

寫到最會,其實骨架屏沒有多大的難度,若是有效的來管理這些骨架屏纔是難點之一。
文章後面寫到的 骨架屏並未作過多的闡述,後面有機會的話再來拿實際的項目數據來解釋這個不難的技術。

Github傳送門,歡迎 Star - -

Github地址,歡迎 Star

相關文章
相關標籤/搜索