滚动条问题
浏览器默认滚动条各不相同,感觉都是不好看的样式。而且同一种浏览器 window 和 mac 也有着比较大的区别。
于是打算从滚动条的css基础属性入手,然后制定两种解决方案,并阐述。
我想达到的效果是滚动条悬浮于盒子上,并不占用盒子宽度。类似 fixed 的效果
滚动条的组成
滚动条可以帮助盒子在固定区域内展示更多的内容,对比下面两种操作系统对于滚动条的处理,mac是比较理想的。
window 下的 chrome (92)
mac 下的 chrome (92)
滚动条基础属性
- ::-webkit-scrollbar 整个滚动条
- ::-webkit-scrollbar-button 滚动条上的按钮,按钮有上下箭头,点击可以控制滚动条的移动
- ::-webkit-scrollbar-thumb 滚动条上的滚动滑块,点击可以拖拽
- ::-webkit-scrollbar-track 滚动条轨道
- ::-webkit-scrollbar-track-piece 滚动条没有滑块的轨道部分
- ::-webkit-scrollbar-corner 当同时拥有两个方向的滚动条时的交汇部分
- ::-webkit-resizer 某些元素的 corner 部分的部分样式. (例 textarea 的可拖动按钮)
隐藏滚动条,制作虚拟滚动条
主要是以下3个目标的实现 💡
- 监听内容页面的滚动事件 =》页面随之滚动
- 监听滚动条的拖拽 =》滚动条滚动 & 页面内容滚动
- 监听滚动轨道空白的点击 =》滚动条跳转 & 页面内容滚动
准备工作
html部分
<div class="box" id="box"> <div class="content" id="content"> <div class="blue"> 蓝色背景行 </div> </div> <div class="scrollbar" id="scrollbar"> <div class="thumb" id="thumb"></div> </div> </div>
css部分
/* 利用css 实现 */ .box { width: 400px; height: 400px; background-color: orange; overflow: hidden; position: relative; } .content { width: 400px; height: 400px; overflow-y: scroll; overflow-x: hidden; } /* 当box被hover时,显示scroll */ .box:hover .scrollbar { opacity: 1 !important; } /* 隐藏原生滚动条 */ .box ::-webkit-scrollbar { display: none; } /* 滚动条容器,即滚动条轨道 */ .scrollbar { height: 100%; width: 6px; position: absolute; top: 0px; right: 0; opacity: 0; transition: 0.1s ease-out; } /* thumb 滚动条 */ .thumb { width: 6px; height: 100%; background-color: red; border-radius: 8px; position: absolute; cursor: pointer; top: 0px; }
js中获取DOM
const scrollbar = document.getElementById('scrollbar') // 整个滚动条 const thumb = document.getElementById('thumb') // thumb 滚动条 const content = document.getElementById('content') // 内容容器,和滚动条容器并行
js中设置内容容器内填充 & 设置滚动条高度
// 渲染内容,产生滚动 content.innerHTML = content.innerHTML + new Array(400).fill('123').join('-') // 设置滚动条thumb height,随着内容的变化变化 thumb.style.height = scrollHeight * 100 + '%'
监听内容容器的滚动
const clientHeight = content.clientHeight // 容器的高度 px const canScrollHeight = content.scrollHeight - clientHeight // 滚动区域的高度 = 内容滚动高度 - 内容容器高度 const scrollHeight = clientHeight / content.scrollHeight // 滚动条的高度(长度,height) % = 内容容器高度 / 内容滚动高度 content.addEventListener('scroll', function (e) { const scrollTop = content.scrollTop // 设置滚动条 thumb 的 top。满足:(当前滚动 / 滚动区域) = (top / top最大值) thumb.style.top = (clientHeight - clientHeight * scrollHeight) * (scrollTop / canScrollHeight) + 'px' })
监听滚动条的拖拽
thumb.addEventListener('mousedown', downHandler) / * 开始滚动 */ let cursorDown = false // 拖拽标识 let y1 = 0 // 点击滚动条的位置 距离滚动条顶端的距离 let y2 = 0 // 点击滚动条的位置 距离滚动条底部的距离 // 鼠标开始点击 function downHandler(e) { y1 = e.layerY // 滚动条的 layerY 属性为点击位置到元素顶部的距离 y2 = scrollHeight * clientHeight - y1 // 滚动条长度 - y1 // 开始拖拽 cursorDown = true // 如果一个元素上被添加多个事件,当事件触发的时候,会按照添加顺序执行。如果添加 stopImmediatePropagation(),那么这个元素上的其他事件将不执行 e.stopImmediatePropagation(); // 显示滚动条容器 scrollbar.style.opacity = 1 // 监听全屏拖动 document.addEventListener('mousemove', moveHandler) // 监听全屏拖动结束 document.addEventListener('mouseup', upHandler) // 拖动过程中去除选中事件 document.onselectstart = () => { return false } } // mousemove 移动操作函数 function moveHandler(e) { console.log('moveHandler') const contentY = e.pageY // 如果超出了允许滚动的范围,不做处理 if (contentY <= y1 || contentY >= (clientHeight - y2)) { return } // 设置滚动条 thumb top属性 thumb.style.top = e.pageY - y1 + 'px' // 控制内容滚动 x,y content.scrollTo(0, thumb.offsetTop / (clientHeight - scrollHeight * clientHeight) * canScrollHeight) } // mouseup 结束拖动 function upHandler() { console.log('拖动结束,注销监听事件') // 结束拖拽 cursorDown = false // 还原选中事件 document.onselectstart = null // 移除监听全屏的两个事件 document.removeEventListener('mousemove', moveHandler) document.removeEventListener('mouseup', upHandler) scrollbar.style.opacity = 0 }
监听点击滚动轨道
scrollbar.addEventListener('mousedown', clickScrollbar) // 点击空白轨道 function clickScrollbar(e) { const pageY = e.layerY const height = scrollHeight * clientHeight let top = (pageY - height / 2) if (top < 0) { top = 0 } if (top > (clientHeight - clientHeight * scrollHeight)) { top = (clientHeight - clientHeight * scrollHeight) } thumb.style.top = top + 'px' content.scrollTo(0, thumb.offsetTop / (clientHeight - scrollHeight * clientHeight) * canScrollHeight) }
总结
- 实现自定义滚动条的每个步骤都是清晰明了的,需要我们耐心的讲它们拼接在一起
- 实现的原理和 el-scrollbar 基本一样,但是项目的公共组件在成本允许的前提下尽量我们自己实现。一方面可以借助这个组件整体的学习或复习遇到的知识点,很多优秀的用法可以积累下来,例如对 DOM 和 body 两者之间的交互的处理。另一方面可以定制团队的想法,虽然这个滚动条没啥太多可以定制的东西
- 监听 mousemove 事件的时候,刚开始我监听的是 document.body,会出现鼠标移出浏览器范围就失去监听,然后松开鼠标回来也能滚动。于是乎需要监听 document 上的 mousemove
- 对于鼠标事件的 event 和 直接获取 DOM 两者的位置属性对应的意思需要单独拎出来理解然后运用
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.mushiming.com/mjyfx/16114.html