滚动条可以是window(浏览器)的,也可以是单个div的。基本上二者性质一致。来自AI的回答:
浏览器的滚动条和div的滚动条在功能上是相同的,都是用来在有限的视图区域内浏览更多的内容。但在使用和样式上,它们有一些区别:
- 使用上的区别:浏览器的滚动条是用来控制整个网页视图的滚动,而div的滚动条是用来控制div元素内部的滚动。当网页或div的内容超出其视图区域时,浏览器会自动显示滚动条。
- 样式上的区别:浏览器的滚动条样式通常是由浏览器或操作系统决定的,不同的浏览器和操作系统的滚动条样式可能会有所不同。而div的滚动条样式可以通过css来自定义,例如可以使用
::-webkit-scrollbar伪元素来自定义滚动条的样式(注意这是Webkit内核浏览器的特性,不是所有浏览器都支持)。 - 交互上的区别:浏览器的滚动条通常会响应鼠标滚轮和键盘上下键的操作,而div的滚动条只有在div获得焦点时才会响应这些操作。
- 性能上的区别:频繁的div滚动可能会导致页面重排和重绘,影响页面性能,而浏览器的滚动通常不会有这个问题。
针对滚动条有一些典型的开发需求场景。
判断滚动条是否到底
这里判断的是浏览器滚动条而不是单个div的。下同。
window.onscroll = function() {
// 滚动的高度
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
// dom的高度
const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
// 可视区高度
const clientHeight = document.documentElement.clientHeight || document.body.clientHeight;
if (scrollTop + clientHeight >= scrollHeight) {
console.log('Scrolled to bottom');
}
};
首先获取滚动元素的scrollTop、clientHeight和scrollHeight属性。scrollTop属性表示元素已经滚动过的距离,clientHeight属性表示元素的可视区域的高度,scrollHeight属性表示元素的总高度。
然后比较scrollTop + clientHeight和scrollHeight,如果它们相等或者前者大于后者,那么就表示滚动条已经滚动到底部了。
判断滚动条滚动的方向
let lastScrollTop = 0;
window.addEventListener('scroll', function() {
let scrollTop = window.pageYOffset || document.documentElement.scrollTop;
if (scrollTop > lastScrollTop){
console.log('scrolling down');
} else {
console.log('scrolling up');
}
lastScrollTop = scrollTop;
});
首先定义一个变量lastScrollTop来保存上一次的滚动位置。然后监听window的scroll事件。在每次滚动时,我们获取当前的滚动位置,然后与上一次的滚动位置进行比较,以判断滚动的方向。最后,更新上一次的滚动位置。
这个方法只能判断垂直滚动的方向。如果想要判断水平滚动的方向,可以使用scrollLeft属性代替scrollTop属性。
滚动条滚动到锚点
只要在url后面加上hash就可以让滚动条滚动到对应的锚点,再代码上需要href和id关联起来。
<a href="#section1">Go to Section 1</a>
<a href="#section2">Go to Section 2</a>
<div id="section1" style="height: 2000px; background: lightblue;">
<h1>Section 1</h1>
</div>
<div id="section2" style="height: 2000px; background: lightgreen;">
<h1>Section 2</h1>
但上面的方式并非滚动的而是瞬间到达锚点位置。想要平滑滚动可以添加代码
document.querySelectorAll('a').forEach(function(link) {
link.addEventListener('click', function(event) {
event.preventDefault();
var target = document.querySelector(this.getAttribute('href'));
window.scrollTo({
top: target.offsetTop,
behavior: 'smooth' // 平滑实现
});
});
})
获取到所有的链接元素,然后为每个链接添加一个click事件监听器。当点击链接时,阻止链接的默认行为(即跳转到锚点),然后获取到锚点元素,最后使用window.scrollTo方法将页面滚动到锚点的位置。
滚动条各种运动方式回到顶部
这是之前总结实现的,实际是补间动画,当触发回到顶部时,将运动变化规则化,从而实现各种形式的运动回到顶部。
需要借助tween.js提供的一系列缓动函数。详情可参考:easings.net/zh-cn
/*
* Tween.js
* t: current time(当前时间);
* b: beginning value(初始值);
* c: change in value(变化量);
* d: duration(持续时间)。
* you can visit 'http://easings.net/zh-cn' to get effect
*/
var Tween = {
Linear: function (t, b, c, d) {
return c * t / d + b
},
Quad: {
easeIn: function (t, b, c, d) {
return c * (t /= d) * t + b
},
easeOut: function (t, b, c, d) {
return -c * (t /= d) * (t - 2) + b
},
easeInOut: function (t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t + b
return -c / 2 * ((--t) * (t - 2) - 1) + b
}
},
Cubic: {
easeIn: function (t, b, c, d) {
return c * (t /= d) * t * t + b
},
easeOut: function (t, b, c, d) {
return c * ((t = t / d - 1) * t * t + 1) + b
},
easeInOut: function (t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t * t + b
return c / 2 * ((t -= 2) * t * t + 2) + b
}
},
Quart: {
easeIn: function (t, b, c, d) {
return c * (t /= d) * t * t * t + b
},
easeOut: function (t, b, c, d) {
return -c * ((t = t / d - 1) * t * t * t - 1) + b
},
easeInOut: function (t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b
return -c / 2 * ((t -= 2) * t * t * t - 2) + b
}
},
Quint: {
easeIn: function (t, b, c, d) {
return c * (t /= d) * t * t * t * t + b
},
easeOut: function (t, b, c, d) {
return c * ((t = t / d - 1) * t * t * t * t + 1) + b
},
easeInOut: function (t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t * t * t * t + b
return c / 2 * ((t -= 2) * t * t * t * t + 2) + b
}
},
Sine: {
easeIn: function (t, b, c, d) {
return -c * Math.cos(t / d * (Math.PI / 2)) + c + b
},
easeOut: function (t, b, c, d) {
return c * Math.sin(t / d * (Math.PI / 2)) + b
},
easeInOut: function (t, b, c, d) {
return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b
}
},
Expo: {
easeIn: function (t, b, c, d) {
return (t == 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b
},
easeOut: function (t, b, c, d) {
return (t == d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b
},
easeInOut: function (t, b, c, d) {
if (t == 0) return b
if (t == d) return b + c
if ((t /= d / 2) < 1) return c / 2 * Math.pow(2, 10 * (t - 1)) + b
return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b
}
},
Circ: {
easeIn: function (t, b, c, d) {
return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b
},
easeOut: function (t, b, c, d) {
return c * Math.sqrt(1 - (t = t / d - 1) * t) + b
},
easeInOut: function (t, b, c, d) {
if ((t /= d / 2) < 1) return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b
return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b
}
},
Elastic: {
easeIn: function (t, b, c, d, a, p) {
var s
if (t == 0) return b
if ((t /= d) == 1) return b + c
if (typeof p === 'undefined') p = d * 0.3
if (!a || a < Math.abs(c)) {
s = p / 4
a = c
} else {
s = p / (2 * Math.PI) * Math.asin(c / a)
}
return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b
},
easeOut: function (t, b, c, d, a, p) {
var s
if (t == 0) return b
if ((t /= d) == 1) return b + c
if (typeof p === 'undefined') p = d * 0.3
if (!a || a < Math.abs(c)) {
a = c
s = p / 4
} else {
s = p / (2 * Math.PI) * Math.asin(c / a)
}
return (a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b)
},
easeInOut: function (t, b, c, d, a, p) {
var s
if (t == 0) return b
if ((t /= d / 2) == 2) return b + c
if (typeof p === 'undefined') p = d * (0.3 * 1.5)
if (!a || a < Math.abs(c)) {
a = c
s = p / 4
} else {
s = p / (2 * Math.PI) * Math.asin(c / a)
}
if (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b
return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * 0.5 + c + b
}
},
Back: {
easeIn: function (t, b, c, d, s) {
if (typeof s === 'undefined') s = 1.70158
return c * (t /= d) * t * ((s + 1) * t - s) + b
},
easeOut: function (t, b, c, d, s) {
if (typeof s === 'undefined') s = 1.70158
return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b
},
easeInOut: function (t, b, c, d, s) {
if (typeof s === 'undefined') s = 1.70158
if ((t /= d / 2) < 1) return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b
return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b
}
},
Bounce: {
easeIn: function (t, b, c, d) {
return c - Tween.Bounce.easeOut(d - t, 0, c, d) + b
},
easeOut: function (t, b, c, d) {
if ((t /= d) < (1 / 2.75)) {
return c * (7.5625 * t * t) + b
} else if (t < (2 / 2.75)) {
return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b
} else if (t < (2.5 / 2.75)) {
return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b
} else {
return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b
}
},
easeInOut: function (t, b, c, d) {
if (t < d / 2) {
return Tween.Bounce.easeIn(t * 2, 0, c, d) * 0.5 + b
} else {
return Tween.Bounce.easeOut(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b
}
}
}
}
当点击回到顶部,触发方法。通过切换缓动函数实现各种运动形式的回到顶部。
gotoPageTop () {
const scrollTop = document.documentElement.scrollTop ||
document.body.scrollTop
let t = 0 // 开始时间
const b = scrollTop // 开始位置
const c = -scrollTop // 变化值
const d = 60
const doc = document
const win = window
let reAnition = null
const step = () => {
const value = Tween.Cubic.easeOut(t, b, c, d)
if (doc.documentElement.scrollTop) {
doc.documentElement.scrollTop = value
}
if (doc.body.scrollTop) {
doc.body.scrollTop = value
}
t++
if (t <= d) {
// 继续运动 使用requestAnimationFrame
reAnition = win.requestAnimationFrame(step)
} else {
win.cancelAnimationFrame(reAnition)
// 动画结束
}
}
step()
}
自制滑块-水平滚动条
<div id="container">
<div id="content">Your content here...</div>
</div>
<div id="slider">
<div id="handle"></div>
</div>
<style>
#container {
width: 100%;
overflow: auto;
}
#content {
height: 50px;
width: 200%; /* make it wider than container to enable horizontal scroll */
}
#slider {
width: 100%;
height: 20px;
background: #ccc;
position: relative;
}
#handle {
width: 50px;
height: 20px;
background: #333;
cursor: pointer;
position: absolute;
}
</style>
首先获取到容器元素(container)、滑块元素(slider)和手柄元素(handle)。然后设置手柄的宽度为滑块宽度乘以容器宽度除以容器滚动宽度,这样手柄的位置就可以对应到容器的滚动位置。
let container = document.getElementById('container');
let slider = document.getElementById('slider');
let handle = document.getElementById('handle');
// update handle width when content width changes
handle.style.width = slider.offsetWidth * container.clientWidth / container.scrollWidth + 'px';
添加一个mousedown事件监听器到手柄上,当鼠标按下时,记录下当前的鼠标位置和滚动位置,然后在window的mousemove事件中,根据鼠标移动的距离来更新滚动位置。
最后添加一个scroll事件监听器到容器上,当容器的滚动位置改变时,将手柄的位置设置为滑块宽度乘以滚动位置除以滚动宽度。
这样无论是拖动手柄还是滚动容器,都会同步更新另一个元素的位置,实现了手柄和真实滚动条的同步
// drag handle to scroll
handle.onmousedown = function(e) {
let startX = e.clientX;
let startLeft = container.scrollLeft;
window.onmousemove = function(e) {
let deltaX = e.clientX - startX;
container.scrollLeft = startLeft + deltaX * container.scrollWidth / slider.offsetWidth;
};
window.onmouseup = function() {
window.onmousemove = null;
window.onmouseup = null;
};
};
// scroll to move handle
container.onscroll = function() {
handle.style.left = slider.offsetWidth * this.scrollLeft / this.scrollWidth + 'px';
};
本文完。