移动端 touch 和 click 区别
移动端点击后依次触发:touchstart => touchmove => touchend => click。
其中触碰后未移动或移动距离很小,则会在触碰结束后触发click事件,但click事件会有300ms延迟(这是为了等待用户手势操作,例如缩放、滑动等,如果通过preventDefault或touch-action: none;等禁用了触控相关的浏览器默认行为,则不会触发后续方法)。
点击穿透问题
由于click事件延迟300ms触发,在该期间内如果页面发生变化,原本触发touch事件的元素所在位置已被其他元素代替,则由该新元素触发click事件。
- 等待300ms后再移除上层元素
- 300ms期间临时禁止下层元素的点击事件,如添加
pointer-events: none;等。 - 阻止后续的click事件发生
- Fastclick.js 插件
它会在检测到touchend事件时通过 DOM 自定义事件立即触发一个模拟click事件,并把浏览器在 300 毫秒之后真正触发的click事件阻止掉 - 使用
touch-action: none;禁用浏览器对touch事件的默认响应。 - 在
touchstart事件回调中使用preventDefault,但在部分浏览器中该方式可能无效。
- Fastclick.js 插件
蒙版下的内容滚动问题
操作弹窗时,蒙版下的父级元素也会被滚动
- 打开蒙层时,给滚动元素的父级添加样式,关闭蒙层时,移除样式:
overflow: hidden;
height: 100%;
缺点:滚动条消失可能会导致页面跳动。
- 移动端可以给蒙层dom节点的
touchmove添加preventDefault阻止页面滚动的默认行为:
node.addEventListener('touchmove', e => {
e.preventDefault()
}, false)
缺点:会导致弹窗内容也无法滚动,只适用于弹窗内容无需滚动的场景。
- 最终方案 : 既然我们要阻止页面滚动,那么何不将其固定在视窗(即position: fixed),这样它就无法滚动了,当蒙层关闭时再释放。
当然还有一些细节要考虑,将页面固定视窗后,内容会回头最顶端,这里我们需要记录一下,同步top值。
示例代码:
var bodyEl = document.getElementById('toFix');
var top = 0;
function stopBodyScroll(isFixed) {
if (isFixed) {
top = window.scrollY;
bodyEl.style.position = 'fixed';
bodyEl.style.top = -top + 'px';
} else {
bodyEl.style.position = '';
bodyEl.style.top = '';
window.scrollTo(0, top) // 回到原先的top
}
}