最近有项目需要使用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)事件触发:根据滑动位置的临界条件,来判断是否应该触发刷新和加载事件。

部分实现代码如下:

  1. // scroller.js
  2. export default class Scroller {
  3. constructor(el, option) {
  4. this._el = el
  5. this._parent = el.parentNode
  6. this._option = option
  7. this._pos = 0
  8. this._handleScrollStart = this.handleScrollStart.bind(this)
  9. this._handleScroll = this.handleScroll.bind(this)
  10. this.init()
  11. }
  12. init() {
  13. this._el.addEventListener('touchstart', this._handleScrollStart)
  14. this._el.addEventListener('touchend', this._handleScroll)
  15. if(this._option.auto) {
  16. this.handleRefresh()
  17. }
  18. }
  19. destroy() {
  20. this._el.removeEventListener('touchstart', this._handleScrollStart)
  21. this._el.removeEventListener('touchend', this._handleScroll)
  22. }
  23. handleScrollStart(e) {
  24. const touch = e.targetTouches[0] || e.changedTouches[0]
  25. this._pos = touch.clientY
  26. }
  27. handleScroll(e) {
  28. const touch = e.targetTouches[0] || e.changedTouches[0]
  29. const delta = touch.clientY - this._pos
  30. if(delta >= this._option.threhold) {
  31. // 手势向下,且下行滚动距离超过判定阈值
  32. if(this._parent.scrollTop == 0) {
  33. this.handleRefresh(e)
  34. }
  35. } else if(this._parent.scrollHeight > this._parent.clientHeight && delta <= -this._option.threhold){
  36. // 内容可滚动,且上行滚动距离超过判定阈值
  37. if(this._parent.scrollTop > this._parent.scrollHeight - this._parent.clientHeight - this._option.threhold){
  38. this.handleLoad(e)
  39. }
  40. }
  41. }
  42. handleRefresh(...params) {
  43. this._option.onRefresh && this._option.onRefresh.apply(this, params)
  44. }
  45. handleLoad(...params) {
  46. this._option.onRefresh && this._option.onLoad.apply(this, params)
  47. }
  48. }

调用demo:

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

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

作者:京东零售 陈震

来源:京东云开发者社区

