列表組件抽象(4)-滾動列表及分頁說明

来源:http://www.cnblogs.com/lyzg/archive/2016/09/20/5881355.html
-Advertisement-
Play Games

這是我寫的關於列表組件的第4篇博客。前面的相關文章有: 1. 列表組件抽象(1)-概述 2. 列表組件抽象(2)-listViewBase說明 3. 列表組件抽象(3)-分頁和排序管理說明 本文介紹列表組件中我對滾動列表及滾動分頁的實現思路。 在pc端,通過滾動進行翻頁的需求非常常見;移動端也是,只 ...


這是我寫的關於列表組件的第4篇博客。前面的相關文章有:

1. 列表組件抽象(1)-概述

2. 列表組件抽象(2)-listViewBase說明

3. 列表組件抽象(3)-分頁和排序管理說明

本文介紹列表組件中我對滾動列表及滾動分頁的實現思路。

在pc端,通過滾動進行翻頁的需求非常常見;移動端也是,只不過移動端由於scroll事件觸發有延遲,必須等到屏幕停止滑動後才會觸發,而不是在用戶的手指離開屏幕就立即觸發,所以移動端最好是不用scroll事件直接做滾動翻頁,而是用iscroll這類插件提供更實時的scroll事件更好。

不管是pc還是移動端,滾動翻頁列表的特點都是差不多的:

1)基本上由以下幾個部分組成:數據列表,頂部的載入中提示,底部的載入中提示,沒有更多了,沒有找到記錄。正是按照這個思路,所以我把滾動列表的html結構設計成:

image

2)跟其它列表組件不同的是,滾動列表在請求新的數據後,有2種方式來渲染新的數據。一種是跟其它列表組件一樣,直接把原來的列表內容替換;另一種是將新數據追加在原有的列表內容之後。第1種通常用於直接更改列表的查詢條件時使用;第2種用於翻頁查詢或者刷新操作。

3)在前面的幾個部分中,有兩個載入中的提示,都是用來提升用戶體驗的東西。頂部載入提示用於條件查詢,底部載入提示用於翻頁查詢。從它們在html中的位置也能看出來。

4)載入更多的按鈕,一是防止滾動事件失效而準備的,二是有些場景可能會禁用掉滾動翻頁,所以就要提供直接點擊按鈕的手工翻頁。

5)沒有更多了這個部分,在翻頁查詢後,根據數據結果判斷沒有更多的數據時顯示。

6)沒有找到記錄的這個部分用於在列表首次查詢時,如果數據為空時顯示。

7)當通過滾動或者滑動操作,使得滾動列表隱藏於可視區域之下的部分不斷往上滾動,併在達到某一個臨界點的時候,觸發翻頁查詢,將下一頁的數據追加到數據列表後面進行顯示。

針對以上的這些需求邏輯,考慮pc端和移動端的場景,我寫了兩個組件分別用於實現滾動列表。同時與這兩個列表組件一起使用的還有另外兩個分頁組件,它們兩兩之間是配套使用的。

首先是用於實現pc端,可相對window或者某個DOM元素進行滾動分頁的列表組件scrollListView以及它配套的分頁組件scrollPageView組件,源碼分別是:

https://github.com/liuyunzhuge/blog/blob/master/form/src/js/mod/listView/scrollListView.js

https://github.com/liuyunzhuge/blog/blob/master/form/src/js/mod/listView/scrollPageView.js

然後是用於移動端,結合iscroll一起使用的iscrollListView和iscrollPageView組件,源碼分別是:

https://github.com/liuyunzhuge/blog/blob/master/form/src/js/mod/listView/iscrollListView.js

https://github.com/liuyunzhuge/blog/blob/master/form/src/js/mod/listView/iscrollPageView.js

針對以上組件有以下demo可以查看相關功能演示:

pc端相對window滾動分頁demo:http://liuyunzhuge.github.io/blog/form/dist/html/listView_2.html

pc端相對某個DOM元素滾動分頁demo:http://liuyunzhuge.github.io/blog/form/dist/html/listView_3.html

移動端滾動分頁demo:http://liuyunzhuge.github.io/blog/form/dist/html/listView_4.html

後面的部分說明以上組件的要點。不過由於iscrollListView直接繼承了scrollListView,實現非常簡單;iscrollPageView的實現思路也跟scrollPageView差不多。所以後面只介紹scrollListView和scrollPageView的相關內容。

先來看scrollListView.js。

首先,代碼結構還是跟前幾篇博客介紹的組件都差不多,所以這裡不再覆述。defaults是這樣定義的:

