skip to content
寻找莉莉丝

为什么元素的 scrollTop 一直为 0?

/ 4 min read / 次阅读

最近在开发时,遇到一个理论与实践不相符的问题:明明 div 可以滚动,偏偏获取不到它的 scrollTop。

功能:

  1. “返回顶部”按钮默认隐藏;
  2. 当页面往下滚动到 200px 以后时,“返回顶部”按钮出现。
  3. 当点击“返回顶部”按钮时,页面滚动到顶部。

草图:

image.png image.png

思路:

  1. 一开始让“返回顶部”按钮隐藏,监听 div 滚动事件,到达指定高度后,让按钮出现。
  2. “返回顶部”按钮监听点击事件,点击后,回到 div 顶部即可。

本来是 Vue 代码,懒得启动脚手架,直接写原生演示。

页面结构: 可以根据 Vue 单页面想象一下,最外层是元素 app,中间的 container 是某路由渲染的模块内容。

<body>
	<div id="app">
		<div class="container">
			<h1>app 某模块</h1>
			<h2>模块总高度:2000px</h2>
			<p>当前滚动的高度:<span class="current-scrollTop">0</span> px</p>
			<a class="back-top hide" href="javascript:;">返回顶部</a>
		</div>
	</div>
</body>

页面样式:

* {
	margin: 0;
	padding: 0;
	box-sizing: border-box;
}

html,
body {
	height: 100%;
	overflow: hidden;
}

#app {
	height: 100%;
	border: 5px dashed red;
	overflow: auto;
}

#app > .container {
	height: 2000px;
	margin: 0 auto;
	background-color: #abcdef;
}

#app > .container > h1,
#app > .container > h2,
#app > .container > p {
	text-align: center;
}

#app > .container > p {
	position: fixed;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
	font-weight: 700;
}

/* 按钮 */
a.back-top {
	position: fixed;
	right: 50px;
	bottom: 50px;
	display: inline-block;
	padding: 10px;
	text-decoration: none;
	border: 2px solid #000;
}

/* 控制按钮隐藏 */
a.back-top.hide {
	display: none;
}

页面逻辑:

  1. 开始犯错
const dom = document.getElementsByClassName("container")[0];
dom.addEventListener("scroll", function () {
	console.log("scrollTop: ", this.scrollTop);
});
console.log(dom.scrollTop); // 0

按照上面的写法,始终没有监听到 div 的滚动事件,打印的 scrollTop 也只有一个 0

  1. 明明页面可以滚动,怎么监听不到事件呢?
  2. 哦!div.container 根本不能滚动,能滚动的是设置了 overflow: auto; 的 app 元素!
  3. 监听 app 元素的滚动事件试一试:
const app = document.getElementById('app');
app.addEventListener('scroll', function () {
  console.log('scrollTop: ', this.scrollTop);
})

成功了,控制台里有数据记录。

  1. 接下来根据设计思路,编写正确的代码就行:
const app = document.getElementById('app');
const dom = document.getElementsByClassName('container')[0];
const btn = document.getElementsByClassName('back-top')[0];
const text = document.getElementsByClassName('current-scrollTop')[0];
app.addEventListener('scroll', function () {
  text.innerText = this.scrollTop;
  if (this.scrollTop > 200) {
    btn.classList.remove('hide');
  } else {
    btn.classList.add('hide');
  }
})
btn.addEventListener('click', function () {
  if (app.scrollTop > 200) {
    app.scrollTo({
      top: 0,
      behavior: 'smooth'
    })
  }
})

没错,真相就在这里:由于给 html、body 设置了 overflow: hidden; 属性,视觉上,让我误以为滚动条是 div.container 的,实际上是 #app 的。(当然,Vue 里的代码最好在 unmounted(destroyed) 钩子触发时,将一些事件监听器移除,以免对其他组件造成影响。)