写点什么

缕析条分 Scroll 属性 | 京东云技术团队

  • 2023-07-18
    北京
  • 本文字数:2883 字

    阅读完需:约 9 分钟

缕析条分Scroll属性 | 京东云技术团队

最近有项目需要使用 js 原生开发滑动组件,频繁要用到 dom 元素的各种属性,其中以各种类型的 height 和 top 属性居多,名字相近,含义也很容易搞混。因此特地总结归纳了一下常用的知识点,在文末我们来挑战实现一个简易的移动端 Scroll 组件。


要理解 height 和 top,要从盒模型开始说起,首先我们来认识一下 css3 中定义的盒模型。dom 元素在页面上实际占据的面积可以由下面这张图来说明:



我们可以把它想象成一枚鸡蛋,从外到内依次是


橙色区域——外边距 margin:鸡蛋壳


黑色区域——边框 border:蛋壳膜


绿色区域——内边距 padding:蛋白


白色区域——内容 content:蛋黄


我们真正关心的部分是内容 content,也就是鸡蛋营养最丰富的部分。content 外面包裹了这么多层,我们在页面中才会感觉 dom 结构整体疏密有致,不会被密密麻麻的文字和图片所困扰。为了更好地描述盒模型,web 规范中还定义了一些其他接口来描述他们,也就是我们今天要聊到的主角们:



在上面这张图中,我们描述了具有嵌套关系的两层 dom 元素,注意这里的内容区和前述一张图中有所不同,这里的内容区也是一个独立的 dom 元素(即它也有自己的 margin、border、padding 和 content!,为了简化起见,子元素不再具体绘制出来),子元素由于整体高度很高,甚至超出了父元素的高度,在父元素中不能完全展示出来(超出的不可见部分用灰色表示),也就是两者构成了滚动关系。下面我们来尝试描述以下属性:

一、clientHeight

只读属性。clientHeight 实际上就是垂直滚动条的高度,一般情况下,垂直滚动条都是要紧贴上下 border 的,因此 clientHeight = 上下 padding + 内容 height。别忘了有个特殊情况,当存在水平方向滚动条时,还需要考虑水平滚动条挤占了垂直滚动条的一部分空间,即:clientHeight = 上下 padding + 内容 height - 水平滚动条高度。用鸡蛋来比喻的话,clientHeight 就是剥了壳的鸡蛋。

二、clientTop

只读属性。描述了顶部 border 的宽度。顶部 border 容易让我们联想到头发的厚度,下次在镜子前可以对自己说:你的 clientTop 变少了,不过你也变强了。

三、offsetHeight

只读属性。offsetHeight 和 clientHeight 比较类似,观察可以发现,比 clientHeight 多了 border 这一层:offsetHeight = clientHeight + 上下 border。现在我们把时光回退到给鸡蛋剥壳的前一刻,鸡蛋从水里捞出来洗干净呈现的样子——offsetHeight。

四、offsetTop

只读属性。它返回当前元素相对于其 offsetParent 元素的顶部内边距的距离。这段距离是不包含自身和父元素的 border 宽度的,实际上:offsetTop=自己的上 margin + 父元素的上 padding,相当于鸡蛋和鸡蛋盒之间挡板的厚度。

五、scrollHeight

只读属性。描述了元素内容高度的度量,包括由于溢出导致的视图中不可见内容。我们可以 hack 一下,把父元素中不可见的内容也展示出来,子元素在垂直方向可以分为两部分:外层的 margin 和内部的 offsetHeight:



对于具有滚动关系的父子元素,其 scrollHeight 具有不同的含义:


(1)对于子元素而言,由于子元素本身不包含溢出部分,其 scrollHeight 和 clientHeight 具有相同的值


(2)对于父元素而言,由于父元素的内容实际上被子元素“撑起来”了,因此其 scrollHeight 为把内容区域“展开”后的实际高度:父元素 scrollHeight = 子元素的 offsetHeight + 子元素上下 margin + 自己的上下 padding

六、scrollTop

读写属性,可以获取或设置一个元素的内容垂直滚动的像素数。这个是目前为止咱们遇到的第一个可以支持设置的属性。


(1)初始状态时,内容垂直方向未滚动,其值为 0


(2)当内容垂直方向滚动到底时,由于内容区实际高度为 scrollHeight,可显示高度为 clientHeight,多出来的部分就是此时的 scrollTop 值为 scrollHeight - clientHeight


因此可以得出结论:0 <= scrollTop <= scrollHeight - clientHeight

七、实战

学习了以上属性和信息,我们模仿京东小程序的 scroll-view 组件功能,来实现一个 H5 版滑动组件的常见功能:列表的下拉刷新和上拉加载。要实现这个功能,大概可以分为 3 个核心要点:


(1)可滚动:需要有一个不可动的外壳和可滑动的内容区。


(2)手势识别:通过移动端的 touch 属性,我们可以对比 touchend 和 touchstart 的手指位置,来简单进行手势识别。


(3)事件触发:根据滑动位置的临界条件,来判断是否应该触发刷新和加载事件。


部分实现代码如下:


// scroller.js
export default class Scroller { constructor(el, option) { this._el = el this._parent = el.parentNode this._option = option this._pos = 0 this._handleScrollStart = this.handleScrollStart.bind(this) this._handleScroll = this.handleScroll.bind(this) this.init() } init() { this._el.addEventListener('touchstart', this._handleScrollStart) this._el.addEventListener('touchend', this._handleScroll) if(this._option.auto) { this.handleRefresh() } } destroy() { this._el.removeEventListener('touchstart', this._handleScrollStart) this._el.removeEventListener('touchend', this._handleScroll) } handleScrollStart(e) { const touch = e.targetTouches[0] || e.changedTouches[0] this._pos = touch.clientY } handleScroll(e) { const touch = e.targetTouches[0] || e.changedTouches[0] const delta = touch.clientY - this._pos if(delta >= this._option.threhold) { // 手势向下,且下行滚动距离超过判定阈值 if(this._parent.scrollTop == 0) { this.handleRefresh(e) } } else if(this._parent.scrollHeight > this._parent.clientHeight && delta <= -this._option.threhold){ // 内容可滚动,且上行滚动距离超过判定阈值 if(this._parent.scrollTop > this._parent.scrollHeight - this._parent.clientHeight - this._option.threhold){ this.handleLoad(e) } } } handleRefresh(...params) { this._option.onRefresh && this._option.onRefresh.apply(this, params) } handleLoad(...params) { this._option.onRefresh && this._option.onLoad.apply(this, params) }}
复制代码


调用 demo:


var inner = document.getElementsById('inner')var list = []var pageIndex = 1function getMockData() {  const newData = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map(i => i+(pageIndex-1)*10)  setTimeout(() => {      if(pageIndex==1) { list = newData } else if(pageIndex < 5) { list.push(...newData) } else { alert('没有更多内容了') }      pageIndex++      inner.innerHTML = list.map(i => `<div class="inner-item">${i}</div>`).join('')  }, 300)}function onRefresh() {  pageIndex = 1  getMockData()}new Scroller(inner, {  threhold: 20,  onRefresh: onRefresh,  onLoad: getMockData,  auto: true})
复制代码


大家可以自己试用一下,感觉效果还不错~~~欢迎留言区讨论交流。


作者:京东零售 陈震

来源:京东云开发者社区

发布于: 刚刚阅读数: 5
用户头像

拥抱技术,与开发者携手创造未来! 2018-11-20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
缕析条分Scroll属性 | 京东云技术团队_前端_京东科技开发者_InfoQ写作社区