var DEFAULTS = $.extend({}, ListViewBase.DEFAULTS, {
    //列表容器的選擇器
    dataListSelector: '.data_list',
    //頂部載入中的html
    loadingTopHtml: '<div class="loading_top">載入中...</div>',
    //沒有結果的html
    noContentHtml: '<div class="no_content">沒有找到相關記錄:(</div>',
    //底部載入中的html
    loadingBottomHtml: '<div class="loading_bottom">載入中...</div>',
    //沒有更多的html
    noMoreHtml: '<div class="no_more">沒有更多了</div>',
    //載入更多的html
    loadMoreHtml: '<a href="javascript:;" class="btn_load_more">載入更多</a>'
});

主要是用來定義滾動列表的那幾個組成部分。如果不想用預設值,那麼在實例化組件的時候,傳入想要設置的option就行了。

scrollListView繼承了listViewBase,為了增加自己的初始化邏輯,所以用到了initMiddle這個模板方法,併在其中做了一些jq對象緩存,以及內部狀態管理初始化的邏輯:

initMiddle: function () {
    var opts = this.options,
        $element = this.$element,
        $data_list = this.$data_list = $element.find(opts.dataListSelector),
        $load_more = this.$load_more = $(opts.loadMoreHtml).appendTo($element),
        $no_content = $(opts.noContentHtml).appendTo($element),
        $loading_top = $(opts.loadingTopHtml).prependTo($element),
        $loading_bottom = $(opts.loadingBottomHtml).appendTo($element),
        $no_more = $(opts.noMoreHtml).appendTo($element);

    $load_more.css('display', 'block');

    //狀態管理:初始化完畢,頂部載入中,底部載入中,沒有結果,沒有更多,載入完畢
    var states = this.states = {
        init: function () {
            $data_list.show();
            $load_more.hide();
            $no_content.hide();
            $loading_top.hide();
            $loading_bottom.hide();
            $no_more.hide();
        },
        loading_top: function () {
            $data_list.show();
            $load_more.hide();
            $no_content.hide();
            $loading_top.show();
            $loading_bottom.hide();
            $no_more.hide();
        },
        loading_bottom: function () {
            $data_list.show();
            $load_more.hide();
            $no_content.hide();
            $loading_top.hide();
            $loading_bottom.show();
            $no_more.hide();
        },
        no_content: function () {
            $data_list.hide();
            $load_more.hide();
            $no_content.show();
            $loading_top.hide();
            $loading_bottom.hide();
            $no_more.hide();
        },
        loaded: function () {
            $data_list.show();
            $load_more.show();
            $no_content.hide();
            $loading_top.hide();
            $loading_bottom.hide();
            $no_more.hide();
        },
        no_more: function () {
            $data_list.show();
            $load_more.hide();
            $no_content.hide();
            $loading_top.hide();
            $loading_bottom.hide();
            $no_more.show();
        },
        'set': function (action) {
            this.curState = action;
            this[action]();
        },
        isNomore: function () {
            return this.curState == 'no_more';
        },
        isNoContent: function () {
            return this.curState == 'no_content';
        }
    };

    states.set('init');
},

以上代碼中的那個states對象用來實現內部的狀態管理,可以看作一個簡單的狀態機。採用這個做法的原因,一是為了滿足最前面介紹滾動列表組件特點時描述的那些需求邏輯,二是為了讓這些UI控制邏輯看起來更清晰。有了它,我只要在何時的時機,改變下組件的狀態,就能列表組件顯示不同的內容了。比較簡單好理解。

然後通過createPageView來實現分頁組件的初始化邏輯。這裡就得使用scrollPageView來實例化了:

createPageView: function () {
    var pageView,
        opts = this.options;

    if (opts.pageView) {
        //初始化分頁組件
        delete opts.pageView.onChange;
        pageView = new ScrollPageView($.extend(opts.pageView, {
            $loadMore: this.$load_more,
            $element: this.$element
        }));
    }
    return pageView;
},

然後把scrollPageView需要的幾個dom對象以option的形式傳給了它。

考慮到滾動列表組件的特殊性,我還用到了listViewBase的其它幾個模板方法來實現滾動列表的需求。

首先是beforeQuery:

beforeQuery: function (clear) {
    //如果clear為true,則顯示頂部的載入狀態,表示正在進行新條件的列表查詢
    //否則顯示底部的載入狀態,表示正在進行翻頁查詢
    this.states.set(clear ? 'loading_top' : 'loading_bottom');
},

這個方法會接受一個參數clear,為true則表示進行條件查詢,否則表示進行翻頁查詢。這個方法的作用在於查詢前顯示載入提示。

然後是querySuccess:

querySuccess: function (html, args) {
    var pageView = this.pageView,
        rows = this.originalRows,
        method = args.clear ? 'html' : 'append';//根據查詢類型,來決定要如何處理渲染新的數據

    //沒有查到結果
    if (rows.length == 0 && pageView.pageIndex == 1) {
        this.states.set('no_content');
        return;
    }

    //沒有更多
    if (rows.length < pageView.pageSize) {
        this.states.set('no_more');
        html.length && this.$data_list[method](html);
        return;
    }

    //載入完畢
    this.states.set('loaded');
    this.$data_list[method](html);
},

它用來實現請求成功的後的邏輯,最重要的當然是渲染數據。但是考慮到列表組件的需求,還得根據多方面的參數,判斷該把列表設置為什麼樣的狀態。請求成功後的結果無非三種,沒有查到數據,沒有更多,載入成功。這三個狀態,根據分頁信息和記錄數就能判斷清楚,見源碼裡面if邏輯的寫法。

然後是queryError:

queryError: function () {
    this.states.set('loaded');
},

這個主要是在請求失敗的時候,還原列表的狀態而已。

最後是afterQuery:

afterQuery: function () {
    if (this.states.isNoContent() || this.states.isNomore()) {
        this.pageView.disable();
    }
}

它在請求完成之後,判斷如果是沒有數據或者沒有更多的狀態的話,就禁用掉分頁組件,免得用戶操作不慎導致還會發出一些查不到數據的請求。

以上就是scrollListView實現的核心了,只有100多行。

再來看scrollPageView。

它的defaults定義如下:

var DEFAULTS = $.extend({}, PageViewBase.DEFAULTS, {
    //載入更多的按鈕
    $loadMore: null,
    //滾動元素
    $element: null,
    //滾動區域的目標元素,如果沒有傳,預設就是window對象,用來註冊scroll事件
    $target: null,
    //是否啟用滾動翻頁
    scrollPage: true,
    //滾動元素底邊跟滾動可視區域底邊的距離,它是滾動翻頁的臨界點
    offset: -100,
    //滾動事件的綁定時的延遲時間
    scrollBindDelay: 0,
    //滾動事件的節流間隔
    throttle: 100,
});

應該好理解。offset的作用後面會繼續說明,scrollBindDelay是用來延遲滾動事件綁定的。為啥會搞這個,是因為chrome瀏覽器有個特性,如果在瀏覽網頁的時候,刷新之後,滾動條會恢復到刷新前瀏覽的位置,並且它這個自動恢復也會觸發滾動事件。那麼當列表組件初始化完畢之後,很有可能會發出兩次查詢請求,一次是由初始化調用發出的,一次是由自動恢復的滾動事件發出的。所以加上這個option,有利於控制列表初始化後的首次請求。$loadMore用於註冊點擊事件,添加手動翻頁的邏輯。$element表示滾動列表相關的dom對象。$target表示滾動相對的目標對象,如果不傳,就指向window對象。

scrollPageView內部提供了簡單的節流函數來做滾動事件回調的節流控制:

//簡單函數節流
function throttle(func, interval) {
    var last = Date.now();
    return function () {
        var now = Date.now();
        if ((now - last) > interval) {
            func.apply(this, arguments);
            last = now;
        }
    }
}

也提供了獲取css屬性在瀏覽器重繪之後的值的函數:

//用來獲取css某個屬性經過瀏覽器重繪之後的值
function getComputedValue(element, prop) {
    var computedStyle = window.getComputedStyle(element, null);
    if (!computedStyle) return null;
    if (computedStyle.getPropertyValue) {
        return computedStyle.getPropertyValue(prop);
    } else if (computedStyle.getAttribute) {
        return computedStyle.getAttribute(prop);
    } else if (computedStyle[prop]) {
        return computedStyle[prop];
    }
}

其它代碼倒是沒啥好補充的,重點看下滾動事件相關的翻頁控制邏輯,我就只貼了相關的匿名函數代碼了:

