国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專(zhuān)欄INFORMATION COLUMN

設(shè)計(jì)無(wú)限滾動(dòng)下拉加載,實(shí)踐高性能頁(yè)面真諦

novo / 2173人閱讀

摘要:論壇上有過(guò)這么一篇熱門(mén)文章,它從角度分析了無(wú)限滾動(dòng)加載的設(shè)計(jì)實(shí)踐。無(wú)限滾動(dòng)加載背后的技術(shù)挑戰(zhàn)其實(shí)比想象中要多不少。整體思路和方案設(shè)計(jì)我們要實(shí)現(xiàn)的頁(yè)面樣例如圖,它能夠做到無(wú)限下拉加載內(nèi)容。

UX Planet論壇上有過(guò)這么一篇熱門(mén)文章: Infinite Scrolling Best Practices,它從UX角度分析了無(wú)限滾動(dòng)加載的設(shè)計(jì)實(shí)踐。

無(wú)限滾動(dòng)加載在互聯(lián)網(wǎng)上到處都有應(yīng)用:
豆瓣首頁(yè)是一個(gè),F(xiàn)acebook的Timeline是一個(gè),Tweeter的話題列表也是一個(gè)。當(dāng)你向下滾動(dòng),新的內(nèi)容就神奇的“無(wú)中生有”了。這是一個(gè)得到廣泛贊揚(yáng)的用戶(hù)體驗(yàn)。

無(wú)限滾動(dòng)加載背后的技術(shù)挑戰(zhàn)其實(shí)比想象中要多不少。尤其是要考慮頁(yè)面性能,需要做到極致。
本文通過(guò)代碼實(shí)例,來(lái)實(shí)現(xiàn)一個(gè)無(wú)限滾動(dòng)加載效果。更重要的是,在實(shí)現(xiàn)過(guò)程中,對(duì)于頁(yè)面性能的分析和處理力圖做到最大化,希望對(duì)讀者有所啟發(fā),同時(shí)也歡迎與我討論。

性能測(cè)量

在開(kāi)啟我們的代碼之前,有必要先了解一下常用的性能測(cè)量手段:

1)使用window.performance

HTML5帶來(lái)的performance API功能強(qiáng)大。我們可以使用其performance.now()精確計(jì)算程序執(zhí)行時(shí)間。performance.now()與Date.now()不同的是,返回了以微秒(百萬(wàn)分之一秒)為單位的時(shí)間,更加精準(zhǔn)。并且與 Date.now() 會(huì)受系統(tǒng)程序執(zhí)行阻塞的影響不同,performance.now() 的時(shí)間是以恒定速率遞增的,不受系統(tǒng)時(shí)間的影響(系統(tǒng)時(shí)間可被人為或軟件調(diào)整)。
同時(shí),也可以使用performance.mark()標(biāo)記各種時(shí)間戳(就像在地圖上打點(diǎn)),保存為各種測(cè)量值(測(cè)量地圖上的點(diǎn)之間的距離),便可以批量地分析這些數(shù)據(jù)了。

2)使用console.time方法與console.timeEnd方法

其中console.time方法用于標(biāo)記開(kāi)始時(shí)間,console.timeEnd方法用于標(biāo)記結(jié)束時(shí)間,并且將結(jié)束時(shí)間與開(kāi)始時(shí)間之間經(jīng)過(guò)的毫秒數(shù)在控制臺(tái)中輸出。

3)使用專(zhuān)業(yè)的測(cè)量工具/平臺(tái):jsPerf

這次實(shí)現(xiàn)中,我們使用第二種方法,因?yàn)樗呀?jīng)完全可以滿足我們的需求,且兼容性更加全面。

整體思路和方案設(shè)計(jì)

我們要實(shí)現(xiàn)的頁(yè)面樣例如圖,

它能夠做到無(wú)限下拉加載內(nèi)容。我把紅線標(biāo)出的部分叫做一個(gè)block-item,后續(xù)也都用這種命名。

1)關(guān)于設(shè)計(jì)方案,肯定第一個(gè)最基本、最樸素的思想是下拉到底部之后發(fā)送ajax異步請(qǐng)求,成功之后的回調(diào)里進(jìn)行頁(yè)面拼接。

