- 作者:陈大鱼头
- github: KRISACHAN
前言
在日常的开发中,我们对 scroll
这个单词肯定不陌生。
例如因为看不惯浏览器默认样式而用 JS 一顿猛如虎操作的 自定义滚动条 。
或者是嗖~一下就到顶的 回到顶部 。
又或者是想去哪点哪的 标题导航 。
但是在过去的开发中,要实现这些功能并不是那么轻松的一件事情。
例如我们要实现一个有滚动效果的 回到顶部 功能,我们可能需要写下这些代码。
let timer = null;
let backTop = document.querySelector("#backTop");
backTop.addEventListener("click", () => {
cancelAnimationFrame(timer);
let startTime = +new Date();
let scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
let totalTime = 300;
timer = requestAnimationFrame(() => {
let lastTime = totalTime - Math.max(0, startTime - +new Date() + totalTime);
document.documentElement.scrollTop = document.body.scrollTop =
(lastTime * -scrollTop) / totalTime + scrollTop;
timer = requestAnimationFrame(func);
if (lastTime === totalTime) {
cancelAnimationFrame(timer);
}
});
});
(免责声明:伪代码未经测试,如有 BUG,跪求原谅。)
嘤,意思就是要写个动画,不断修改当前页面的垂直滚动距离,直到为 0。
(吃瓜群众:“很难嘛?是你太菜了吧,大叔!”)
但其实随着时间的推移, web api 以及 css 规范的不断改进,那些我们曾经认为实现起来很麻烦的功能也变得简单了起来。下面我们可以一起来探讨一下这些改进的内容。
Web API 中的 scroll 家族
我们来康康 scroll 家族 里有趣的 API。
scroll 与 scrollTo
scroll()
与 scrollTo
方法是用于在给定的元素中滚动到某个特定坐标的 Element 接口。
语法如下:
-
scroll/scrollTo(x, y)
x
:元素要移动的位置横坐标。y
:元素要移动的位置纵坐标。
-
scroll/scrollTo(options)
options
支持属性有left
,top
以及behavior
top
:元素要移动的位置横坐标。left
:元素要移动的位置纵坐标。behavior
:元素的运动模式,如果是auto
,则没有动画效果,如果是smooth
,则是平滑滚动。
我们来康康栗子:
上面例子来自 MDN 的 GitHub 仓库dom-examples
核心 JS 代码如下:
let scrollOptions;
const form = document.querySelector("form");
const leftInput = document.getElementById("left");
const topInput = document.getElementById("top");
const scrollInput = document.getElementById("scroll");
form.addEventListener("submit", e => {
e.preventDefault();
scrollOptions = {
left: leftInput.value,
top: topInput.value,
behavior: scrollInput.checked ? "smooth" : "auto"
};
window.scrollTo(scrollOptions);
});
scrollBy
scrollBy()
方法是使得元素滚动一段特定距离的 Element 接口。
语法如下:
-
scrollBy(x, y)
x
:元素要移动的位置横坐标。y
:元素要移动的位置纵坐标。
-
scrollBy(options)
options
支持属性有left
,top
以及behavior
top
:元素要移动的位置横坐标。lef:
:元素要移动的位置纵坐标。behavior
:元素的运动模式,如果是auto
,则没有动画效果,如果是smooth
,则是平滑滚动。
再举个栗子:
核心代码如下:
let scrollOptions;
const form = document.querySelector("form");
const leftInput = document.getElementById("left");
const topInput = document.getElementById("top");
const scrollInput = document.getElementById("scroll");
form.addEventListener("submit", e => {
e.preventDefault();
scrollOptions = {
left: leftInput.value,
top: topInput.value,
behavior: scrollInput.checked ? "smooth" : "auto"
};
window.scrollBy(scrollOptions);
});
Mmmmm,没错,就是只是把上面的 DEMO 中的scrollTo
改为scrollBy
而已。非常的机智~
我们再来看看上述三个API的兼容性:
Mmmm,兼容性挺好的。
(吃瓜群众:IE没有人权?????????)
Element.scrollIntoView
Element.scrollIntoView()
方法可以让当前的元素滚动到浏览器窗口的可视区域内。
语法如下:
-
scrollIntoView(alignToTop)
alignToTop
是一个布尔值,如果不填则默认为true
。相当于{block: 'start', inline: ‘nearest‘}
。如果值为
true
,则元素的顶端将和其所在滚动区的可视区域的顶端对齐。如果为false
,则是底端对齐。相当于{block: 'end', inline: 'nearest'}
-
scrollIntoView(scrollIntoViewOptions)
scrollIntoViewOptions
包含下列属性的对象:behavior
:元素的运动模式,如果是auto
,则没有动画效果,如果是smooth
,则是平滑滚动。block
:定义垂直方向的对齐方式,值可以是start
,center
,end
或nearest
之一。默认为nearest
。inline
:定义水平方向的对齐方式,值可以是start
,center
,end
或nearest
之一。默认为nearest
。
来来来,我给大家解释一下block
跟inline
的可选值到底是怎么回事:
start
:跟当前元素它爹的头发(顶部)对齐。center
:跟当前元素它爹的肚子(中间)对齐。end
:跟当前元素它爹的 jio(底部)对齐。nearest
:就近原则,挨哪里近去哪,如果在中间就不动。
如果是block
,就当元素是站着的(从上往下),如果是inline
,就当元素是躺着的(从左到右)。当然,前提是默认的writing-mode: horizontal-tb
。
有点绕?快来康康栗子呀:codepen.io/krischan77/…
好了,上述就是杀马特,哦不是,scroll
家族的特性了。(废弃或准备废弃的就没往上写了。)
最后我们同样来看看兼容性:
嗯,还行,IE都能用了。。。
至此,我们Web AP里的杀马特家族,哦不是,scroll家族已介绍完毕,像Element.scrollIntoViewIfNeed
这种不染发(不是标准)的API,就没有介绍了,有兴趣的可以自己去看看它到底能干啥。
CSS scroll
分享完 JS 中的 scroll
,我们再来了解下 CSS 中的 scroll
。
scroll-behavior
我们上面在讲这个 JS 中的 scroll
时,多次提到一个单词叫“behavior
”。
Mmmm,所以你们猜猜这个scroll-behavior
跟 JS 里的behavior
有木有关系?
嗯,没错,你们猜对啦,是有关系的。
(吃瓜群众:“都没人理你~”)
scroll-behavior
跟上述各个scroll
API 里的behavior
一样,是用来定义页面进行滚动操作时的动画效果。
如果定义为smooth
,则页面触发滚动操作时,就会有滚动的效果,如果为auto
,则跟原来一样,是瞬间移动到指定位置。这指的是类似于点击#hash
跳转一样的触发,而不是滑动滚动条。
其效果可以参照本文第一小节的 DEMO。
兼容性就如图:
Scroll Snap
CSS Scroll Snap 是 CSS 中一个比较新的独立模块,它的第一个正式版本CSS Scroll Snap 模块 Level 1也是在 2019 年 3 月 19 日才发布。
CSS Scroll Snap 模块 可以让页面容器停止滚动时,捕捉并让其自动滑动到指定元素的指定位置。
一给我哩 giaogiao!这可是非常了不起的特性啊~
它分了两部分,一部分作用于滚动容器上,一部分作用于相对的滚动子元素上,具体关系如下表:
作用于滚动容器 | 作用于滚动子元素 |
---|---|
scroll-snap-type | scroll-snap-align |
scroll-padding | scroll-snap-stop |
scroll-margin |
scroll-snap-type
scroll-snap-type
属性指定能不能去捕捉当前滚动的容器并让它对齐,以及所执行的方向跟严格程度。
它可选的方向值有:
x
:捕捉 X 轴上的位置y
:捕捉 Y 轴上的位置block
:捕捉块轴上的位置(逻辑意义上与 y 一样)inline
:捕捉内联轴上的位置(逻辑意义上与 x 一样)both
:捕捉两个方向上的位置
它可选的严格值有:
none
:默认值,Mmmm,啥也不干proximity
:一个感性的值,如果元素进入到了容器的捕捉位置范围内,则进行捕捉并滚动,否则就不管,至于这个范围是多少,约莫着 45%的位置吧(手动测的,W3C 没给出具体算法,瞎猜吧,哈哈哈)。mandatory
:一个靠谱点的值,只要有参数,停止滚动时就肯定能对齐。
我们来康康这玩意到底是啥效果:
以上 DEMO 来自于 MDN 的scroll-snap-type
scroll-snap-align
scroll-snap-align
属性指定捕捉容器要捕捉的捕捉子元素位置。可选的值如下:
none
:默认值,啥也不干 0.0。start
:跟开始位置对齐。end
:跟结束位置对齐。center
:居中对齐。
效果如下:
以上 DEMO 来自于 Andy Adams 的scroll-snap-align
scroll-snap-stop
因为 Scroll Snap 元素会有几个捕捉的位置,而scroll-snap-stop
可以控制到达这些位置之后是否被捕获,还是到了指定的位置才被捕获。可选属性如下:
normal
:默认值,滚动的时候,可以忽略捕捉位置。always
:滚动的时候,不能忽略捕捉位置,还必须定位到第一个捕捉元素的位置。
栗子如下:
以上 DEMO 来自于 MDN 的scroll-snap-stop
scroll-margin
scroll-margin
是一个简写属性,跟margin
一样,有不同的逻辑属性可以选。它可以设置元素跟滚动条之间的外边框大小。我们看两个动图对比下区别。
当我们点击#hash
跳转时。
普通操作:
h3 {
}
添加了scroll-margin-top
:
h3 {
scroll-margin-top: 5rem;
}
上面 DEMO 来自于 Chris Coyier 的Fixed Headers and Jump Links? The Solution is scroll-margin-top
从上面的两个 DEMO,我们可以清晰地对比,假设#hash
导航的元素有修改scroll-margin
,那么最终跳转的位置是会以scroll-margin
的值为边界的。
不仅如此,我们再看下面的 DEMO:
以上 DEMO 源自于 Andy Adams 的scroll-margin
当我们设置了scroll-margin
的元素进入 scroll 的可视区域时,浏览器会根据当前元素就近的scroll-margin
值,移动到相应的位置。
scroll-margin
的复写属性有以下几个:
scroll-margin-top
scroll-margin-right
scroll-margin-bottom
scroll-margin-left
scroll-margin-block
scroll-margin-inline
scroll-margin-block-start
scroll-margin-inline-start
scroll-margin-block-end
scroll-margin-inline-end
scroll-padding
scroll-padding
跟scroll-margin
类型,只不过跟padding
与margin
的一样,有内外边距的区别。
来个 DEMO:
以上 DEMO 源自于 Andy Adams 的scroll-padding
scroll-padding
的复写属性也同样有以下几个:
scroll-padding-top
scroll-padding-right
scroll-padding-bottom
scroll-padding-left
scroll-padding-block
scroll-padding-inline
scroll-padding-block-start
scroll-padding-inline-start
scroll-padding-block-end
scroll-padding-inline-end
以上API兼容性如下:
CSS overscroll
overscroll-behavior
overscroll-behavior
是 2019 年 6 月份 W3C 第一次发布的CSS 过渡滚动行为模块 Level 1里唯一一个属性。
overscroll-behavior
让你可以控制浏览器滚动到边界时的表现。
它也是个简写属性,具体的属性有:
overscroll-behavior-x
:正常情况下,处理横轴滚动条滚动到边界时的表现。overscroll-behavior-y
:正常情况下,处理纵轴滚动条滚动到边界时的表现。overscroll-behavior-inline
:跟overscroll-behavior-x
一样。overscroll-behavior-block
:跟overscroll-behavior-y
一样
可选的值为:
auto
:默认值。contain
:当一个元素滚动到边界时,不会再影响临近的滚动元素。none
:当一个元素滚动到边界时,不仅不会不会再影响临近的滚动元素,连默认滚动到边界的表现都会被阻止。
栗子如下:
使用了overscroll-behavior: contain;
默认情况
兼容性如下:
课外姿势
新旧逻辑属性
不知道各位有没有注意上述各个属性的值,除了有常规的x
,y
,top
,right
,bottom
跟left
之外,还有四个比较少见的值block
,inline
,start
跟end
。
所以这到底是什么呢?
其实是因为 W3C 为了照顾到非西文排序国家的书写习惯,特意修改了 CSS 的逻辑属性。
对于像我们国家或者是美国这样,文档排列是从上到下,从左到右的,top
、 right
、 bottom
跟 left
就很好理解。
但是像日本或者阿拉伯等书写排列跟我们不一样的国家,在逻辑上就会有不合理的地方,例如:
- 在阿拉伯,他们的
padding-left
实际上方向是我们的padding-right
- 在日本,他们的
padding-left
实际上方向是我们的padding-top
按照上面的情况,这就比较诡异。所以 W3C 出来新的逻辑属性,新旧的对比如下:
旧的逻辑属性 | 新的逻辑属性 |
---|---|
top | inset-block-start |
bottom | inset-block-end |
left | inset-inline-start |
right | inset-inline-end |
margin-top | margin-block-start |
margin-right | margin-inline-end |
margin-bottom | margin-block-end |
margin-left | margin-inline-start |
border-top | border-block-start |
border-right | border-inline-end |
border-bottom | border-block-end |
border-left | border-inline-start |
padding-top | padding-block-start |
padding-right | padding-inline-end |
padding-bottom | padding-block-end |
padding-left | padding-inline-start |
width | inline-size |
height | block-size |
总结一下就是横坐标为inline
,纵坐标为block
,起始位置为start
,结束位置为end
。
最后来个特效
这是一个利用scroll-behavior: smooth
的特性写出来的效果,之前跟朋友们一起出去玩,我们进行了许多的活动。其中有一项游戏就是“你比划我猜”,作为策划者的鱼头,自然不能放过这次机会,遂用技术小秀了一把。
我们先来看看效果。
源码地址在这:
由于代码太长,就不完全贴出来了,但是核心逻辑就是利用scroll-behavior: smooth;
来控制#hash
跳转时的效果,为了不污染 url,同时利用了 history api 来维护正常的 url。大概就是酱紫:
let pageIndex = 1;
const urlChangeHandler = event => {
const { newURL } = event;
const current = newURL.replace(/.+\#page\-(\d)/, "$1");
pageIndex = +current;
console.log(pageIndex);
history.pushState(
{},
window.location.href,
window.location.origin + window.location.pathname
);
};
window.onhashchange = urlChangeHandler;
大家也不妨尝试下用所掌握的姿势增添点生活情趣呀~
后记
吃瓜群众:我看完了整篇,没看到哪里有跟忍术相关的内容啊?骗我流量,赔钱。
鱼头:没有又咋啦?说好的宠我,你现在凶我是什么意思?
参考资料
- scrollIntoView block vs inline
- CSSOM View Module
- Element.scrollIntoView()
- CSS Scroll Snap Module Level 1
- CSS TRICK scroll-snap-type
- MDN scroll-snap-type
- CSS TRICK scroll-snap-align
- scroll-snap-stop
- scroll-margin
- scroll-padding
- Fixed Headers and Jump Links? The Solution is scroll-margin-top
- caniuse
后记
如果你喜欢探讨技术,或者对本文有任何的意见或建议,非常欢迎加鱼头微信好友一起探讨,当然,鱼头也非常希望能跟你一起聊生活,聊爱好,谈天说地。 鱼头的微信号是:krisChans95