原生js实现移动端滑动反弹效果
纵向滑动效果演示
横向滑动效果演示
请在谷歌浏览器移动端模拟下查看
具体实现思路
触摸事件(touch event)可响应用户手指(或触控笔)对屏幕或者触控板操作,给基于触控的用户界面提供了可靠支持。
触摸事件接口是较为底层的 API,可为特定程序提供多点触控交互(比如双指手势)的支持。多点触控交互开始于一个手指(或触控笔)开始接触设备平面的时刻。随后其他手指也可触摸设备表面,并随意进行划动。当所有手指离开设备平面时,交互结束。整个交互期间,程序接收开始、移动、结束三个阶段的触摸事件。
触摸事件与鼠标事件类似,不同的是触摸事件还提供同一表面不同位置的同步触摸。TouchEvent 接口将当前所有活动的触摸点封装起来。Touch 接口表示单独一个触摸点,其中包含参考浏览器视角的相对坐标。
touchStart
当触点与触控设备表面接触时触发touchstart事件touchmove
当触点在触控平面上移动时触发touchmove事件touchend
当触点离开触控平面时触发touchend事件
详细知识可查看文档 触摸事件
我们需要用到的Touch事件触发的Event对象中的属性有changedTouches
、target
changedTouches
一个伪数组,装有触摸点信息列表target
触摸点的DOM元素对象
实现细节
- 获取需要监听的元素对象 ( 这里是 ul 列表 )
var list = document.querySelector('.list')
复制代码
- 监听
touchStart
事件
var startX = 0; //初始化
list.addEventListener('touchstart', function(e) {
e = e || window.event;
e.preventDefault(); //阻止事件的默认事件
startX = e.changedTouches[0].clientX;//触碰点距离屏幕左侧可视区的距离
})
复制代码
- 监听
touchmove
事件
var nav = document.querySelector('#nav'); //获取父容器
var centerX = 0; //初始移动距离
var maxLeft = 50; //允许左侧超出移动距离
var maxRight = -(list.offsetWidth - nav.offsetWidth + maxLeft) //允许右侧超出移动距离
list.addEventListener('touchmove', function(e) {
e = e || window.event
e.preventDefault()
var dx = e.changedTouches[0].clientX - startX; //获取触碰点在屏幕上X轴移动的距离(即横向移动距离)
var tempX = centerX + dx //累计移动距离
if (tempX > maxLeft) { //如果移动距离超过了左侧最大移动距离
tempX = maxLeft;
} else if (tempX < maxRight) { //如果移动距离超过了右侧最大移动距离
tempX = maxRight;
}
list.style.transform = 'translateX('+tempX+'px)'
// ul.style.transform = `translateY(${tempY}px)` //es6写法
})
复制代码
- 监听
touchend
事件
var maxLeftSlide = 0; //最大左侧显示距离
var maxRightSlide = -(list.offsetWidth - nav.offsetWidth + maxLeftSlide); //最大右侧显示距离
list.addEventListener('touchend', function(e) {
e = e || window.event
e.preventDefault()
var dx = e.changedTouches[0].clientX - startX; //获取最终移动距离
centerX = centerX + dx; //累加上上次移动的距离
if (centerX > maxLeftSlide) {
centerX = maxLeftSlide; //回到初始显示位置
} else if (centerX < maxRightSlide) {
centerX = maxRightSlide;
}
if(dx === 0) {
var text = document.querySelector('#text')
text.innerHTML = e.target.innerHTML
}
list.style.transition = 'transfrom .5s' //添加过渡动画
list.style.transform = 'translateX('+centerX+'px)'
// ul.style.transform = `translateY(${centerY}px)`
})
复制代码
由于在 touchmove
事件中我们允许 ul
左右滑动可以超出 maxLeft
的距离,结束时我们就需要将距离回弹回来,这里通过添加一个 css3
过渡动画来达到回弹效果
各事件触发顺序 touchstart
>
click
在所有事件中我们都阻止了默认事件(这是为了防止浏览器的默认滑动事件影响)结果导致点击事件也不会触发,那么我们需要用 Touch
来模拟点击事件,其实就是当触发了 touchStart
且移动距离为 0
我们就可以认为是点击事件
- 小小的优化
移动到边界时会发现超出边界时会出现空白背景,这样体验有点不好。
给 ul
加 50px
横向填充,然后向左平移 50px
,最后修改一下最大左侧超出距离和最大左侧显示距离
css
.list {
padding: 0 50px;
transform: translateX(-50px);
}
复制代码
js
var maxLeft = 0;
var maxLeftSlide = -50;
复制代码
最终代码
Head部分
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no">
复制代码
Document结构(以横向滑动为例)
<nav id="nav">
<ul class="list">
<li>最新</li>
<li>排行</li>
<li>国内</li>
<li>国际</li>
<li>社会</li>
<li>评论</li>
<li>深度</li>
<li>军事</li>
<li>历史</li>
<li>探索</li>
<li>图片</li>
<li>博客</li>
<li>媒体</li>
<li>视频</li>
</ul>
</nav>
<p id="text"></p>
复制代码
CSS代码
* {
margin: 0;
padding: 0;
}
#nav {
width: 100%;
overflow: hidden;
}
#nav::after {
content: '';
display: block;
visibility: hidden;
height: 0;
clear: both;
}
.list {
float: left;
white-space: nowrap;
background: #1062ac;
color: #CCCCCC;
padding: 0 60px;
transform: translateX(-50px);
}
.list>li {
display: inline-block;
padding: 10px;
}
复制代码
以上都还是简单一个目录导航布局,所有li元素进行左浮动,然后利用after伪类清楚浮动
js代码
var nav = document.querySelector('#nav');
var list = nav.children[0];
var newList = list.children[0];
var startX = 0;
var centerX = -50;
var maxLeft = 0;
var maxRight = -(list.offsetWidth - nav.offsetWidth + maxLeft);
var maxLeftSlide = -50;
var maxRightSlide = -(list.offsetWidth - nav.offsetWidth + maxLeftSlide);
list.addEventListener('touchstart', function(e) {
e = e || window.event
e.preventDefault()
list.style.transition = 'none'
startX = e.changedTouches[0].clientX;
})
list.addEventListener('touchmove', function(e) {
e = e || window.event
e.preventDefault()
var dx = e.changedTouches[0].clientX - startX;
var tempX = centerX + dx
if (tempX > maxLeft) {
tempX = maxLeft;
} else if (tempX < maxRight) {
tempX = maxRight;
}
list.style.transform = 'translateX('+tempX+'px)'
// ul.style.transform = `translateY(${tempY}px)`
})
list.addEventListener('touchend', function(e) {
e = e || window.event
e.preventDefault()
var dx = e.changedTouches[0].clientX - startX;
centerX = centerX + dx;
if (centerX > maxLeftSlide) {
centerX = maxLeftSlide;
} else if (centerX < maxRightSlide) {
centerX = maxRightSlide;
}
if(dx === 0) {
var text = document.querySelector('#text')
text.innerHTML = e.target.innerHTML
}
list.style.transition = 'transfrom .5s'
list.style.transform = 'translateX('+centerX+'px)'
// ul.style.transform = `translateY(${centerY}px)`
})