2)但是觀察頁(yè)面布局,很明顯圖片較多,每一個(gè)block-item區(qū)塊都有一張配圖。當(dāng)加載后的內(nèi)容插入到頁(yè)面中時(shí),瀏覽器就開(kāi)始獲取圖片。這意味著所有的圖像同時(shí)下載,瀏覽器中的下載通道將被占滿。同時(shí),由于內(nèi)容優(yōu)先于用戶(hù)瀏覽而加載,所以可能被迫下載底部那些永遠(yuǎn)也不會(huì)被用戶(hù)瀏覽到的圖像。
所以,我們需要設(shè)計(jì)一個(gè)懶加載效果,使得頁(yè)面速度更快,并且節(jié)省用戶(hù)的流量費(fèi)用和延長(zhǎng)電池壽命。

3)上一條提到的懶加載實(shí)現(xiàn)上,為了避免到真正的頁(yè)面底部時(shí)才進(jìn)行加載和渲染,而造成用戶(hù)較長(zhǎng)時(shí)間等待。我們可以設(shè)置一個(gè)合理閾值,在用戶(hù)滾動(dòng)到頁(yè)面底部之前,先進(jìn)行提前加載。

4)另外,頁(yè)面滾動(dòng)的事件肯定是需要監(jiān)聽(tīng)的。同時(shí),頁(yè)面滾動(dòng)問(wèn)題也比較棘手,后面將專(zhuān)為滾動(dòng)進(jìn)行分析。

5)DOM操作我們知道是及其緩慢而低效的,有興趣的同學(xué)可以研究一下jsPerf上一些經(jīng)典的benchmark,比如這篇。關(guān)于造成這種緩慢的原因,社區(qū)上同樣有很多文章有過(guò)分析,這里就不再深入。但我想總結(jié)并補(bǔ)充的是:DOM操作,光是為了找一個(gè)節(jié)點(diǎn),就從本質(zhì)上比簡(jiǎn)單的檢索內(nèi)存中的值要慢。一些DOM操作還需要重新計(jì)算樣式來(lái)讀取或檢索一個(gè)值。更突出的問(wèn)題在于:DOM操作是阻塞的,所以當(dāng)有一個(gè)DOM操作在進(jìn)行時(shí),其他的什么都不能做,包括用戶(hù)與頁(yè)面的交互(除了滾動(dòng))。這是一個(gè)極度傷害用戶(hù)體驗(yàn)的事實(shí)。

所以,在下面的效果實(shí)現(xiàn)中,我采用了大量“不可思議”的DOM緩存,甚至極端的緩存everything。當(dāng)然,這樣做的收益也在最后部分有所展現(xiàn)。

滾動(dòng)問(wèn)題

滾動(dòng)問(wèn)題不難想象在于高頻率的觸發(fā)滾動(dòng)事件處理上。具我親測(cè),在極端case下,滾動(dòng)及其卡頓。即使?jié)L動(dòng)不卡頓,你可以打開(kāi)Chrome控制臺(tái)發(fā)現(xiàn),幀速率也非常慢。關(guān)于幀速率的問(wèn)題,我們有著名的16.7毫秒理論。關(guān)于這個(gè)時(shí)間分析,社區(qū)上也有不少文章闡述,這里不再展開(kāi)。

針對(duì)于此,有很多讀者會(huì)立刻想到“截流和防抖動(dòng)函數(shù)”(Throttle和Debounce)。
簡(jiǎn)單總結(jié)一下:

1)Throttle允許我們限制激活響應(yīng)的數(shù)量。我們可以限制每秒回調(diào)的數(shù)量。反過(guò)來(lái),也就是說(shuō)在激活下一個(gè)回調(diào)之前要等待多少時(shí)間;

2)Debounce意味著當(dāng)事件發(fā)生時(shí),我們不會(huì)立即激活回調(diào)。相反,我們等待一定的時(shí)間并檢查相同的事件是否再次觸發(fā)。如果是,我們重置定時(shí)器,并再次等待。如果在等待期間沒(méi)有發(fā)生相同的事件,我們就立即激活回調(diào)。

具體這里就不代碼實(shí)現(xiàn)了。原理明白之后,應(yīng)該不難寫(xiě)出。

但是我這里想從移動(dòng)端主要瀏覽器處理滾動(dòng)的方式入手,來(lái)思考這個(gè)問(wèn)題:

1)在Android機(jī)器上,用戶(hù)滾動(dòng)屏幕時(shí),滾動(dòng)事件高頻率發(fā)生——在Galaxy-SIII手機(jī)上,大約頻率是一秒一百次。這意味著,滾動(dòng)處理函數(shù)也被調(diào)用了數(shù)百次,而這些又都是成本較大的函數(shù)。