缕析条分Scroll属性的更多相关文章

  1. 你不知道的CSS背景—css背景属性全解

    CSS背景在网页设计中使用频率非常高,然而对于这个开发人员很熟悉的CSS属性,却隐藏着许多不为初级开发人员熟知的细节,这篇文章尝试扒开这层不为人知的面纱. 首先列举一下CSS中关于元素背景的所有属性并 ...

  2. 深入理解滚动scroll

    前面的话 前面两篇博文分别介绍过偏移大小.客户区大小.本文介绍元素尺寸中内容最多的一部分——滚动scroll 滚动宽高 scrollHeight scrollHeight表示元素的总高度,包括由于溢出 ...

  3. js鼠标及对象坐标控制属性详细解析

    对js鼠标及对象坐标控制属性进行了详细的分析介绍.  offsetTop获取对象相对于版面或由 offsetParent 属性指定的父坐标的计算顶端位置. offsetLeft获取对象相对于版面或由 ...

  4. js中offsetHeight、clientHeight、scrollHeight等相关属性区分总结

    今天再次遇到了offset***.client***.scroll***的这三类属性的问题,总是混淆,现归纳总结如下: 大体上来说可以这样理解: client***属性(clientWidth.cli ...

  5. jacascript 滚动 scroll 与回到顶部

    前言:这是笔者学习之后自己的理解与整理.如果有错误或者疑问的地方,请大家指正,我会持续更新! 滚动 scroll scrollHeight 表示元素的总高度,包括由于溢出而无法展示在网页的不可见部分: ...

  6. 元素大小-偏移量(offset)客户区大小(client)滚动大小(scroll)

    一.偏移量---offset 1.定位父级 在理解偏移大小之前,首先要理解offsetParent.人们并没有把offsetParent翻译为偏移父级,而是翻译成定位父级,很大原因是offsetPar ...

  7. JavaScript---详解scroll

    scroll scroll--译为‘滚动’,他是非常常用的属性. 滚动宽高 scrollHeight scrollHeight表示元素的总高度,包括由于溢出而无法展示在网页的不可见部分(不要误解为只有 ...

  8. 强制css属性生效

    今天在写一个隐藏滚动条的css时,设置的overflow-x: hidden; overflow-y: scroll;属性死活不生效, 在谷歌浏览器中查看,发现这两条属性是被划掉的.如图. 这表明别处 ...

  9. Web前端篇:CSS常用格式化排版、盒模型、浮动、定位、背景边框属性

    目录 Web前端篇:CSS常用格式化排版.盒模型.浮动.定位.背景边框属性 1.常用格式化排版 2.CSS盒模型 3.浮动 4.定位 5.背景属性和边框属性 6.网页中规范和错误问题 7.显示方式 W ...

  10. jacascript 滚动scroll

    滚动 scroll scrollHeight 表示元素的总高度,包括由于溢出而无法展示在网页的不可见部分: scrollWidth 表示元素的总宽度,包括由于溢出而无法展示在网页的不可见部分: 没有滚 ...

随机推荐

  1. React课堂笔记1

    一.概要 React是用于构建用户界面的MVVM框架. React拥有较高的性能,代码逻辑非常简单,越来越多的人已开始关注和使用它.认为它可能是将来Web开发的主流工具之一. 官网:https://z ...

  2. Go语言实战: 即时通信系统(未完)

    使用Go语言构建一个即时通信系统,旨在锻炼Go语言编程能力 该通信系统至少能够允许用户能够在客户端进行公聊,即所发消息能被所有用户看到,也可发起私聊(即两个用户之间私密通信).同时,用户能够看到当前有 ...

  3. [操作系统] - 进程状态&进程描述

    2.1 进程(Process) 2.1.1 定义 chatGPT版:一个具有独立功能的程序关于某个数据集合的一次运行活动 人话版:程序在并发环境中的执行过程& 进程是程序的一次执行 2.1.2 ...

  4. 基于ChatGPT用AI实现自然对话

    1.概述 ChatGPT是当前自然语言处理领域的重要进展之一,通过预训练和微调的方式,ChatGPT可以生成高质量的文本,可应用于多种场景,如智能客服.聊天机器人.语音助手等.本文将详细介绍ChatG ...

  5. 一文搞懂 x64 IA-64 AMD64 Inte64 IA-32e 架构之间的关系

    想要搞清楚 x64.IA64.AMD64 指令集之间的关系,就要先了解 Intel 和 AMD 这两家公司在生产处理器上的发展历史. x86 处理器 1978年 Intel 生产了它的第一款 16bi ...

  6. 通过Handsontable实现像Excel一样编辑数据

    ​一.Handsontable是指什么? 官网: http://handsontable.com Handsontable是一个JavaScript库,可以帮助您轻松实现类似Excel电子表格一样的编 ...

  7. Pwn系列之Protostar靶场 Stack6题解

    源码如下: #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <stri ...

  8. 记一次 Oracle 下的 SQL 优化过程

    1. 介绍 事情是这样的,UAT 环境的测试小伙伴向我扔来一个小 bug,说是一个放大镜的查询很慢,转几分钟才出数据,我立马上开发环境试了一下,很快啊我说,放大镜的数据立马就出来了,然后我登录 UAT ...

  9. 【HDU】1559 最大子矩阵 (二维前缀和,动态规划)

    动态规划之二维前缀和 题目 给你一个m×n的整数矩阵,在上面找一个x×y的子矩阵,使子矩阵中所有元素的和最大. 输入 输入数据的第一行为一个正整数T,表示有T组测试数据.每一组测试数据的第一行为四个正 ...

  10. vue项目连接socket.io跨域及400异常处理

    最近看人家用socket.io聊天,于是自己也想打个服务试试,不试不知道,一试吓一跳,原来这里吗还有这么多坑,下面就是所遇的坑,记录一哈,但愿可以帮助到遇到同样的坑的小伙伴 文章目录 一.服务端配置 ...