先有问题再有答案
滚动的前提是什么
浏览器提供了哪些滚动api
scroll, scrollTo, scrollBy, scrollIntoView 有什么区别?
这些api具体如何使用
这些api在使用上有什么问题
如何实现一个smooth效果的scrollTo api?
offsetTop、offsetLeft是相对直接父元素嘛?
scrollLeft、scrollTop是相对于哪个元素进行距离计算的
滚动的条件
内容尺寸超过容器区域
当内容的总尺寸(宽度或高度)超出容器的可视区域时,会触发滚动。这是滚动的根本原因,因为内容超出容器后,容器必须具有某种机制来让用户访问未显示的内容。
- 宽度或高度超过:无论是内容的宽度超过容器的宽度,还是内容的高度超过容器的高度,都会引发相应方向的滚动。
- 动态内容:通过 JavaScript 或其他方式动态增加内容,也可能导致内容尺寸超过容器区域,从而触发滚动。
容器具备滚动能力
这个条件要保证容器能够实际处理溢出的内容并显示滚动条。
这主要依赖于 CSS 的 overflow 属性的设置:
- overflow: auto:只有在内容溢出的情况下才会显示滚动条,这是最常用的选项。
- overflow: scroll:即使内容未溢出,也会始终显示滚动条。
- overflow: hidden:内容溢出时不显示滚动条,且内容不可见。
- overflow: visible:内容溢出时不会裁剪,内容会直接展示在容器之外。
scroll API
scroll & scrollTo
scroll 方法用于将元素滚动到 指定的坐标位置
。它可以对窗口或某个可滚动的元素应用。
scrollTo可以看做是 scroll 的别名,二者在用法上是完全相同的。
适用于需要将滚动条移动到一个绝对位置的情况。
scrollBy
scrollBy 方法也是用于窗口或可滚动元素,但与 scroll 和 scrollTo 方法不同的是,它滚动的是 相对当前位置的偏移量
,而不是绝对坐标。
适合相对当前滚动位置进行滚动的情况,如用户连续滚动页面或内容。
scrollIntoView
scrollIntoView 方法用于将元素滚动到视图中。如果元素在视口中已经可见,则不会有任何效果。
主要用于确保某个元素进入视口中,这个方法不需要开发者手动计算目标位置,可以方便快速实现某些滚动功能,可以简化代码复杂度 因此建议优先使用这个。
完整测试demo
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Scroll API Examples</title>
<style>
#scroll-container {
margin-top: 30px;
width: 100%;
height: 600px;
overflow: auto;
border: 1px solid #ccc;
position: relative;
}
.item {
width: 100%;
height: 200px;
background-color: lightgrey;
margin-bottom: 10px;
display: flex;
justify-content: center;
align-items: center;
}
</style>
</head>
<body>
<button onclick="useScroll()">scroll</button>
<button onclick="useScrollTo()">scrollTo</button>
<button onclick="useScrollBy()">scrollBy</button>
<button onclick="useScrollIntoView()">scrollIntoView</button>
<div id="scroll-container">
<div class="item" id="item1">Item 1</div>
<div class="item" id="item2">Item 2</div>
<div class="item" id="item3">Item 3</div>
<div class="item" id="item4">Item 4</div>
<div class="item" id="item5">Item 5</div>
<div class="item" id="item6">Item 6</div>
<div class="item" id="item7">Item 7</div>
<div class="item" id="item8">Item 8</div>
<div class="item" id="item9">Item 9</div>
<div class="item" id="item10">Item 10</div>
</div>
<script>
// 使用 scroll
function useScroll() {
const container = document.getElementById('scroll-container');
// 滚动到绝对位置 使用对象参数进行平滑滚动
container.scroll({ top: 200, left: 0, behavior: 'smooth' });
}
// 使用 scrollTo
function useScrollTo() {
const container = document.getElementById('scroll-container');
// 滚动到绝对位置 使用对象参数进行平滑滚动
container.scrollTo({ top: 200, left: 0, behavior: 'smooth' });
}
// 使用 scrollBy
function useScrollBy() {
const container = document.getElementById('scroll-container');
// 使用对象参数进行平滑滚动 基于当前滚动位置, 向下滚动200px
container.scrollBy({ top: 200, left: 0, behavior: 'smooth' });
}
// 使用 scrollIntoView
function useScrollIntoView() {
const target = document.getElementById('item8');
// 使用对象参数将目标元素平滑滚动到视口中心
target.scrollIntoView({
behavior: 'smooth'
});
}
</script>
</body>
</html>
scroll API的问题
- scroll & scrollTo & scrollBy 不够简单 需要计算具体的滚动位置
- scrollIntoView 这个api是够简单 但是有兼容问题 smooth 效果在部分浏览器上不能生效。
实现scrollIntoView方法
所以为了解决浏览器api的问题 我们可以自己实现一个scrollIntoView的方法。
const Direction = {
x: 'x',
y: 'y',
};
function getTargetPosition(element, direction) {
return direction === Direction.x ? element.offsetLeft : element.offsetTop;
}
function getScrollOffset(element, direction) {
return direction === Direction.x ? element.scrollLeft : element.scrollTop;
}
function scrollToPosition(element, position, isHorizontal) {
if (isHorizontal) {
element.scrollTo(position, 0);
} else {
element.scrollTo(0, position);
}
}
function animateScroll(element, start, end, isHorizontal) {
let distance = end - start;
let pos = start;
const step = () => {
pos += distance / 5;
distance = end - pos;
if (Math.abs(distance) < 1) {
scrollToPosition(element, end, isHorizontal);
} else {
scrollToPosition(element, pos, isHorizontal);
requestAnimationFrame(step);
}
};
step();
}
function scrollIntoView({ target, container, smooth = true, direction = Direction.y }) {
if (!target) {
return;
}
const parentEle = container ?? document.documentElement;
const isHorizontal = direction === Direction.x;
const targetPosition = getTargetPosition(target, direction);
const scrollOffset = getScrollOffset(parentEle, direction);
if (!smooth) {
scrollToPosition(parentEle, targetPosition, isHorizontal);
return;
}
animateScroll(parentEle, scrollOffset, targetPosition, isHorizontal);
}
scrollIntoView 方法可以将指定的目标元素滚动到视口内,支持水平和垂直两个方向,并且可以选择是否进行平滑滚动。
- 参数解构:解构传入的参数,获取目标元素、滚动容器、是否平滑滚动以及滚动方向。
- 默认容器:如果未提供 container 参数,默认使用文档作为滚动容器。
- 计算位置:调用 getTargetPosition 获取目标元素在滚动方向上的相对位置;调用 getScrollOffset 获取当前滚动容器的滚动偏移量。
- 直接滚动:如果 smooth 参数为 false,直接滚动到目标位置。
- 平滑滚动:如果 smooth 参数为 true,调用 animateScroll 进行平滑滚动。
使用如下:
function useScrollIntoView2() {
const target = document.getElementById('item8');
const container = document.getElementById('scroll-container');
scrollIntoView({
target,
container
});
}
效果如图:
offsetTop & offsetLeft
offsetTop 和 offsetLeft 属性是用于获取元素相对于其包含块(containing block)
的偏移量,而不是直接父元素。
确定包含块的元素,依赖于元素的 CSS 位置属性 (position)。不同的 position 值会导致不同的包含块。
当前元素position 为static, relative,absolute 包含块是最近的已定位祖先元素(即 position 属性为 relative、absolute、或 fixed)。如果没有已定位的祖先元素 包含块是初始包含块(对于 HTML 文档通常是 body)。
当前元素 position: fixed 包含块是视口(viewport)。
scrollTop & scrollLeft
表示当前已经滚动的水平或垂直距离。可以用来获取或设置滚动容器的滚动位置