2)在Safari瀏覽器上,我們遇到的問(wèn)題恰恰是相反的:用戶(hù)每次滾動(dòng)屏幕時(shí),滾動(dòng)事件只在滾動(dòng)動(dòng)畫(huà)停止時(shí)才觸發(fā)。當(dāng)用戶(hù)在iPhone上滾動(dòng)屏幕時(shí),不會(huì)運(yùn)行更新界面的代碼(滾動(dòng)停止時(shí)才會(huì)運(yùn)行一次)。

另外,我想也許會(huì)有讀者想到rAf(requestAnimationFrame),但是據(jù)我觀察,很多前端其實(shí)并不明白requestAnimationFrame技術(shù)的原理和解決的問(wèn)題。只是機(jī)械地把動(dòng)畫(huà)性能、掉幀問(wèn)題甩到這么一個(gè)名詞上。在真實(shí)項(xiàng)目中,也沒(méi)有親自實(shí)現(xiàn)過(guò),更不要說(shuō)考慮requestAnimationFrame的兼容性情況了。這里場(chǎng)景我并不會(huì)使用rAf,因?yàn)椤etTimeout的定時(shí)器值推薦最小使用16.7ms(原因請(qǐng)去社區(qū)上找答案,不再細(xì)講),我們這里并不會(huì)超過(guò)這個(gè)限制,并且考慮兼容性。關(guān)于這項(xiàng)技術(shù)的使用,如果有問(wèn)題,歡迎留言討論。

基于以上,我的解決方案是既不同于Throttle,也不同于Debounce,但是和這兩個(gè)思想,尤其是Throttle又比較類(lèi)似:把滾動(dòng)事件替換為一個(gè)帶有計(jì)時(shí)器的滾動(dòng)處理程序,每100毫秒進(jìn)行簡(jiǎn)單檢查,看這段時(shí)間內(nèi)用戶(hù)是否滾動(dòng)過(guò)。如果沒(méi)有,則什么都不做;如果有,就進(jìn)行處理。

用戶(hù)體驗(yàn)優(yōu)化小竅門(mén)

在圖像加載完成時(shí),使用淡入(fade in)效果出現(xiàn)。這在實(shí)際情況上會(huì)稍微慢一下,應(yīng)該慢一個(gè)過(guò)渡執(zhí)行時(shí)間。但用戶(hù)體驗(yàn)上感覺(jué)會(huì)更快。這是已經(jīng)被證實(shí)且普遍應(yīng)用的小“trick”。但是據(jù)我感覺(jué),它確實(shí)有效。我們的代碼實(shí)現(xiàn)也采用了這個(gè)小竅門(mén)。不過(guò)類(lèi)似這種“社會(huì)心理學(xué)”范疇的東西,顯然不是本文研究的重點(diǎn)。

總結(jié)一下

代碼上將會(huì)采用:超前閾值的懶加載+DOM Cache和圖片Cache+滾動(dòng)throttle模擬+CSS fadeIn動(dòng)畫(huà)。
具體功能封裝上和一些實(shí)現(xiàn)層面的東西,請(qǐng)您繼續(xù)閱讀。

代碼實(shí)現(xiàn) DOM結(jié)構(gòu)

整體結(jié)構(gòu)如下:

    

主體內(nèi)容放在id為“expListBox”的container里面,id為“expList”的ul是頁(yè)面加載內(nèi)容的容器。
因?yàn)槊看渭虞d并append進(jìn)入HTML的內(nèi)容相對(duì)較多。我使用了模版來(lái)取代傳統(tǒng)的字符串拼接。前端模版這次選用了我的同事顏海鏡大神的開(kāi)源作品,模版結(jié)構(gòu)為:

     <#dataList.forEach(function (v) {#>
        
    <#})#>

以上模版內(nèi)容由每次ajax請(qǐng)求到的數(shù)據(jù)填充,并添加進(jìn)入頁(yè)面,構(gòu)成每個(gè)block-item。
這里需要注意觀察,有助于對(duì)后面邏輯的理解。頁(yè)面中一個(gè)block-item下div屬性存有該block-item的eid值,對(duì)應(yīng)class叫做"slide",子孫節(jié)點(diǎn)包含有一個(gè)image標(biāo)簽,src初始賦值為1px的空白圖進(jìn)行占位。真實(shí)圖片資源位置存儲(chǔ)在"data-src"中。
另外,請(qǐng)求返回的數(shù)據(jù)dataList可以理解為由9個(gè)對(duì)象構(gòu)成的數(shù)組,也就是說(shuō),每次請(qǐng)求加載9個(gè)block-item。

樣式亮點(diǎn)

