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


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

這是一篇關於在前端開發中 與圖片相關的一些常見問題,回想一下,我們在日常的開發過程中前端與圖片打交道的次數可以說是比所有開發職位都要多吧。特別是在 nodeJs 盛行以后。

從我們最開始學習前端的那一天,我們是不是認識了 一個叫 <img /> 的 標簽,這個標簽的 src 屬性可以引用對應路徑的圖片,然后手動刷新頁面,我們的圖片就顯示在了頁面上了, 哇~ 大學的教師里大家都不約而同的發出了哇的聲音,回想起來還是歷歷在目啊~

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

一、 前端圖片的類型

jpg、png、gif、base64、字體圖標。貌似日常開發中,我們常常會用到的就說這些了。

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

( 因為本身對於圖片的理解度還是不夠的,所以詢問了公司的 UI設計師的小姐姐們來幫忙答疑解惑 )
1.1、矢量圖 和 位圖
先上和UI部門的小姐姐的聊天~

其實我覺得 小姐姐的回答 可以說是非常容易懂了

矢量圖: 一般來說矢量圖表示的是幾何圖形,文件相對較小,並且放大縮小不會失真。

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

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

用途:png,gif,jpg,canvas
1.2、有損壓縮 和 無損壓縮

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

  • 無損壓縮是先判斷圖像上哪些區域的顏色是相同的,哪些是不同的,然后把這些相同的數據信息進行壓縮記錄,而把不同的數據另外保存。適用於: PNG。對於圖片的壓縮也會造成一定的損傷,但是相對有限。

  • 看完上面的 對比,仿佛發現了上帝是公平的 開了一扇門,也關上了一扇窗。

1.3、透明度
  • 索引透明

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

  • Alpha透明

    半透明,可以設置 0~100 的透明度。

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

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

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

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

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

1.6、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.1 經常會用到的方法:
  • 圖片大小與展示區一致 (圖片大小合適不過多浪費下載資源)
  • GIF轉為PNG8 (減少gif 體積)
  • 縮略圖(大圖片,先加載一張縮略圖)(避免頁面中圖片的位置出現長時間的空白,影響用戶體驗)

三、預加載

3.1、原理

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

3.2、場景

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

3.3、優缺點

如果都在首頁進行預加載肯定會加長首頁加載時間,首屏加載變慢,影響體驗。
但是在 http2 來臨的時候這個問題,應該可以很有效的進行一個解決。

3.4、實現方法

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.1、原理

當要使用到圖片時,再加載圖片,而不是一下子加載完所有的圖片的方式,來提高頁面其他圖片的加載速度。

4.2、場景

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

4.3、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.1、原理

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

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

5.2、場景

先上圖:

圖片來源網絡,侵刪

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

5.3、JS實現

目前前端大的框架、模式大致可以分為三類: Vue、 React、 小程序。(為什么沒有 Angular ? 你可以去問大漠窮秋撒~😄)

那么我們今天就來講講這三個方向的 骨架屏 優化!

5.3.1 React

React 實現方式一、

    <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 實現方式二、

上面已經介紹到了,骨架屏實際最為真實的原理,那么我們現在就需要讓 這個真實的原理穿上盔甲,變得強硬起來。

1、 先定義一個 skeleton 的組件。
2、 通過 react-dom 的 renderToStaticMarkup 方法獲取到當前組件的 html 代碼。
3、 在 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.4、尾聲

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

Github傳送門,歡迎 Star - -

Github地址,歡迎 Star


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM