寫在前面 列表一直是展示數據的一個重要方式,在手機端的列表展示又和PC端展示不同,畢竟手機端主要靠滑。之前手機端之前一直使用的 ,但是 本身其實有很多相容性 ,想改動一下需求也很不容易,可以看我之前寫的這一文章 "IScroll那些事——內容不足時下拉刷新" (這裡並不是說 不好,裡面對手機、瀏覽器 ...
寫在前面
列表一直是展示數據的一個重要方式,在手機端的列表展示又和PC端展示不同,畢竟手機端主要靠滑。之前手機端之前一直使用的IScroll
,但是IScroll
本身其實有很多相容性BUG
,想改動一下需求也很不容易,可以看我之前寫的這一文章IScroll那些事——內容不足時下拉刷新(這裡並不是說IScroll
不好,裡面對手機、瀏覽器相容性都做了大量的處理,只是當遇到bug
時或者想改一下需求時不時特別方便,畢竟是一個這麼大的庫)。因此也一直想瞭解一下這類列表的實現原理,萬一真到時候可以自己寫一個,這樣自己維護自己的代碼也可以更加得心應手。
下麵主要是閱讀了餓了麽UI
組件庫mint-ui
然後編寫出來的效果圖:
代碼請看這裡:github
1 核心解析
1.1 整體思路圖
1.2 HTML結構
<div class="page-loadmore-wrapper">
<div id='loadMore' class="loadmore">
<div id="loadMoreContent" class="loadmore-content">
<!-- 這裡是頂部狀態生成的地方 -->
<ul class="page-loadmore-list" id="loadMoreList"></ul>
<!-- 這裡是底部狀態生成的地方 -->
</div>
</div>
</div>
這裡有一點需要註意,滑動內容部分需要一個設置為overflow:scroll
的容器,如果不設置,就會一直向上找,直到最後返回window
,這點在下麵的代碼可以體現
/**
* 獲取滾動容器
* @param DOM element
* @return
*/
getScrollEventTarget: function(element) {
var currentNode = element;
while (currentNode && currentNode.tagName !== 'HTML' &&
currentNode.tagName !== 'BODY' && currentNode.nodeType === 1) {
var overflowY = document.defaultView.getComputedStyle(currentNode).overflowY;
if (overflowY === 'scroll' || overflowY === 'auto') {
return currentNode;
}
currentNode = currentNode.parentNode;
}
return window;
}
1.3 滑動彈性與狀態變化
這兩點我們在touchmove
事件中可以找到相應的代碼:
// 彈性滑動
// 這裡用手指滑動的位移除以比例繫數來得出內容應該滑動的位移
// 因此這裡的內容滑動的位移一定是會小於手指滑動的位移的,除非你將系列設置為小於1,那我就沒得話說了
// 於是就造成了一種滑動又滑不動的感覺
var distance = (_this.currentY - _this.startY) / _this.config.distanceIndex;
// 下移條件
// 1. 必須有刷新函數
// 2. 方向為向下
// 3. 初始的scrollTop為0
// 4. 狀態不為載入中
if (typeof _this.config.topMethod === 'function' && _this.direction === 'down' &&
_this.getScrollTop(_this.scrollEventTarget) === 0 && _this.topStatus !== 'loading') {
event.preventDefault();
event.stopPropagation();
if (_this.config.maxDistance > 0) {
_this.translate = distance <= _this.config.maxDistance ? distance - _this.startScrollTop : _this.translate;
} else {
_this.translate = distance - _this.startScrollTop;
}
if (_this.translate < 0) {
_this.translate = 0;
}
// 這裡是滑動中(touchmove)時應該判斷的
// 如果滑動的位移操作了我們設置的值就置為pull
// 同時更新狀態,改變內容的transform
// 同理可以在向上拉動的時候找到相應的代碼,這裡不作累述
_this.topStatus = _this.translate >= _this.config.topDistance ? 'drop' : 'pull';
Event.trigger('topStatus', _this.topStatus);
Event.trigger('translate', _this.translate);
}
// 在向上滑動的過程中,還需要時刻檢測是否已經滑倒最下麵了
// 如果沒有滑倒最下麵,則正常滑動,否則,載入新的數據
if (_this.direction === 'up') {
_this.bottomReached = _this.bottomReached || _this.checkBottomReached();
}
1.4 載入數據
當狀態在loading
的時候,就是載入數據的時候,而只有當滑動停止之後,狀態才需要置為loading
,因此載入數據的代碼需要在touchend
中執行,具體看下麵代碼註釋:
// 這裡分析向下刷新數據時候的代碼
// 向上部分的類似,可以自行去瞭解
if (_this.direction === 'down' && _this.getScrollTop(_this.scrollEventTarget) === 0 && _this.translate > 0) {
// 這裡觸發topDropped為true是為了給內容部分加上動畫
Event.trigger('topDropped', true);
// 判斷當前是否已經拉倒了足夠的位移,只有狀態為drop的時候放手才會載入數據
if (_this.topStatus === 'drop') {
// 重置狀態為loading,改變位移
Event.trigger('topStatus', 'loading');
// 向下移動50px像素是為了展示出loading的文字
Event.trigger('translate', 50);
// 載入數據
_this.config.topMethod(function() {
var args = [].slice.call(arguments);
_this.onTopLoaded.apply(_this, args);
});
} else {
// 如果向下拉動狀態仍為pull,說明拉動的距離很小
Event.trigger('translate', 0);
Event.trigger('topStatus', 'pull');
}
}
1.5 上拉載入數據完成之後
這裡與下拉刷新有一點小小的不同,這裡貼一下代碼:
onBottomLoaded: function(list, isAllLoaded) {
Event.trigger('bottomStatus', 'pull');
Event.trigger('bottomDropped', false);
Event.trigger('data', list);
// 這裡給scrollEventTarget設置了scrollTop為50是為了防止跳動
if (this.scrollEventTarget === window) {
document.body.scrollTop += 50;
} else {
this.scrollEventTarget.scrollTop += 50;
}
Event.trigger('translate', 0);
this.bottomAllLoaded = isAllLoaded;
}
1.6 關於數據初始化填充
在數據內容不足一屏時,如果設置了autoFill
欄位為true
的話,會自動調用一遍bottomMethod
來填充數據
fillContainer: function() {
var _this = this;
// 如果自動填充
if (this.config.autoFill) {
// 根據滾動容器來判斷當前數據是否已經填充滿容器
if (this.scrollEventTarget === window) {
this.containerFilled = this.$el.getBoundingClientRect().bottom >=
document.documentElement.getBoundingClientRect().bottom;
} else {
this.containerFilled = this.$el.getBoundingClientRect().bottom >=
this.scrollEventTarget.getBoundingClientRect().bottom;
}
// 如果數據沒有填充滿容器,則載入數據
if (!this.containerFilled) {
// 這裡算是一點小遺憾,為了在自動載入loading的時候,顯示出狀態
// 將內容部分位移了-50px,這就是為什麼在自動載入的時候會出現一個跳動的過程
Event.trigger('bottomStatus', 'loading');
Event.trigger('translate', -50);
var data = this.config.bottomMethod(function(list) {
Event.trigger('data', list);
Event.trigger('bottomStatus', 'pull');
Event.trigger('translate', 0);
});
}
}
},
2 總結
最開始會認為這樣的效果實現起來會比較複雜(不過實際上確實也寫了快500到600行代碼了