嵌套滚动场景
假设有这么一个场景:一个长页的表格(纵向滚动),上方有一排功能按钮(横向滚动)
注意:html 中鼠标滚轮操作的,默认是纵向滚动,如果要支持横向滑动,需要手动修改元素的 scrollLeft。
测试代码如下
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8">
<title>横向滑动</title>
<style type="text/css">
body {
height: 150vh;
overflow-y: scroll;
}
.slide-box{
margin-top: 10rem;
display: flex;
flex-direction: row;
overflow-x: scroll;
-webkit-overflow-scrolling:touch;
}
.slide-item{
flex: 0 0 auto;
width: 200px;
height: 200px;
border:1px solid #ccc;
margin: 1rem;
}
</style>
<script>
function horizontalScroll(e) {
const event = e ? e : window.event;
console.log(event)
const step = 20;
let diff = 0;
if (event.wheelDelta) { //判断浏览器IE,谷歌滑动事件
if (event.wheelDelta > 0) { //当滑轮向上滚动时,chrome滑轮向上滚动
console.log("IE、Chrome up");
diff = -step;
}
if (event.wheelDelta < 0) { //当滑轮向下滚动时,chrome滑轮向下滚动
console.log("IE、Chrome down");
diff = step;
}
} else if (event.detail && event.detail != 0) { //Firefox滑动事件
if (event.detail> 0) { //当滑轮向上滚动时,firefox滑轮向下滚动
console.log("firefox down");
diff = step;
}
if (event.detail< 0) { //当滑轮向下滚动时,firefox滑轮向上滚动
console.log("firefox up");
diff = -step;
}
} else if (event.deltaY) { //Firefox直接定义在html中的动事件,会被拆分为三维数值(分为是:deltaX、Y、Z)
if (event.deltaY> 0) { //当滑轮向上滚动时,firefox滑轮向下滚动
console.log("firefox2 down");
diff = step;
}
if (event.deltaY< 0) { //当滑轮向下滚动时,firefox滑轮向上滚动
console.log("firefox2 up");
diff = -step;
}
}
event.currentTarget.scrollLeft += diff;
console.log(event.currentTarget.scrollLeft, diff)
cancelBubble(event);
}
function cancelBubble(event) {
if(event.stopPropagation) {
event.stopPropagation();
} else { //IE
event.cancelBubble = true;
}
event.preventDefault();
}
window.onload=()=>{
const slideBox = document.getElementsByClassName("slide-box")[0];
const div = document.createElement("div");
div.className="slide-item";
div.innerHTML = "<div>Child</div>";
for(let i=0; i<10; i++) {
const tmp = div.cloneNode();
slideBox.appendChild(tmp);
}
}
</script>
</head>
<body>
<div class="slide-box" onWheel="horizontalScroll(event)">
</div>
</body>
</html>
知识点
-
scrollLeft 控制横向滑动的距离,如果要滑动到底部就是
element.clientHeight
-
event.target返回触发事件的元素;event.currentTarget返回绑定事件的元素
-
-webkit-overflow-scrolling
控制元素在移动设备上是否使用滚动回弹效果
- auto: 使用普通滚动, 当手指从触摸屏上移开,滚动会立即停止
- touch: 使用具有回弹效果的滚动,当手指从触摸屏上移开,内容会继续保持一段时间的滚动效果(继续滚动的速度和持续的时间和滚动手势的强烈程度成正比)
-
window.onload 表示 html 元素渲染完毕之后才调用(这时候才能获取到相应的元素)
-
element.cloneNode(deep),分为 “浅拷贝” (不填默认)和 “深拷贝”。深拷贝会递归复制该元素和其下的所有子元素;而浅拷贝只会复制元素本身,不会复制它的子元素
-
在 html 中绑定事件,只能使用 event 作为参数,不能用 e,也不能不传。参数传递到函数后,会被封装到 arguments 的数组中,然后依次拆包,逐个分配给形参。
-
IE 使用 cancelBubble 来停止事件传递;其它浏览器则统一采用 stopPropagation 来停止事件传播
改为在JS中动态绑定
首先,将 <div class="slide-box" onWheel="horizontalScroll(event)">
改为 <div class="slide-box">
其次,给页面绑定滑动事件
if (document.addEventListener) {
//Chrome、IE(wheel 和 mousewheel 两种均可以实现)
slideBox.addEventListener("wheel", horizontalScroll);
//slideBox.addEventListener("mousewheel", horizontalScroll);
//Firefox
slideBox.addEventListener('DOMMouseScroll', horizontalScroll);
}
react 中出现问题
来看一下 addEventListener 的定义 addEventListener(type, listener[, useCapture ])
17年底,DOM 规范做了修订:addEventListener() 的第三个参数可以是个对象值了,也就是说第三个参数现在可以是两种类型的值了
addEventListener(type, listener, {
capture: false,
passive: false,
once: false
})
其中 capture 属性等价于以前的 useCapture 参数;
- capture 如果将useCapture设置为true,则侦听器只在捕获阶段处理事件,而不在目标或冒泡阶段处理事件。默认为false,也就是在冒泡阶段执行。
- once 属性就是表明该监听器是一次性的,执行一次后就被自动 removeEventListener 掉。
- passive 是 2016年Google I/O上提出的概念,目的是用来提升页面滑动的流畅度。
谷歌做了个统计有 80% 的滚动事件监听器是不会阻止默认行为的。由于浏览器无法预知是否要执行默认行为,那么它只有等到监听器执行完毕的时候才知道,也就是说大部分情况下,浏览器是白等了。所以,passive 监听器诞生了,passive 的意思是“顺从的”,表示它不会对事件的默认行为说 no,浏览器知道了一个监听器是 passive 的,它就可以在两个线程里同时执行监听器中的 JavaScript 代码和浏览器的默认行为了。
react 在翻译 JSX 的 html 时,默认会把所有定义在 html 上的事件翻译成 passive = true
,也就是默认不执行 preventDefault 来提升性能。因此,如果你在 react 的监听器中调用了 preventDefault,在谷歌浏览器(火狐正常执行,不会报错)上会报如下错误 Unable to preventDefault inside passive event listener invocation.
react 解决上述问题的办法也很简单,就是不在JSX中绑定事件,而改为在 componentDidMount 和 componentWillUnmount 绑定和移除监听器,手动将 passive 设为 true。例如
handleWheel=()=>{
//TODO 处理鼠标中键滚动事件
}
isChrome() {
return navigator.userAgent.indexOf("Chrome");
}
componentDidMount() {
if (this.isChrome()) {
this.scroll.current.addEventListener("wheel", this.handleWheel, { passive: false });
}
}
componentWillUnmount() {
if (this.isChrome()) {
this.scroll.current.removeEventListener("wheel", this.handleWheel);
}
}