// 2019-10-23 15:11:06
关于弹出层滑动一直以来都是一个令人难受的问题。我们一直在探索解决的方法与途径,虽然没有真正意义上的成功,也从未停止探索的步伐,直到写这篇文章的时候,我们仍未找到最完美的方法。这里记录一些探索的经验,以便后人参考。
先来看这样一个GIF
虽然这个例子已近是最后面对的困难,但他能更直观得提现出问题所在。
下面的弹出框是一个position:fixed;的一个div,他与他的父元素一样都属于内容比较多,需要overflow:auto的元素。于是在为做任何处理的情况下,就有这种,滑动穿透的问题出现。
这种问题还出现在iframe中,弹出的iframe滑动到底部的时候,仍然执行向下滑动的操作,滑动穿透会继续将其父元素向下滑动,造成很糟糕的用户体验。
这种方法常见于iframe中,因为新弹出的页面,往往把父页面整个遮挡,用户只看到子页面的内容,而不知父页面到底发生了什么。
基于用户看不到父页面的特性,我们一般采用——”滚动条获取并重新赋值“的方法
通过以上两步,基本能将问题解决。
关于取值方法,其实有很多种,这里只做一些简单的介绍:
直接通过变量的传输。
a.在如果是弹出普通的div(在同一个页面),这样子就可以通过全局申明一个变量解决问题。
b.如果在iframe
父页面调用子元素方法(直接从子元素方法中获取数值)
document.getElementById('').contentWindow.
子页面获取父元素的方法
window.parent.父元素的方法();
将数据存在浏览器本地
如果从我现在用的谷歌浏览器来讲的话,一共有8种储存数据的方法。但必须要提的是,请注意兼容性的问题,这里有相当一部分是谷歌浏览器所独有的储存方式,不兼容其他内核的浏览器,不要为了此刻图方便而采用他们。
这里只讨论了父页面被全面覆盖的情况,其实还存在上面第一张GIF所至展示的,父页面被覆盖了部分,这样采用方案一就容易被发现。
在实际使用过程中,虽然他令人难受,但在绝大多数情况下并不致命。
所以,在限制条件过多的情况下,我们可以考虑暂时先放弃这块硬骨头。
方案三的思路是从事件的角度出发,解决问题的。
https://www.zhangxinxu.com/wordpress/2016/12/web-mobile-scroll-prevent-window-js-css/
先通过给整个body添加一个禁止滚动属性,再通过判断,选择禁止默认属性的元素,从而达到一部分元素可以滚动,一部分元素不能滚动的效果。
核心代码分为两块css 和js
.noscroll,
.noscroll body {
overflow: hidden;
}
.noscroll body {
position: relative;
}
先注意一下上面这两个类,他是给HTML加的,子页面出来的时候,将类名加给 HTML。再当子页面消失的时候,将类删除。
再来看一下,下面这个方法他的使用方法是$.smartScroll.(子页面整个元素,‘滑动部件的类名’)。
请注意,第一个是元素【最好用id选择器,因为只有一个】,第二个是类名(一个字符串)
举个例子: . s m a r t S c r o l l ( .smartScroll( .smartScroll((’#after-page’), ‘.content’);
$.smartScroll = function(container, selectorScrollable) {
// 如果没有滚动容器选择器,或者已经绑定了滚动时间,忽略
if (!selectorScrollable || container.data('isBindScroll')) {
return;
}
// 是否是搓浏览器
// 自己在这里添加判断和筛选
var isSBBrowser;
var data = {
posY: 0,
maxscroll: 0
};
// 事件处理
container.on({
touchstart: function (event) {
var events = event.touches[0] || event;
// 先求得是不是滚动元素或者滚动元素的子元素
var elTarget = $(event.target);
if (!elTarget.length) {
return;
}
var elScroll;
// 获取标记的滚动元素,自身或子元素皆可
if (elTarget.is(selectorScrollable)) {
elScroll = elTarget;
} else if ((elScroll = elTarget.parents(selectorScrollable)).length == 0) {
elScroll = null;
}
if (!elScroll) {
return;
}
// 当前滚动元素标记
data.elScroll = elScroll;
// 垂直位置标记
data.posY = events.pageY;
data.scrollY = elScroll.scrollTop();
// 是否可以滚动
data.maxscroll = elScroll[0].scrollHeight - elScroll[0].clientHeight;
},
touchmove: function (event) {
// 如果不足于滚动,则禁止触发整个窗体元素的滚动
if (data.maxscroll <= 0 || isSBBrowser) {
// 禁止滚动
event.preventDefault();
}
// 滚动元素
var elScroll = data.elScroll;
// 当前的滚动高度
var scrollTop = elScroll.scrollTop();
// 现在移动的垂直位置,用来判断是往上移动还是往下
var events = event.touches[0] || event;
// 移动距离
var distanceY = events.pageY - data.posY;
if (isSBBrowser) {
elScroll.scrollTop(data.scrollY - distanceY);
elScroll.trigger('scroll');
return;
}
// 上下边缘检测
if (distanceY > 0 && scrollTop == 0) {
// 往上滑,并且到头
// 禁止滚动的默认行为
event.preventDefault();
return;
}
// 下边缘检测
if (distanceY < 0 && (scrollTop + 1 >= data.maxscroll)) {
// 往下滑,并且到头
// 禁止滚动的默认行为
event.preventDefault();
return;
}
},
touchend: function () {
data.maxscroll = 0;
}
});
// 防止多次重复绑定
container.data('isBindScroll', true);
};
虽然这已经是目前为止找到的最能满足要求的方法,但是他仍然存在令人不满意的问题。而且是让人很难受的那种。
其在ios(iPhone6s ios13)上表现完美,没有值得挑刺的地方,但在安卓上则有些小问题。
虽然他并不像开头那个图片那样明显,也不会有大的位置改变,但这仍然是是一个值得去解决的问题。
方案三还是有一个问题是是浏览器报错。
[Intervention] Ignored attempt to cancel a touchmove event with cancelable=false, for example because scrolling is in progress and cannot be interrupted.
[干预]忽略了取消cancelable=false的touchMove事件的尝试,例如,因为滚动正在进行且无法中断。
我不知道这个错是什么引起的,也不知道他是否必须去解决,但是我知道他挺麻烦的
这里用的是方案三
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<meta charset="UTF-8">
<title>用来尝试到底行不行title>
<style>
/**{touch-action: none;}*/
.noscroll,
.noscroll body {
overflow: hidden;
}
.noscroll body {
position: relative;
}
.main{
text-align: center;
}
.after-page{
position: fixed;
bottom: 0px;
width: 100%;
height: 400px;
background-color: aqua;
text-align: center;
}
.content{
position: absolute;
width: 100%;
height: 400px;
}
style>
head>
<body>
<div class="main">
<p>1前页的内容p>
<p>前页的内容p>
<p>前页的内容p>
<p>前页的内容p>
<p>前页的内容p>
<p>2前页的内容p>
<input class="page-btn" type="button" value="getNext">
<p>前页的内容p>
<p>前页的内容p>
<p>前页的内容p>
<p>3前页的内容p>
<p>前页的内容p>
<p>前页的内容p>
<p>前页的内容p>
<p>前页的内容p>
<p>3前页的内容p>
<p>前页的内容p>
<input class="page-btn" type="button" value="getNext">
<p>前页的内容p>
<p>前页的内容p>
<p>前页的内容p>
<p>前页的内容p>
<p>4前页的内容p>
<p>前页的内容p>
<p>前页的内容p>
<p>前页的内容p>
<p>前页的内容p>
<p>前页的内容p>
<input class="page-btn" type="button" value="getNext">
<p>前页的内容p>
div>
<div class="after-page" id="after-page" style="display: none; overflow: hidden">
<h1>我是大标题h1>
<input type="button" class="close-btn" value="close">
<div class="content" style="overflow: auto; -webkit-overflow-scrolling: touch;">
<ul>
<li>1后页的内容li>
<li>后页的内容li>
<li>后页的内容li>
<li>后页的内容li>
<li>后页的内容li>
<li>后页的内容li>
<li>后页的内容li>
<li>后页的内容li>
<li>2后页的内容li>
<li>后页的内容li>
<li>后页的内容li>
<li>后页的内容li>
<li>后页的内容li>
<li>后页的内容li>
<li>后页的内容li>
<li>后页的内容li>
<li>3后页的内容li>
<li>后页的内容li>
<li>后页的内容li>
<li>后页的内容li>
<li>后页的内容li>
<li>后页的内容li>
<li>4后页的内容li>
<li>后页的内容li>
<li>后页的内容li>
<li>后页的内容li>
<li>后页的内容li>
<li>5后页的内容li>
<li>后页的内容li>
<li>后页的内容li>
<li>后页的内容li>
<li>6后页的内容li>
<li>后页的内容li>
<li>后页的内容li>
<li>后页的内容li>
ul>
div>
div>
body>
<script type="text/javascript" src="jquery-3.4.1.min.js">script>
<script>
$('.page-btn').off().on('click',function () {
$('.after-page').show();
$('html').addClass('noscroll');
$.smartScroll($('#after-page'), '.content');
});
$('.close-btn').off().on('click',function () {
$('.after-page').hide();
$('html').removeClass('noscroll');
})
// 构造函数
$.smartScroll = function (container, selectorScrollable) {
// 如果没有滚动容器选择器,或者已经绑定了滚动时间,忽略
console.log(selectorScrollable);
if (!selectorScrollable || container.data('isBindScroll')) {
return;
}
// 是否是搓浏览器
// 自己在这里添加判断和筛选
var isSBBrowser;
var data = {
posY: 0,
maxscroll: 0
};
// 事件处理
container.on({
touchstart: function (event) {
// var events = event.touches[0] || event;
var events = event.originalEvent.targetTouches[0] || event;
// 先求得是不是滚动元素或者滚动元素的子元素
var elTarget = $(event.target);
if (!elTarget.length) {
return;
}
var elScroll;
// 获取标记的滚动元素,自身或子元素皆可
if (elTarget.is(selectorScrollable)) {
elScroll = elTarget;
} else if ((elScroll = elTarget.parents(selectorScrollable)).length === 0) {
elScroll = null;
}
if (!elScroll) {
return;
}
// 当前滚动元素标记
data.elScroll = elScroll;
// 垂直位置标记
data.posY = events.pageY;
data.scrollY = elScroll.scrollTop();
// 是否可以滚动
data.maxscroll = elScroll[0].scrollHeight - elScroll[0].clientHeight;
},
touchmove: function (event) {
// 如果不足于滚动,则禁止触发整个窗体元素的滚动
if (data.maxscroll <= 0 || isSBBrowser) {
// 禁止滚动
event.preventDefault();
}
// 滚动元素
var elScroll = data.elScroll;
// 当前的滚动高度
var scrollTop = elScroll.scrollTop();
// 现在移动的垂直位置,用来判断是往上移动还是往下
var events = event.touches[0] || event;
// var events = event.originalEvent.targetTouches[0] || event;
// 移动距离
var distanceY = events.pageY - data.posY;
if (isSBBrowser) {
elScroll.scrollTop(data.scrollY - distanceY);
elScroll.trigger('scroll');
return;
}
// 上下边缘检测
if (distanceY > 0 && scrollTop == 0) {
// 往上滑,并且到头
// 禁止滚动的默认行为
event.preventDefault();
return;
}
// 下边缘检测
if (distanceY < 0 && (scrollTop + 1 >= data.maxscroll)) {
// 往下滑,并且到头
// 禁止滚动的默认行为
event.preventDefault();
return;
}
},
touchend: function () {
data.maxscroll = 0;
}
});
// 防止多次重复绑定
container.data('isBindScroll', true);
};
script>
html>