【写在前面】
最近项目中需要用到滚动条,然后尝试了不少方法:
比如使用 overflow: scroll,但感觉不好控制,效果也不怎么满意。
然后,自己就想着进行简单的实现,这里就讲解一下当时的实现思路。
【正文开始】
首先,我们思考一下,滚动条是什么:
在一个页面中,当某个元素不足以显示内容时,可以滚动的,用于显示剩下内容的条。
因此,对于一个滚动条,我认为它应该具有以下属性:
pressed: bool :是否被按下 ( 用于判断用户是否按住滚动条 )。
attach: any :附着元素 ( 滚动条附着的元素 )。
attachWidth & attachHeight: number :附着元素宽高。
target: any :目标元素 ( 被滚动的元素 )。
targetWidth & targetHeight: number :目标元素宽高 ( 用于计算滑块宽度以及和后续滚动时的位置计算 )。
scrollRatio: number : 滚动比例 [ 0.0 ~ 1.0 ] ( 因为我这里打算是通过改变比例来控制内容的滚动 )。
scrollBar: any : 滚动条 ( 仅为条部分,这里我将滚动条分为两部分【滚动条】和【滑块】)。
scrollBarWidth: number : 滚动条宽度 ( 用于后续计算 )。
scrollThumb: any : 滚动滑块 ( 仅为滑块部分 )。
scrollThumbWidth: number : 滚动滑块宽度 ( 用于后续计算 )。
scrollThumbPosition: number : 滑块位置 ( 用于后续计算 )。
orientation: 方向 ( 水平 / 垂直滚动条 )。
接下来进行初步实现,当然,我只实现了水平滚动条,垂直方向的仿照即可。
先是构造函数,它主要是完成一些赋值和添加事件响应等工作:
注意:初始的 scrollThumb 的宽度需要根据 attach 和 target 的宽度来计算,并且需要给 window 添加事件响应 ( 否则会出现鼠标离开滑块就不能滚动 )。
constructor(attach, target, scrollBar, scrollThumb) {
this.attach = attach;
this.target = target;
this.scrollBar = scrollBar;
this.scrollThumb = scrollThumb;
this.attachWidth = attach.outerWidth();
this.targetWidth = target.outerWidth();
this.scrollBarWidth = scrollBar.outerWidth();
this.scrollThumb.css('width', this.attachWidth / this.targetWidth * this.scrollBarWidth + 'px');
this.scrollThumbWidth = this.scrollThumb.outerWidth();
this.scrollThumb.hover(this.enter.bind(this), this.leave.bind(this));
this.scrollThumb.on('mousemove', this.move.bind(this));
this.scrollThumb.on('mouseup', this.up.bind(this));
this.scrollThumb.on('mousedown', this.down.bind(this));
window.addEventListener('mouseup', this.up.bind(this));
window.addEventListener('mousemove', this.move.bind(this));
}
这里:
enter:为鼠标移入处理函数。
leave:为鼠标移出处理函数。
move:为滑块移动处理函数,它通过计算移动的距离来改变 ratio ( 比率 ) 进行滚动。
up:为鼠标按键弹起处理函数。
down:为鼠标按键按下处理函数。
具体实现如下:
/**
* @param {MouseEvent} event
*/
move(event) {
if (this.pressed) {
let x = event.pageX - this.startX;
let right = this.scrollBarWidth - this.scrollThumbWidth;
this.ratio += x / right;
this.startX += x;
}
}
up(event) {
this.pressed = false;
this.leave();
}
down(event) {
this.pressed = true;
this.startX = event.pageX;
}
enter() {
this.scrollThumb.addClass('hover');
}
leave() {
if (!this.pressed)
this.scrollThumb.removeClass('hover');
}
然后是关键的 ratio,由它来控制滚动,这里给出简单的实现:
/**
* @brief 改变滑块位置的比率并控制 target 滚动
* @param {number} ratio 比率 [0.0 ~ 1.0]
*/
set ratio(ratio) {
ratio = ratio < 0.0 ? 0.0 : ratio > 1.0 ? 1.0 : ratio;
this.scrollRatio = ratio;
let left = (this.scrollBarWidth - this.scrollThumbWidth) * ratio + 'px';
this.scrollThumb.css('left', left);
this.target.css('left', (this.attachWidth - this.targetWidth) * ratio + 'px');
}
get ratio() {
return this.scrollRatio;
}
到这儿,我们基本已经实现了 ScrollBar,现在来试试效果:
【结语】
虽然整个 ScrollBar 用起来体验还行,但是仍然有一点小问题,一些情况也没有考虑周全,因此,如果自用的话还需要改进。
其中,代码在:https://github.com/mengps/Web-jx 可以找到,话说这个项目有相当多的小技巧( css 和 js ),请多多 star 呀..⭐_⭐
最后,这里也给出全部代码,可以直接看到效果 ( 注意:使用了 jquery,并且需要把图片路径改一下,还有要支持ES6 ):
<!DOCTYPE html>
<html lang="cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.outer {
position: relative;
margin: auto;
width: 500px;
max-width: 1000px;
height: 1000px;
max-height: 1000px;
overflow: hidden;
border: 5px solid black;
resize: both;
}
.inner {
position: absolute;
width: 1000px;
height: 1000px;
background-image: url(./images/0.jpg);
background-size: contain;
background-position: center;
background-repeat: no-repeat;
}
.outer .scroll_bar {
position: absolute;
margin: 0px 5%;
width: 90%;
height: 2px;
bottom: 10px;
background-color: blue;
border-radius: 5px;
}
.outer .scroll_bar .thumb {
position: absolute;
top: -5px;
height: 10px;
width: 120px;
border-radius: 5px;
background-color: #88cfff;
}
.outer .scroll_bar .thumb.hover {
background-color: #049aff;
cursor: pointer;
}
.unselectable {
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
</style>
<script type="text/javascript" src="lib/jquery-3.5.1.min.js"></script>
</head>
<body>
<div class="outer">
<div class="inner unselectable"></div>
<div class="scroll_bar">
<div class="thumb"></div>
</div>
</div>
<script>
class ScrollBar {
constructor(attach, target, scrollBar, scrollThumb) {
this.attach = attach;
this.target = target;
this.scrollBar = scrollBar;
this.scrollThumb = scrollThumb;
this.attachWidth = attach.outerWidth();
this.targetWidth = target.outerWidth();
this.scrollBarWidth = scrollBar.outerWidth();
this.scrollThumb.css('width', this.attachWidth / this.targetWidth * this.scrollBarWidth + 'px');
this.scrollThumbWidth = this.scrollThumb.outerWidth();
this.scrollThumb.hover(this.enter.bind(this), this.leave.bind(this));
this.scrollThumb.on('mousemove', this.move.bind(this));
this.scrollThumb.on('mouseup', this.up.bind(this));
this.scrollThumb.on('mousedown', this.down.bind(this));
window.addEventListener('mouseup', this.up.bind(this));
window.addEventListener('mousemove', this.move.bind(this));
}
show() {
this.scrollBar.show();
}
hide() {
this.scrollBar.hide();
}
/**
* @param {MouseEvent} event
*/
move(event) {
if (this.pressed) {
let x = event.pageX - this.startX;
let right = this.scrollBarWidth - this.scrollThumbWidth;
this.ratio += x / right;
this.startX += x;
}
}
up(event) {
this.pressed = false;
this.leave();
}
down(event) {
this.pressed = true;
this.startX = event.pageX;
}
enter() {
this.scrollThumb.addClass('hover');
}
leave() {
if (!this.pressed)
this.scrollThumb.removeClass('hover');
}
/**
* @brief 改变滑块位置的比率并控制 target 滚动
* @param {number} ratio 比率 [0.0 ~ 1.0]
*/
set ratio(ratio) {
ratio = ratio < 0.0 ? 0.0 : ratio > 1.0 ? 1.0 : ratio;
this.scrollRatio = ratio;
let left = (this.scrollBarWidth - this.scrollThumbWidth) * ratio + 'px';
this.scrollThumb.css('left', left);
this.target.css('left', (this.attachWidth - this.targetWidth) * ratio + 'px');
}
get ratio() {
return this.scrollRatio;
}
pressed = false;
startX = 0;
scrollRatio = 0.0;
scrollBar;
scrollBarWidth;
scrollThumb;
scrollThumbWidth;
target;
targetWidth;
attach;
attachWidth;
};
(() => {
let scrollBar = new ScrollBar($('.outer'), $('.inner')
, $('.outer .scroll_bar'), $('.outer .scroll_bar .thumb'));
})();
</script>
</body>
</html>