关于滚动条,有几个公式要先了解下:
scale
= scrollBar.clientHeight / scrollBarThumb.clientHeight
= page.scrollHeight / page.clientHeight
// = (scrollBar.clientHeight - scrollBarThumb.clientHeight) / page.scrollTop
翻译下就是:
缩放比例
= 滚动条高度(x) / 滑块高度(y)
= 页面总高度(a) / 页面可视区高度(b)
// = 滑块顶部距离(x-y) / 页面可视区顶部距离(a-b)
比如:
- 比例是5
- 滚动条高度是4,滑块高度是0.8
- 页面总高度为25,页面可视区高度为5
- 页面可视区顶部距离为25 - 5,滑块顶部距离为4 - 0.8
5
= 25 / 5
= 4 / 0.8
!= (25 - 5) / (4 - 0.8)
注意x/y
等于a/b
时,不一定等于x-y/a-b
。
另外,上述只是一个理想例子,比较好处理,如果你硬要滑块高度变小,也没问题。下面这个比例才是滑动时是真正要处理的:
页面可视区顶部最大距离(a - b, maxScrollTop) / 滑块顶部最大距离(x - y)
= 当前页面顶部距离scrollTop / 当前滑块顶部距离
还是以上面的数值为例,(25 - 5) / (4 - 0.8)
等于 6.25
- 滑块顶部高度为1时,页面顶部距离应为
6.25
- 滑块顶部高度为2时,页面顶部距离应为
6.25 * 2 = 12.5
- 滑块顶部高度为3时,页面顶部距离应为
6.25 * 3 = 18.75
- 滑块顶部高度为3.2时,页面顶部距离应为
6.25 * 3.2 = 20
,此时页面在最底部,20 + 5 正好等于 25。
下面是基本的结构,一个scrollBar
包住scrollBarThumb
:
<div
v-show="scrollBarNum>2 && (showThis || isHoldDown)"
ref="scrollBar"
class="tip-match-scrollbar"
:class="{'tip-match-scrollbar--active': isHoldDown}"
:style="{height: `${scrollBarTotalHeight}px`}"
@click.stop="onClickScrollBar"
>
<div
ref="scrollBarThumb"
class="tip-match-scrollbar-thumb"
:style="{transform:`translate3d(0,${scrollTranslateY}px,0)`, height: `${thumbHeight}px`}"
@touchmove="onTouchMoveScrollBar"
@touchstart="onTouchStartScrollBar"
@touchend="onTouchEndScrollBar"
>
<div class="tip-match-team-member-tip">
{{ +currentPageNum }}/{{ Math.ceil(scrollBarNum) - 1 }}页
</div>
</div>
</div>
获取scrollBarThumb拖动的距离:
// 触摸开始时:
startY = e.touches[0].clientY;
disY = startY - this.thumbTranslateY;
// 触摸滑动时:
const endY = e.touches[0].clientY;
let T = endY - disY; // 拖动距离
注意
clientY
是鼠标相对于浏览器(这里说的是浏览器的有效区域)左上角y轴的坐标; 不随滚动条滚动而改变;offsetY
是鼠标相对于事件源左上角Y轴的坐标
点击scrollBar时,thumb也应该跳到对应位置:
const { offsetY } = e;
this.thumbTranslateY = offsetY;
let scrollTop = offsetY * this.getPageBarScale();
pageScrollRef.scrollTo(pageScrollRef.scrollLeft, scrollTop);
下面是一些帮助方法:
// 页面顶部最大距离
getPageMaxTop() {
const { pageScrollRef } = this;
return pageScrollRef.scrollHeight - pageScrollRef.clientHeight;
},
// scrollBar顶部最大距离
getScrollBarMaxTop() {
const { scrollBar, scrollBarThumb } = this.$refs;
return scrollBar.clientHeight - scrollBarThumb.clientHeight;
},
// 修正滑块顶部高度
reviseThumbTop(T) {
const maxTop = this.getScrollBarMaxTop();
if (T < 0) {
T = 0;
} else if (T > maxTop) {
T = maxTop;
}
return T;
},
// 页面顶部最大距离/scrollBar顶部最大距离
getPageBarScale() {
const scale = this.getPageMaxTop() / this.getScrollBarMaxTop();
return scale;
},
// 获取当前页数
getCurrentPageNum(scrollTop) {
const { pageScrollRef } = this;
this.currentPageNum = scrollTop === 0 ? 1 : Math.ceil(scrollTop / pageScrollRef.clientHeight) ;
},
// 让页面滚动
makePageScroll(scrollTop) {
const { pageScrollRef } = this;
if (!pageScrollRef) return;
pageScrollRef.scrollTo(pageScrollRef.scrollLeft, scrollTop);
},
scrollBarThumb拖动时的几个事件:
let startY = 0;
let disY = 0;
let showThisTimer = null;
let holdDownTimer = null;
onTouchEndScrollBar() {
clearTimeout(holdDownTimer);
this.isHoldDown = false;
},
onTouchMoveScrollBar(e) {
if (!this.isHoldDown) return;
this.onShowThis();
e.preventDefault();
const endY = e.touches[0].clientY;
let T = endY - disY;
T = this.reviseThumbTop(T);
this.scrollTranslateY = T;
this.$forceUpdate();
const scale = this.getPageBarScale();
let scrollTop = T * scale;
if (T === this.getScrollBarMaxTop()) {
scrollTop = this.getPageMaxTop();
}
this.makePageScroll(scrollTop);
this.getCurrentPageNum(scrollTop);
},
// 触发滚动条
onTouchStartScrollBar(e) {
startY = e.touches[0].clientY;
disY = startY - this.scrollTranslateY;
clearTimeout(holdDownTimer);
holdDownTimer = setTimeout(() => {
this.isHoldDown = true;
}, 200);
}
点击scrollBar的某一处时的事件:
onClickScrollBar(e) {
this.clickScrollBar = true;
this.onShowThis();
const { offsetY } = e;
this.scrollTranslateY = offsetY;
let scrollTop = offsetY * this.getPageBarScale();
const maxTop = this.getPageMaxTop();
if (scrollTop > maxTop) {
scrollTop = maxTop;
}
this.makePageScroll(scrollTop);
setTimeout(() => {
this.clickScrollBar = false;
});
}
此外还有一个监听页面主体滚动的方法:
onScrollMain(e) {
if (this.isHoldDown || this.clickScrollBar) return;
this.onShowThis();
const { scrollTop } = e.target;
const scale = this.getPageBarScale();
this.scrollTranslateY = scrollTop / scale;
this.getCurrentPageNum(scrollTop);
}