移動端效果之ScrollList


寫在前面

列表一直是展示數據的一個重要方式,在手機端的列表展示又和PC端展示不同,畢竟手機端主要靠滑。之前手機端之前一直使用的IScroll,但是IScroll本身其實有很多兼容性BUG,想改動一下需求也很不容易,可以看我之前寫的這一文章IScroll那些事——內容不足時下拉刷新(這里並不是說IScroll不好,里面對手機、瀏覽器兼容性都做了大量的處理,只是當遇到bug時或者想改一下需求時不時特別方便,畢竟是一個這么大的庫)。因此也一直想了解一下這類列表的實現原理,萬一真到時候可以自己寫一個,這樣自己維護自己的代碼也可以更加得心應手。

下面主要是閱讀了餓了么UI組件庫mint-ui然后編寫出來的效果圖:

LoadMore

代碼請看這里:github

移動端效果之swiper

移動端效果之picker

移動端效果之cellSwiper

移動端效果之IndexList

1 核心解析

1.1 整體思路圖

silu

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行代碼了😀),但是真正分析起來,其實覺得代碼還好,並沒有想像得辣么困難。遇上自己想要實現的東西,就努力地去啃吧,就像遇到了你喜歡的女孩一樣🤣


免責聲明!

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



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