樣式方面不是這篇文章的重點(diǎn),挑選最核心的一行來(lái)說(shuō)明一下:

    .slide .img{
        display: inline-block;
        width: 90px;
        height: 90px;
        margin: 0 auto;
        opacity: 0;
        -webkit-transition: opacity 0.25s ease-in-out;
        -moz-transition: opacity 0.25s ease-in-out;
        -o-transition: opacity 0.25s ease-in-out;
        transition: opacity 0.25s ease-in-out;
    }

唯一需要注意的是image的opacity設(shè)置為0,圖片將會(huì)在成功請(qǐng)求并渲染后調(diào)整為1,輔助transition屬性實(shí)現(xiàn)一個(gè)fade in效果。
對(duì)應(yīng)我們上面所提到的那個(gè)“trick”

邏輯部分

我是完全按照業(yè)務(wù)需求來(lái)設(shè)計(jì),并沒(méi)有做抽象。其實(shí)這樣的一個(gè)下拉加載功能完全可以抽象出來(lái)。有興趣的讀者可以下去自己進(jìn)行封裝和抽象。
我們先把精力集中在邏輯處理上。
下面進(jìn)入我們最核心的邏輯部分,為了防止全局污染,我把它放入了一個(gè)立即執(zhí)行函數(shù)中:

    (function() {
        var fetching = false; 
        var page = 1;
        var slideCache = [];
        var itemMap = {};
        var lastScrollY = window.pageYOffset;
        var scrollY = window.pageYOffset;
        var innerHeight;
        var topViewPort;
        var bottomViewPort;
        
        function isVisible (id) {
            // ...判斷元素是否在可見(jiàn)區(qū)域
        }
        
        function updateItemCache (node) {
            // ....更新DOM緩存
        }
        
        function fetchContent () {
            // ...ajax請(qǐng)求數(shù)據(jù)
        }
        

        function handleDefer () {
            // ...懶加載實(shí)現(xiàn)
        }

        function handleScroll (e, force) {
            // ...滾動(dòng)處理程序
        } 
        
        window.setTimeout(handleScroll, 100);
        fetchContent();
    }());

我認(rèn)為好的編程習(xí)慣是在程序開(kāi)頭部分便聲明所有的變量,防止“變量提升”帶來(lái)的潛在困擾,并且也有利于程序的整體把控。
我們來(lái)看一下變量設(shè)置:

    // 加載中狀態(tài)鎖
    1)var fetching = false;
    // 用于加載時(shí)發(fā)送請(qǐng)求參數(shù),表示第幾屏內(nèi)容,初始為1,以后每請(qǐng)求一次,遞增1
    2)var page = 1; 
    // 只緩存最新一次下拉數(shù)據(jù)生成的DOM節(jié)點(diǎn),即需要插入的dom緩存數(shù)組
    3)var slideCache = []; 
    // 用于已經(jīng)生成的DOM節(jié)點(diǎn)儲(chǔ)存,存有item的offsetTop,offsetHeight
    4) var slideMap = {}; 
    // pageYOffset設(shè)置或返回當(dāng)前頁(yè)面相對(duì)于窗口顯示區(qū)左上角的Y位置。
    5)var lastScrollY = window.pageYOffset; var scrollY = window.pageYOffset;
    // 瀏覽器窗口的視口(viewport)高度
    6)var innerHeight;
    // isVisible的上下閾值邊界
    7) var topViewPort; 
    8) var bottomViewPort; 

關(guān)于DOM cache的變量詳細(xì)說(shuō)明,在后文有提供。

同樣,我們有5個(gè)函數(shù)。在上面的代碼中,注釋已經(jīng)寫(xiě)明白了每個(gè)方法的具體作用。接下來(lái),我們逐個(gè)分析。

滾動(dòng)處理程序handleScroll

它接受兩個(gè)變量,第二個(gè)是一個(gè)布爾值force,表示是否強(qiáng)制觸發(fā)滾動(dòng)程序執(zhí)行。

