在页面顶端,按住页面往下拉动一段距离再松开,页面就加载新的内容或刷新,这样的交互设计几乎已经成为了诸多移动端App的标配。最适合这个功能的莫过于微博和推特,以及一些需要经常检查是否有新内容的App。
今天心血来潮,尝试了几乎一下午,在几个难点卡了不短的时间,最后实现了最基础的功能。
![](https://img.haomeiwen.com/i749865/4dd4a8badf2bc0b0.gif)
我们来看看这个过程。
先说说思路:
-
页面顶端隐藏了一个元素,只有把主页面下拉才会出现,它提醒用户松开即可刷新,并且在请求数据时显示一个 loading 提醒;
-
可以用负的 margin-top 把这个元素藏起来,页面下拉多长,就给它增加多少 margin-top,直到它完全显示出来;
-
我们需要监听3个事件:点击、拉动和松开。我们可以用拉动时的Y坐标减去点击时的Y坐标来获取拉动距离,把这个距离加到隐藏元素的 margin-top 上;监听松开是为了中途放弃时隐藏元素可以归位。
-
当隐藏元素完全暴露之后,就执行刷新操作。
接下来看看代码:
HTML 结构:
<body>
<div class="container">
<div class="info">松开刷新</div>
<div class="main">正文内容</div>
</div>
</body>
CSS(隐去了无关功能实现的样式)
.container {
width: 100vw;
height: 100vh;
border: 4px solid red;
}
.info {
height: 40px;
border: 4px solid blue;
margin-top: -40px;
}
.main {
border: 4px solid green;
}
JavaScript
const container = document.querySelector('.container');
const info = document.querySelector('.info');
const main = document.querySelector('.main');
let mouseDown = false;
let swipeStartingY = 0;
let swipeDownDistance = 0;
main.addEventListener('mousedown', function(e) {
mouseDown = true;
swipeStartingY = e.clientY;
});
main.addEventListener('mousemove', function(e) {
if (!mouseDown) {
return
}
swipeDownDistance = e.clientY - swipeStartingY;
info.style.marginTop = swipeDownDistance - 40 + 'px';
});
document.addEventListener('mouseup', function() {
mouseDown = false;
if (swipeDownDistance >= 40) {
info.textContent = "刷新中";
console.log('执行刷新操作');
return;
}
info.style.marginTop = "-40px";
});
现在下拉已经可以让隐藏元素 info 出现在视野中了,但是 info 会被拉出远超过自身高度的距离,更糟糕的是,info 还可以被往上推。所以我们要添加一些限制:
let mouseDown = false;
let swipeStartingY = 0;
let swipeDownDistance = 0;
main.addEventListener('mousedown', function(e) {
mouseDown = true;
swipeStartingY = e.clientY;
});
main.addEventListener('mousemove', function(e) {
if (!mouseDown) {
return
}
swipeDownDistance = e.clientY - swipeStartingY;
if (swipeDownDistance > 40) {
swipeDownDistance = 40;
}
if (swipeDownDistance <= 40 && swipeDownDistance >= 0) {
info.style.marginTop = swipeDownDistance - 40 + 'px';
}
});
document.addEventListener('mouseup', function() {
mouseDown = false;
if (swipeDownDistance >= 40) {
info.textContent = "刷新中";
console.log('执行刷新操作');
return;
}
info.style.marginTop = "-40px";
});
现在我们给拉动距离 swipeDownDistance 设置了一个上限,上限就是 info 的高度,如果拉动距离超过上限或者为负,不要改变 info 的 margin-top,这样 info 的活动范围就被限制在自身高度内了。
一个体验问题
但是还有一个无关要紧但影响体验的问题:如果我们拖住页面不放,继续往下拉,拉出远超过 info 高度的距离,还是不放手,这时如果回头往上拉,info 不会在你开始往上拉的那一刻就缩回去。这是因为虽然我们限制了 swipeDownDistance 的上限,但 mousemove 事件中的 e.clientY 太大了,导致 swipeDownDistance 回不到 0 ~ 40 的范围内,因此不能满足改变 info 的 margin-top 的条件。
我的解决办法是,当下拉得太远时,重新定义一下记录拉动起点的 swipeStartingY,让它距现在鼠标所在的位置(e.clientY)最多只有一个 info 高度的距离,这样无论何时往上拉,swipeDownDistance 都会在0~40的范围内减小,从而满足改变 info 的 margin-top 的条件,info 自然就能被推回去了。
main.addEventListener('mousedown', function(e) {
mouseDown = true;
swipeStartingY = e.clientY;
});
main.addEventListener('mousemove', function(e) {
if (!mouseDown) {
return
}
swipeDownDistance = e.clientY - swipeStartingY;
if (swipeDownDistance > 40) {
swipeDownDistance = 40;
swipeStartingY = e.clientY - 40; // 添加这一句就能解决问题
}
if (swipeDownDistance <= 40 && swipeDownDistance >= 0) {
info.style.marginTop = swipeDownDistance - 40 + 'px';
}
});
document.addEventListener('mouseup', function() {
mouseDown = false;
if (swipeDownDistance >= 40) {
info.textContent = "刷新中";
console.log('执行刷新操作');
return;
}
info.style.marginTop = "-40px";
});
到这里,下拉刷新最基本的功能和交互体验都完成了。不过这个实现还有一个坑,那就是放开点击时,info 是瞬间归位的,而最佳体验应该是让它慢慢回去。要实现这个目的就要添加一个动画,以后再填吧。
完整代码和预览:
网友评论