JavaScript 嵌套滚动

嵌套滚动场景

假设有这么一个场景:一个长页的表格(纵向滚动),上方有一排功能按钮(横向滚动)

注意: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>

知识点

  1. scrollLeft 控制横向滑动的距离,如果要滑动到底部就是 element.clientHeight

  2. event.target返回触发事件的元素;event.currentTarget返回绑定事件的元素

  3. -webkit-overflow-scrolling 控制元素在移动设备上是否使用滚动回弹效果

  • auto: 使用普通滚动, 当手指从触摸屏上移开,滚动会立即停止
  • touch: 使用具有回弹效果的滚动,当手指从触摸屏上移开,内容会继续保持一段时间的滚动效果(继续滚动的速度和持续的时间和滚动手势的强烈程度成正比)
  1. window.onload 表示 html 元素渲染完毕之后才调用(这时候才能获取到相应的元素)

  2. element.cloneNode(deep),分为 “浅拷贝” (不填默认)和 “深拷贝”。深拷贝会递归复制该元素和其下的所有子元素;而浅拷贝只会复制元素本身,不会复制它的子元素

  3. 在 html 中绑定事件,只能使用 event 作为参数,不能用 e,也不能不传。参数传递到函数后,会被封装到 arguments 的数组中,然后依次拆包,逐个分配给形参。

  4. 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);
    }
}
发布了471 篇原创文章 · 获赞 565 · 访问量 188万+

猜你喜欢

转载自blog.csdn.net/chy555chy/article/details/103257689
今日推荐