核心思路是:如果時(shí)間間隔100毫秒內(nèi),沒(méi)有發(fā)生滾動(dòng),且并未強(qiáng)制觸發(fā),則do nothing,間隔100毫秒之后再次查詢(xún),然后直接return。
其中,是否發(fā)生滾動(dòng)由lastScrollY === window.scrollY來(lái)判斷。
在100毫秒之內(nèi)發(fā)生滾動(dòng)或者強(qiáng)制觸發(fā)時(shí),需要判斷是否滾動(dòng)已接近頁(yè)面底部。如果是,則拉取數(shù)據(jù),調(diào)用fetchContent方法,并調(diào)用懶加載方法handleDefer。
并且在這個(gè)處理程序中,我們計(jì)算出來(lái)了isVisible區(qū)域的上下閾值。我們使用600作為浮動(dòng)區(qū)間,這么做的目的是在一定范圍內(nèi)提前加載圖片,節(jié)省用戶(hù)等待時(shí)間。當(dāng)然,如果我們進(jìn)行抽象時(shí),可以把這個(gè)值進(jìn)行參數(shù)化。

    function handleScroll (e, force) {
        // 如果時(shí)間間隔內(nèi),沒(méi)有發(fā)生滾動(dòng),且并未強(qiáng)制觸發(fā)加載,則do nothing,再次間隔100毫秒之后查詢(xún)
        if (!force && lastScrollY === window.scrollY) {
            window.setTimeout(handleScroll, 100);
            return;
        }
        else {
            // 更新文檔滾動(dòng)位置
            lastScrollY = window.scrollY;
        }
        scrollY = window.scrollY;
        // 瀏覽器窗口的視口(viewport)高度賦值
        innerHeight = window.innerHeight;
        // 計(jì)算isVisible上下閾值
        topViewPort = scrollY - 1000;
        bottomViewPort = scrollY + innerHeight + 600;

        // 判斷是否需要加載
        // document.body.offsetHeight;返回當(dāng)前網(wǎng)頁(yè)高度 
        if (window.scrollY + innerHeight + 200 > document.body.offsetHeight) {
            fetchContent();
        }
        // 實(shí)現(xiàn)懶加載
        handleDefer();
        window.setTimeout(handleScroll, 100);
    } 
拉取數(shù)據(jù)

這里我用到了自己封裝的ajax接口方法,它基于zepto的ajax方法,只不過(guò)又手動(dòng)采用了promise包裝一層。實(shí)現(xiàn)比較簡(jiǎn)單,當(dāng)然有興趣可以找我要一下代碼,這里不再詳細(xì)說(shuō)了。
我們使用前端模版進(jìn)行HTML渲染,同時(shí)調(diào)用updateItemCache,將此次數(shù)據(jù)拉取生成的DOM節(jié)點(diǎn)緩存。之后手動(dòng)觸發(fā)handleScroll,更新文檔滾動(dòng)位置和懶加載處理。

    function fetchContent () {
        // 設(shè)置加載狀態(tài)鎖
        if (fetching) {
            return;
        }
        else {
            fetching = true;
        }
        ajax({
            url: (!location.pathname.indexOf("/m/") ? "/m" : "")
                + "/list/asyn?page=" + page + (+new Date),
            timeout: 300000,
            dataType: "json"
        }).then(function (data) {
            if (data.errno) {
                return;
            }
            console.time("render");

            var dataList = data.data.list;
            var len = dataList.length;
            var ulContainer = document.getElementById("expList");
            var str = "";
            var frag = document.createElement("div");

            var tpl = __inline("content.tmpl");
            for (var i = 0; i < len; i++) {
                str = tpl({dataList: dataList});
            }
            frag.innerHTML = str;
            ulContainer.appendChild(frag);
            // 更新緩存
            updateItemCache(frag);
            // 已經(jīng)拉去完畢,設(shè)置標(biāo)識(shí)為true
            fetching = false;
            // 強(qiáng)制觸發(fā)
            handleScroll(null, true);
            page++;
            console.timeEnd("render");
        }, function (xhr, type) {
            console.log("Refresh:Ajax Error!");
        });
    }
緩存對(duì)象

之前參數(shù)里提到過(guò),一共有兩個(gè)用于緩存的對(duì)象/數(shù)組:

1)slideCache:緩存最近一次加載過(guò)的數(shù)據(jù)生成的DOM內(nèi)容,緩存方式為數(shù)組儲(chǔ)存:

    slideCache = [
        {
            id: "s-97r45",
            img: img DOM節(jié)點(diǎn),
            node: 父容器DOM node,類(lèi)似
, src: 圖片資源地址 }, ... ]

slideCache由updateItemCache函數(shù)更新,主要用于懶加載時(shí)的賦值src。這樣我們做到“只寫(xiě)入DOM”原則,不需要再?gòu)腄OM讀取。

2)slideMap:緩存DOM節(jié)點(diǎn)的高度和offsetTop,以DOM節(jié)點(diǎn)的id為索引。存儲(chǔ)方式:

    slideMap = {
        s-97r45: {
            node: DOM node,類(lèi)似
, offTop: 300, offsetHeight: 90 } }

slideMap根據(jù)isVisible方法的參數(shù)進(jìn)行更新和讀取。使得我們?cè)谂袛嗍欠駃sVisible時(shí),大量減少讀取DOM的操作。

