web中的移动端的Touch事件和手动实现侧边栏
Touch事件
- 只针对
移动端
使用, 不支持PC端 - 主要有三个事件
-
touchstart
手指触碰屏幕的时候触发 -
touchmove
手指移动的时候触发 -
touchend
手指离开的时候
-
let oDiv = document.querySelector("div");
oDiv.addEventListener("touchstart",function () {
console.log("开始触摸");
});
oDiv.addEventListener("touchmove",function () {
console.log("移动一手指");
});
oDiv.addEventListener("touchend",function () {
console.log("结束触摸---")
});
Touch事件对象
- 我们知道PC端每一个处理事件都有一个事件对象,Touch事件也是
let oDiv = document.querySelector("div");
oDiv.addEventListener("touchstart",function (e) {
e = e || window.event;
console.log(e);
})
image.png
我们经常使用的就是
touches
,targetTouches
和changedTouches
touches
:页面上(屏幕)所有的触目点
targetTouches
:元素(容器盒子)上的触摸点,当touchend
执行的时候,event事件里面的targetTouches
不在有触摸点对象
changedTouches
:当前屏幕上刚刚接触的手指或者离开的手指
区别:
touches和targetTouches
1.如果都是将手指按到了同一个元素上
, 那么这两个对象中保存的内容是一样的
2.如果是将手指按到了不同的元素上
, 那么这个两个对象中保存的内容不一样
3.touches保存的是所有元素中的手指
, 而targetTouches保存的是当前元素中的手指
Touch事件对象中的clientX
,pageX
,screenX
-
screenX/screenY
是相对于屏幕
左上角的偏移位 -
clientX/clientY
是相对于可视区域
左上角的偏移位 -
pageX/pageY
是相对于内容
左上角的偏移位(内容区域有可能超出屏幕)
<style>
*{
padding: 0;
margin: 0;
}
div{
width: 600px;
height: 100px;
background: linear-gradient(to right, red, green);
margin-left: 50px;
margin-top: 50px;
}
</style>
<script>
let oDiv = document.querySelector("div");
oDiv.addEventListener("touchstart",function (event) {
event = event || window.event;
console.log(event.targetTouches);
let obj = event.targetTouches[0];
console.log( "clientX:"+ obj.clientX,"clientY:"+obj.clientY);
console.log("pageX:"+obj.pageX,"pageY:"+obj.pageY );
console.log("screenX:"+obj.screenX,"screenX:"+obj.screenY );
/*
screenX:是参照 页面/屏幕 左上角的
clientX:参考点是 可视区域的 左上角
pageX: 是参考 内容的左上角 (内容过宽 超出屏幕)
*/
/*
});
</script>
image.png下图 可以更改的解释
clientX
,pageX
,screenX
1.红色代表screenX,
2.黄色代表pageX,蓝色的长方格是 内容区域的超出了手机屏幕
3.手绘的蓝色是clientX
也可以参考JS操作html元素中clientX、offsetX、screenX、pageX的区别一文的概述
Touch事件的点透问题
- 当一个元素放覆盖了另一个元素, 覆盖的元素监听touch事件,而下面的元素监听click事件,并且touch事件触发后覆盖的元素就消失了, 那么就会出现点透问题
点透问题.gif注意
粉色盒子消失
,log的打印情况
<script type="text/javascript">
// 绿色盒子 和 粉色盒子 是兄弟关系
let oBox = document.querySelector(".box");
let oTap = document.querySelector(".tap");
//绿色盒子
oBox.addEventListener("click",function () {
console.log("-------");
});
//粉色盒子
oTap.addEventListener("touchstart",function () {
oTap.style.display = "none";
});
</script>
需要
提醒
的是:这种情况不是 事件冒泡
,因为2个盒子是兄弟关系,不是父子关系
出现这种情况的原因是什么呢?
原因是:
1.当我们手指触摸屏幕的时候,系统会生成2个事件,一个是
touch事件
,一个是click
事件, touch事件优先click事件执行, click事件相对会延迟 100-300毫秒
2.当
touch
事件执行的时候pink粉色
盒子的display
属性更改为none
,粉色盒子消失,而click事件
在200毫秒之后,执行的时候,发现粉色盒子已经消失,那么click事件就会执行对应的触摸点
位置的box盒子,进而触发box盒子的click事件
如何解决?
这个解决其实就是
阻止事件扩散
其实就是阻止默认事件的执行event.preventDefault()
,所以需要在 粉色盒子执行touch 和 click
事件时加上
//粉色盒子
oTap.addEventListener("touchstart",function (event) {
event = event || window.event;
oTap.style.display = "none";
//阻止事件的默认行为
event.preventDefault();
});
其他的一些解决方法 第三方插件 比如:
Fastclick
专门用来解决这个问题,Zepto
插件,注意早期的Zepto
没有处理这个点透问题
关于 移动端点击事件
的封装
点击事件的特点
1.按下和离开时间不能太久 100 - 200ms
2.按下和离开距离不能(不能移动)太远 5px
3.单根手指
function Tap(dom, fn) {
if(!(dom instanceof HTMLElement)){
throw new Error("请传入一个DOM元素");
}
let startX = 0;
let startY = 0;
let startTime = 0;
dom.ontouchstart = function (event) {
// 1.判断当前元素中有几根手指
if(event.targetTouches.length > 1){
return;
}
// 2.拿到手指按下的位置
startX = event.targetTouches[0].clientX;
startY = event.targetTouches[0].clientY;
// 3.拿到手指按下的时间
startTime = Date.now();
}
dom.ontouchend = function (event) {
// 1.判断有几根手指离开了
if(event.changedTouches.length > 1){
return;
}
// 2.拿到离开手指的位置
let endX = event.changedTouches[0].clientX;
let endY = event.changedTouches[0].clientY;
// 3.判断手指离开的位置和按下位置的距离
if(Math.abs(endX - startX) > 5 ||
Math.abs(endY - startY) > 5){
return;
}
// 4.拿到手指离开的时间
let endTime = Date.now();
// 5.判断手指离开的时间和按下的时间
if(endTime - startTime > 100){
return;
}
// console.log("单击事件");
fn && fn();
}
}
- 另外的一种方式
//封装 tap事件
function tap(element,callBack) {
//记录刚开始的触摸时间
let startTime = null;
//记录结束的时间
let endTime = null;
//是否移动的标志
let isMove = false;
//触摸开始
element.addEventListener("touchstart",function (e) {
//获取当前的时间戳
startTime = Date.now();
});
//移动
element.addEventListener("touchmove",function (e) {
//移动了
isMove = true;
});
//结束
element.addEventListener("touchend",function (e) {
//获取结束的时间戳
endTime = Date.now();
if (endTime - startTime <= 200 && isMove === false){
if (callBack){
//触摸对象返回给 回调函数
callBack(e);
}
}
// 还原初始状态
isMove = false;
});
}
手动实现侧边栏
-
三层结构
组成:有一个父级盒子Div
包括一个子级盒子ul
,ul移动,ul里面包括你的内容
原理:1.手指触摸屏幕 开始
startY
记录位置 2.手指移动记录位置endy
,其结束的偏移量offsetY = endY - startY
需要注意的是: 因为你第一次和 第二次offsetY
值不同,需要一个记录变量currentY
保存上次的offsetY
,这样在下次移动的时候,就是在上一次的基础上 滚动
<style>
*{
padding: 0;
margin: 0;
touch-action: none;
}
html,body{
width: 100%;
height: 100%;
}
ul {
list-style: none;
}
a{
text-decoration: none;
}
.box{
display: inline-block;
width: 95px;
border: 1px solid;
position: relative;
overflow: hidden;
/*高度100% 就是手机屏幕的 高度*/
/*不设置的话 ul撑开div 高度和ul的一样了*/
height: 100%;
}
.box > ul {
width: 100%;
position: relative;
}
.box > ul > li {
height: 44px;
line-height: 44px;
width: 100%;
border-bottom: 1px solid;
text-align: center;
}
</style>
image.png
- 实现
<script type="text/javascript">
let oDiv = document.querySelector(".box");
let oUl = document.querySelector("ul");
console.log(oDiv.offsetHeight,oUl.offsetHeight);
let startY = 0;
//移动的位置
let offsetY = 0;
//保存上一次移动的位置
let currentY = 0;
//定义的缓冲量
let buffer = 100;
let maxOffsetY = 0;
//向上移动 Y值是 负数
let minOffsetY = - (oUl.offsetHeight - oDiv.offsetHeight);
console.log("minOffsetY:",minOffsetY);
oUl.addEventListener("touchstart",function (e) {
//获取开始的位置
startY = e.targetTouches[0].clientY;
});
//移动
oUl.addEventListener("touchmove",function (e) {
let endY = e.targetTouches[0].clientY;
offsetY = endY - startY;
//当上一次currentY的便宜 + 现在的offsetY 偏移量 > 极限缓冲的时候 不能在向下移动
if ((currentY + offsetY) >= (maxOffsetY + buffer)){
return;
}
if ((currentY + offsetY) <= (minOffsetY - buffer)){
return;
}
this.style.top = currentY + offsetY + "px";
});
//触摸结束
oUl.addEventListener("touchend",function (e) {
//当手指 离开 屏幕的时候 记录 上一次 移动的位置
currentY += offsetY;
//当向下移动的距离 大于等于 最大的极限值时
if (currentY >= maxOffsetY){
currentY = maxOffsetY;
//复位
// this.style.top = currentY + "px";
} else if (currentY < minOffsetY ){
currentY = minOffsetY;
//复位
// this.style.top = currentY + "px";
}
console.log(currentY,minOffsetY);
//复位
this.style.top = currentY + "px";
});
</script>