缕析条分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 = 1 function 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 })
大家可以自己试用一下,感觉效果还不错~~~欢迎留言区讨论交流。
作者:京东零售 陈震
来源:京东云开发者社区

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
聊聊Spring注解@Transactional失效的那些事 | 京东云技术团队
一、前言 emm,又又又踩坑啦。这次的需求主要是对逾期计算的需求任务进行优化,现有的计算任务运行时间太长了。简单描述下此次的问题:在项目中进行多个数据库执行操作时,我们期望的是将其整个封装成一个事务,要么全部成功,或者全部失败,然而在自测异常场景时发现,里面涉及的第一个数据状态更新成功了,但是后面的数据在插入出现异常,后面查询数据表发现,该数据的状态已经被更新成功啦。 emmm,查看代码发现确实是使用了@Transactional注解没问啊。于是通过查询网上相关资料发现,在使用Spring中事务注解@Transactional时会存在几种场景下该注解失效,即不能按照预期封装成一个事务操作,于是对该注解进行学习并对相关失效场景进行分析,整理文章如下; 二、@Transactional注解失效场景实例验证 1、@Transactional注解属性 属性 类型 描述 value String 可选的限定描述符,指定使用的事务管理器 propagation Enum:Propagation· 可选的事务传播行为设置 isolation Enum:Isolation 可选的事务隔离级别设置 re...
- 下一篇
DDD架构为什么应该首选六边形架构? | 京东云技术团队
一、传统分层架构 分层架构的一个重要原则是:每层只能与位于其下方的层发生耦合。 分层架构分两种:一种是严格分层架构,规定某层只能与直接位于其下方的层发生耦合;另一种是松散分层架构,允许任意上方层与任意下方层发生耦合。 下图是一个典型的DDD传统分层架构。 以上分层架构中各层都有自己的职责: 用户接口层负责处理用户请求和用户显示; 应用层实现不同业务场景下的用例或业务流程。其中应用服务通常接收来自用户接口层的请求,然后通过资源库获取聚合实例,最后执行相应的命令操作,如下示例: // 应用层的用例 public void cancelOrder(Long orderId) { Order order = orderRepository.findById(orderId); // 领域层的业务逻辑 order.cancel() orderRepository.save(order); } 领域层实现系统的核心业务逻辑,主要包含基于DDD业务建模产生的领域模型,这里的业务逻辑不同于应用层中的业务流程,如上代码示例; 基础设施层为其它各层提供通用的技术和基础服务,比如...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Red5直播服务器,属于Java语言的直播服务器
- CentOS7设置SWAP分区,小内存服务器的救世主
- CentOS7安装Docker,走上虚拟化容器引擎之路
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS关闭SELinux安全模块
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- Linux系统CentOS6、CentOS7手动修改IP地址