懶加載程序

在上面的滾動(dòng)處理程序中,我們調(diào)用了handleDefer函數(shù)。我們看一下這個(gè)函數(shù)的實(shí)現(xiàn):

    function handleDefer () {
        // 時(shí)間記錄
        console.time("defer");

        // 獲取dom緩存
        var list = slideCache;
        // 對(duì)于遍歷list里的每一項(xiàng),都使用一個(gè)變量,而不是在循環(huán)內(nèi)部聲明。節(jié)省內(nèi)存,把性能高效,做到極致。
        var thisImg;

        for (var i = 0, len = list.length; i < len; i++) {
            thisImg = list[i].img; // 這里我們都是從內(nèi)存中讀取,而不用讀取DOM節(jié)點(diǎn)
            var deferSrc = list[i].src; // 這里我們都是從內(nèi)存中讀取,而不用讀取DOM節(jié)點(diǎn)
            // 判斷元素是否可見(jiàn)
            if (isVisible(list[i].id)) {
                // 這個(gè)函數(shù)是圖片onload邏輯
                var handler = function () {
                    var node = thisImg;
                    var src = deferSrc;
                    // 創(chuàng)建一個(gè)閉包
                    return function () {
                        node.src = src;
                        node.style.opacity = 1;
                    }
                }
                var img = new Image();
                img.onload = handler();
                img.src = list[i].src;
            }
        }
        console.timeEnd("defer");
    }

主要思路就是對(duì)DOM緩存中的每一項(xiàng)進(jìn)行循環(huán)遍歷。在循環(huán)中,判斷每一項(xiàng)是否已經(jīng)進(jìn)入isVisible區(qū)域。如果進(jìn)入isVisible區(qū)域,則對(duì)當(dāng)前項(xiàng)進(jìn)行真實(shí)src賦值,并設(shè)置opacity為1。

更新拉取數(shù)據(jù)生成的DOM緩存

針對(duì)每一個(gè)slide類(lèi),我們緩存對(duì)應(yīng)DOM節(jié)、id、子元素img DOM節(jié)點(diǎn):

    function updateItemCache (node) {
        var list = node.querySelectorAll(".slide");
        var len = list.length;
        slideCache = [];
        var obj;

        for (var i=0; i < len; i++) {
            obj = {
                node: list[i],
                id: list[i].getAttribute("id"),
                img: list[i].querySelector(".img")
            }
            obj.src = obj.img.getAttribute("data-src");
            slideCache.push(obj);
        };
    }
是否在isVisible區(qū)域判斷

該函數(shù)接受相應(yīng)DOM id,并進(jìn)行判斷。
如果判斷條件晦澀難懂的話,你一定要手動(dòng)畫(huà)畫(huà)圖理解一下。如果你就是懶得畫(huà)圖,那么也沒(méi)關(guān)系,我?guī)湍惝?huà)好了,只是丑一些。。。

    function isVisible (id) {
        var offTop;
        var offsetHeight;
        var data;
        var node;

        // 判斷此元素是否已經(jīng)懶加載正確渲染,分為在屏幕之上(已經(jīng)懶加載完畢)和屏幕外,已經(jīng)添加到dom中,但是還未請(qǐng)求圖片(懶加載之前)
        if (itemMap[id]) {
            // 直接獲取offTop,offsetHeight值
            offTop = itemMap[id].offTop;
            offsetHeight = itemMap[id].offsetHeight;
        }
        else {
            // 設(shè)置該節(jié)點(diǎn),并且設(shè)置節(jié)點(diǎn)屬性:node,offTop,offsetHeight
            node = document.getElementById(id);
            // offsetHeight是自身元素的高度
            offsetHeight = parseInt(node.offsetHeight);
            // 元素的上外緣距離最近采用定位父元素內(nèi)壁的距離
            offTop = parseInt(node.offsetTop);
        }

        if (offTop + offsetHeight > topViewPort && offTop < bottomViewPort) {
            return true;
        }
        else {
            return false;
        }
    }

性能收益

如上代碼,我們主要進(jìn)行了兩方面的性能考量:

1)延遲加載時(shí)間

2)渲染DOM時(shí)間

整體收益如下:

優(yōu)化前延遲平均值:49.2ms 中間值:43ms;

優(yōu)化后延遲平均值:17.1ms 中間值:11ms;

優(yōu)化前渲染平均值:2129.6ms 中間值:2153.5ms;

優(yōu)化后渲染平均值:120.5ms 中間值:86ms;

