1.前言
下拉刷新和上拉加载这两种交互方式通常出现在移动端中,本质上等同于PC网页中的分页,只是交互形式不同。
开源社区也有很多优秀的解决方案,如iscroll
、better-scroll
、pulltorefresh.js
库等等。
下面通过原生的方式实现上拉加载,下拉刷新,有助于对第三方库有更好的理解与使用。
2.上拉加载
如下看一张图:
我们可以知道:实现上拉加载的关键点是判断页面触底,或者判断是否快要触底。即scrollTop+clientHeight>= scrollHeight
。
这几个属性我在之前三大家族文章https://blog.youkuaiyun.com/fageaaa/article/details/145728760里面详细讲解过。下面来回忆一下:
scrollTop
:滚动视窗的高度距离window顶部的距离,它会随着往上滚动而不断增加,初始值是0,它是一个变化的值clientHeight
:它是一个定值,表示屏幕可视区域的高度;scrollHeight
:页面不能滚动时也是存在的,此时scrollHeight等于clientHeight。scrollHeight表示body所有元素的总长度(包括body元素自身的padding)。
简单实现:
let clientHeight = document.documentElement.clientHeight; //浏览器高度
let scrollHeight = document.body.scrollHeight;
let scrollTop = document.documentElement.scrollTop;
let distance = 50; //距离视窗还用50的时候,开始触发;
if ((scrollTop + clientHeight) >= (scrollHeight - distance)) {
console.log("开始加载数据");
}
下面再看一个详细的例子:
如上图,我们需要实现的是:页面总共要加载1000条数据,但不可能一开始直接一次性加载在页面上。如图,一次性只加载20条数据,所以一开始页面加载了20条数据,会有一个滚动条,但这个滚动条距离底部很近(大概两个多条目高度的距离,因为最底部条目是item18,总共有20个条目)。当我们向下移动滚动条时候,当发现快要触底时候,我们会自动再加载20条数据,向上滚动时候不加载,因为向上滚动时的条目都是已经加载在页面上的条目了,不用再加载。依次类似诸如此类。
如下我们实现一下代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
<style>
body,
html {
padding: 0px;
margin: 0px;
width: 100%;
height: 100%;
overflow: hidden;
}
ul {
margin: 0;
width: 90%;
height: 100%;
overflow-y: auto;
list-style: none;
}
ul > li {
width: 100%;
height: 40px;
}
ul > li:nth-of-type(2n + 1) {
background-color: aqua;
}
ul > li:nth-of-type(2n) {
background-color: antiquewhite;
}
</style>
</head>
<body>
<ul></ul>
<script>
//初始化一个1000大小的数组-->
// 真实开发中,这个数据是从后台拿到的
let arr = [];
for (let i = 0; i < 1000; i++) {
arr.push(i + 1);
}
//获取这次加载的条目数据,相当于获取分页数据
function getNewArr(start) {
if (start >= 1000) {
return [];
} else if (start + 20 >= 1000) {
return arr.slice(start);
} else {
return arr.slice(start, start + 20);
}
}
//加载新的li条目
function createLiElement(elements) {
let ulBox = document.querySelector("ul");
let documentFragment = document.createDocumentFragment();
for (let i = 0; i < elements.length; i++) {
let li = document.createElement("li");
li.textContent = "Item " + elements[i];
documentFragment.appendChild(li);
}
ulBox.appendChild(documentFragment);
}
let currentCount = 0;
let newArr = getNewArr(currentCount);
let ulBox = document.querySelector("ul");
//刚开始进入页面时,需要加载一次数据
createLiElement(newArr);
let clientHeight = ulBox.clientHeight; //浏览器高度
let distance = 50; //距离视窗还用50的时候,开始触发;
ulBox.addEventListener("scroll", function () {
let scrollTop = ulBox.scrollTop;
let scrollHeight = ulBox.scrollHeight;
//判断是否触底
if (scrollTop + clientHeight >= scrollHeight - distance) {
console.log("开始加载数据");
currentCount++;
let currentArr = getNewArr(currentCount * 20);
createLiElement(currentArr);
}
});
</script>
</body>
</html>
3.下拉刷新
下拉刷新的本质是页面本身置于顶部时,用户下拉时需要触发相应动作。
关于下拉刷新的原生实现,主要分成三步:
- 监听原生
touchstart
事件,记录其初始位置的值,e.touches[0].pageY
; - 监听原生
touchmove
事件,记录并计算当前滑动的位置值与初始位置值的差值,大于0表示向下拉动,并借助CSS3的translateY属性使元素跟随手势向下滑动对应的差值,同时也应设置一个允许滑动的最大值; - 监听原生
touchend
事件,若此时元素滑动达到最大值,则触发callback,同时将translateY重设为0,元素回到初始位置。
下面举个详细例子,html结构如下:
<style>
#refreshContainer>li{
width: 100%;
height: 120px;
}
#refreshContainer>li:nth-of-type(3n){
background-color: antiquewhite;
}
#refreshContainer>li:nth-of-type(3n+1){
background-color: aqua;
}
#refreshContainer>li:nth-of-type(3n+2){
background-color: aquamarine;
}
</style>
<main>
<!-- 用非桌面的环境去实验-->
<p class="refreshText"></p>
<ul id="refreshContainer">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
<li>10</li>
<li>11</li>
<li>12</li>
<li>13</li>
<li>14</li>
<li>15</li>
<li>16</li>
<li>17</li>
<li>18</li>
<li>19</li>
<li>20</li>
<li>21</li>
<li>22</li>
<li>23</li>
<li>24</li>
<li>25</li>
<li>26</li>
<li>27</li>
<li>28</li>
<li>29</li>
<li>30</li>
<li>31</li>
<li>32</li>
<li>33</li>
<li>34</li>
<li>35</li>
<li>36</li>
<li>37</li>
<li>38</li>
<li>39</li>
<li>40</li>
<li>41</li>
<li>42</li>
<li>43</li>
<li>44</li>
<li>45</li>
<li>46</li>
<li>47</li>
<li>48</li>
<li>49</li>
<li>50</li>
<li>51</li>
<li>52</li>
<li>53</li>
<li>54</li>
<li>55</li>
<li>56</li>
</ul>
</main>
监听touchstart事件,记录初始的值:
let _element = document.getElementById('refreshContainer');
let _refreshText = document.querySelector('.refreshText');
let _startPos = 0; // 初始的值
let _transitionHeight = 0; // 移动的距离
_element.addEventListener('touchstart', function (e) {
_startPos = e.touches[0].pageY; // 记录初始位置
_element.style.position = 'relative';
_element.style.transition = 'transform 0s';
}, false);
监听touchmove移动事件,记录滑动差值:
_element.addEventListener('touchmove', function(e) {
// e.touches[0].pageY 当前位置
_transitionHeight = e.touches[0].pageY - _startPos; // 记录差值
if (_transitionHeight > 0 && _transitionHeight < 60) {
_element.style.transform = 'translateY('+_transitionHeight+'px)';
//因为这时候还没松开手,所以这时候在页面上面空白的一部分会有文字或者图标让用户知道此刻马上要进行刷新,这儿我们就用下面'用户正在下拉刷新操作'这个文字来简单表示
_refreshText.innerText = '用户正在下拉刷新操作';
if (_transitionHeight > 55) {
_refreshText.innerText = '释放更新';
}
}
}, false);
最后,就是监听touchend离开的事件:
_element.addEventListener('touchend', function(e) {
_element.style.transition = 'transform 0.5s ease 1s';
_element.style.transform = 'translateY(0px)';
_refreshText.innerText = '更新中...';
// 这后面要写一下更新数据的操作
// todo...
}, false);
总结一下上面的过程:
- 当前手势滑动位置与初始位置差值大于零时,提示正在进行下拉刷新操作
- 下拉到一定值时,显示松手释放后的操作提示
- 下拉到达设定最大值松手时,执行回调,提示正在进行更新操作