function () {
    if (that.disabled) return;

    var targetHeight, bottom;

    //目標元素的clientHeight作為滾動區域的高度
    //bottom:滾動元素的底邊到滾動區域頂邊的距離

    if (!opts.$target) {
        targetHeight = document.documentElement.clientHeight;
        bottom = opts.$element[0].getBoundingClientRect().bottom;
    } else {
        targetHeight = opts.$target[0].clientHeight;

        var targetRect = opts.$target[0].getBoundingClientRect(),
            targetBorderTop = parseInt(getComputedValue(opts.$target[0], 'border-top-width')),
            elemRect = opts.$element[0].getBoundingClientRect();

        //如果target是其它的html元素,由於都是採用boundingClientRect來計算的,所以要減去目標元素頂部邊框的寬度,這樣才不會有誤差
        bottom = elemRect.bottom - targetRect.top - (isNaN(targetBorderTop) ? 0 : targetBorderTop);
    }

    //bottom+ opts.offset等於一條臨界線
    //如果opts.offset小於0,那麼這條臨界線就位於滾動元素底邊再往上opts.offsets的距離的位置
    //如果Opts.offset大於0,那麼這條臨界線就位於滾動元素底邊再往下|opts.offsets|的絕對距離的位置
    //翻頁觸發的條件是:這條臨界線剛好出現在滾動區域裡面的時候
    if ((bottom + opts.offset) < targetHeight) {
        pageIndexChange(that.pageIndex + 1, that);
    }
}

以上的思路也比較簡單,只要判斷列表元素的底部跟目標對象的可視區域的底部達到臨界距離即可,這個臨界距離就是defaults中定義的offset值。以相對window滾動為例說明如何來做這個判斷:

image

根據上圖,可以得知翻頁臨界的判斷條件就是上圖中臨界線到目標對象可視區域頂邊的距離小於目標對象可視區域的高度。這個圖雖然是以相對window的滾動情況來說明問題的,但是相對DOM對象進行滾動的判斷方式跟這個是一模一樣的,只要我們能夠得到DOM對象的可視區域高度以及臨界線到DOM對象可視區域頂邊的距離即可。我的代碼中是利用getBoundingClientRect來求的,相當於還是以瀏覽器的可視區域的頂邊作為參考線,不過考慮到普通的DOM對象可能也有頂部邊框的問題,在計算最後的臨界線到DOM對象可視區域的頂邊距離時,減去了DOM對象頂部變寬的寬度。只有這樣得出的臨界線距離才是相對於DOM對象可視區域頂邊而言的。

以上就是本文的全部內容,介紹了我想要補充說明的關於滾動列表組件的部分。這一塊內容,我覺得沒有特別廣的適用性,畢竟各個產品對滾動翻頁這種需求的邏輯可能也不盡相同,我這邊提供的是我現有項目中的實現思路,可能只能對您有一定的參考價值。所以要是有不妥的,歡迎隨時幫我指正出來。謝謝閱讀:)


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 文檔中的每個元素被描繪為矩形盒子。渲染引擎的目的就是判定大小,屬性——比如它的顏色、背景、邊框方面——及這些盒子的位置。在CSS中,這些矩形盒子用標準盒模型來描述。這個模型描述了一個元素所占用的空間。每一個盒子有四條邊界:外邊距邊界margin, 邊框邊界border, 內邊距邊界padding與內 ...
  • 實現效果 實現代碼 ...
  • 如果html為 <input type="radio" id="test" name="test" value="1" /><span>測試1</span>則js為:$("input[name='test']:checked").next("span").text() 如果html為 <input ...
  • 邊框漸變: 文字漸變:(只支持-webkit-) 背景漸變: ...
  • 有IOS的開關模擬,當然也有MIUI的開關模擬 看到設置選項裡面的開關樣式,突發奇想地來試試 最終效果如圖: 實現過程 1. 選項框checkbox 模擬開關當然需要一個選項框,這裡用到了覆選框checkbox 2. 理解開關的過程 點擊開關按鈕,則開啟或關閉。原生的checkbox無法做到圖示的效 ...
  • 1.佈局 ① 使用 <div> 元素的 HTML 佈局,<div> 元素常用作佈局工具,因為能夠輕鬆地通過 CSS 對其進行定位。 ② 使用 HTML5 的網站佈局 HTML5 提供的新語義元素定義了網頁的不同部分: HTML5 語義元素: ③ 使用表格的 HTML 佈局 。<table> 元素不是 ...
  • 一、簡介 該demo主要實現QQ分享、微信分享和新浪微博分享。(調試包請到論壇掃描對應二維碼下載) 二、效果圖 三、相關討論 http://bbs.deviceone.net/forum.php?mod=viewthread&tid=852&extra=page%3D1 四、源碼地址 https:/ ...
  • 用js封裝一些常用的jquery方法 記錄一下 hasClass:判斷是否有class addClass:增加class removeClass:移除class ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...