繼續(xù)思考

做完這些,其實(shí)也遠(yuǎn)遠(yuǎn)沒(méi)有達(dá)到所謂的“極致化”性能體驗(yàn)。我們無(wú)非就做了各種DOM緩存、映射、懶加載。如果繼續(xù)分析edge case,我們還能做的更多,比如:DOM回收、墓碑和滾動(dòng)錨定。這些其實(shí)很多都是借鑒客戶(hù)端開(kāi)發(fā)理念,但是超前的谷歌開(kāi)發(fā)者團(tuán)隊(duì)也都有了自己的實(shí)現(xiàn)。比如在去年7月份的
一篇文章:Complexities of an Infinite Scroller就都有所提及。這里從原理(非代碼)層面,也給大家做個(gè)介紹。

DOM回收

它的原理是,對(duì)于需要產(chǎn)生的大量DOM節(jié)點(diǎn)(比如我們下拉加載的信息內(nèi)容)不是主動(dòng)用createElement的方式創(chuàng)建,而是回收利用那些已經(jīng)移出視窗,暫時(shí)不會(huì)被需要的DOM節(jié)點(diǎn)。如圖:

雖然DOM節(jié)點(diǎn)本身并非耗能大戶(hù),但是也不是一點(diǎn)都不消耗性能,每一個(gè)節(jié)點(diǎn)都會(huì)增加一些額外的內(nèi)存、布局、樣式和繪制。同樣需要注意的一點(diǎn)是,在一個(gè)較大的DOM中每一次重新布局或重新應(yīng)用樣式(在節(jié)點(diǎn)上增加或刪除樣式所觸發(fā)的過(guò)程)的系統(tǒng)開(kāi)銷(xiāo)都會(huì)比較昂貴。所以進(jìn)行DOM回收意味著我們會(huì)保持DOM節(jié)點(diǎn)在一個(gè)比較低的數(shù)量上,進(jìn)而加快上面提到的這些處理過(guò)程。

據(jù)我觀察,在真正產(chǎn)品線上使用這項(xiàng)技術(shù)的還比較少。可能是因?yàn)閷?shí)現(xiàn)復(fù)雜度和收益比并不很高。但是,淘寶移動(dòng)端檢索頁(yè)面實(shí)現(xiàn)了類(lèi)似的思想。如下圖,

每加載一次數(shù)據(jù),就生成“.page-container .J-PageContainer_頁(yè)數(shù)”的div,在滾動(dòng)多屏之后,早已移除視窗的div的子節(jié)點(diǎn)進(jìn)行了remove(),并且為了保證滾動(dòng)條的正確比例和防止高度塌陷,顯示聲明了2956px的高度。

墓碑(Tombstones)

如之前所說(shuō),如果網(wǎng)絡(luò)延遲較大,用戶(hù)又飛快地滾動(dòng),很容易就把我們渲染的DOM節(jié)點(diǎn)都甩在千里之外。這樣就會(huì)出現(xiàn)極差的用戶(hù)體驗(yàn)。針對(duì)這種情況,我們就需要一個(gè)墓碑條目占位在對(duì)應(yīng)位置。等到數(shù)據(jù)取到之后,再代替墓碑。墓碑也可以有一個(gè)獨(dú)立的DOM元素池。并且也可以設(shè)計(jì)出一些漂亮的過(guò)渡。這種技術(shù)在國(guó)外的一些“引領(lǐng)技術(shù)潮流”的網(wǎng)站上,早已經(jīng)有了應(yīng)有。比如下圖取自Facebook:

我在“簡(jiǎn)書(shū)”APP客戶(hù)端上,也見(jiàn)過(guò)類(lèi)似的方案。當(dāng)然,人家是native...

滾動(dòng)錨定

滾動(dòng)錨定的觸發(fā)時(shí)機(jī)有兩個(gè):一個(gè)是墓碑被替換時(shí),另一個(gè)是窗口大小發(fā)生改變時(shí)(在設(shè)備發(fā)生翻轉(zhuǎn)時(shí)也會(huì)發(fā)生)。這兩種情況,都需要調(diào)整對(duì)應(yīng)的滾動(dòng)位置。

總結(jié)

當(dāng)你想提供一個(gè)高性能的有良好用戶(hù)體驗(yàn)的功能時(shí),可能技術(shù)上一個(gè)簡(jiǎn)單的問(wèn)題,就會(huì)演變成復(fù)雜問(wèn)題的。這篇文章便是一個(gè)例證。
隨著 “Progressive Web Apps” 逐漸成為移動(dòng)設(shè)備的一等公民(會(huì)嗎?),高性能的良好體驗(yàn)會(huì)變得越來(lái)越重要。
開(kāi)發(fā)者也必須持續(xù)的研究使用一些模式來(lái)應(yīng)對(duì)性能約束。這些設(shè)計(jì)的基礎(chǔ)當(dāng)然都是成熟的技術(shù)為根本。

這篇文章參考了Flicker工程師,前YAHOO工程師Stephen Woods的《Building Touch Interfaces with HTML5》一書(shū)。以及王芃前輩對(duì)于《Complexities of an Infinite Scroller》一文的部分翻譯。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/50507.html

相關(guān)文章

  • 設(shè)計(jì)無(wú)限滾動(dòng)下拉加載實(shí)踐性能頁(yè)面真諦

    摘要:論壇上有過(guò)這么一篇熱門(mén)文章,它從角度分析了無(wú)限滾動(dòng)加載的設(shè)計(jì)實(shí)踐。無(wú)限滾動(dòng)加載背后的技術(shù)挑戰(zhàn)其實(shí)比想象中要多不少。整體思路和方案設(shè)計(jì)我們要實(shí)現(xiàn)的頁(yè)面樣例如圖,它能夠做到無(wú)限下拉加載內(nèi)容。 UX Planet論壇上有過(guò)這么一篇熱門(mén)文章: Infinite Scrolling Best Practices,它從UX角度分析了無(wú)限滾動(dòng)加載的設(shè)計(jì)實(shí)踐。 無(wú)限滾動(dòng)加載在互聯(lián)網(wǎng)上到處都有應(yīng)用:豆瓣...

    Hanks10100 評(píng)論0 收藏0
  • JS或Jquery

    摘要:大潮來(lái)襲前端開(kāi)發(fā)能做些什么去年谷歌和火狐針對(duì)提出了的標(biāo)準(zhǔn),顧名思義,即的體驗(yàn)方式,我們可以戴著頭顯享受沉浸式的網(wǎng)頁(yè),新的標(biāo)準(zhǔn)讓我們可以使用語(yǔ)言來(lái)開(kāi)發(fā)。 VR 大潮來(lái)襲 --- 前端開(kāi)發(fā)能做些什么 去年谷歌和火狐針對(duì) WebVR 提出了 WebVR API 的標(biāo)準(zhǔn),顧名思義,WebVR 即 web + VR 的體驗(yàn)方式,我們可以戴著頭顯享受沉浸式的網(wǎng)頁(yè),新的 API 標(biāo)準(zhǔn)讓我們可以使用 ...

    CatalpaFlat 評(píng)論0 收藏0
  • 移動(dòng)前端 - 收藏集 - 掘金

    摘要:使用移動(dòng)設(shè)備查看頁(yè)面時(shí)會(huì)發(fā)現(xiàn),在微信瀏覽器中有頂部導(dǎo)航欄有效解決圖片使用單位邊角缺失的問(wèn)題前端掘金起因在移動(dòng)端使用布局時(shí)圖片也需要用單位。移動(dòng)端實(shí)踐前端掘金說(shuō)起,相信大家并不陌生。 Sticky Footer,完美的絕對(duì)底部 - 前端 - 掘金寫(xiě)在前面 做過(guò)網(wǎng)頁(yè)開(kāi)發(fā)的同學(xué)想必都遇到過(guò)這樣尷尬的排版問(wèn)題:在主體內(nèi)容不足夠多或者未完全加載出來(lái)之前,就會(huì)導(dǎo)致出現(xiàn)(圖一)的這種情況,原因是因?yàn)?..

    Jochen 評(píng)論0 收藏0
  • 深入理解js

    摘要:詳解十大常用設(shè)計(jì)模式力薦深度好文深入理解大設(shè)計(jì)模式收集各種疑難雜癥的問(wèn)題集錦關(guān)于,工作和學(xué)習(xí)過(guò)程中遇到過(guò)許多問(wèn)題,也解答過(guò)許多別人的問(wèn)題。介紹了的內(nèi)存管理。 延遲加載 (Lazyload) 三種實(shí)現(xiàn)方式 延遲加載也稱(chēng)為惰性加載,即在長(zhǎng)網(wǎng)頁(yè)中延遲加載圖像。用戶(hù)滾動(dòng)到它們之前,視口外的圖像不會(huì)加載。本文詳細(xì)介紹了三種延遲加載的實(shí)現(xiàn)方式。 詳解 Javascript十大常用設(shè)計(jì)模式 力薦~ ...

    caikeal 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<