• 作者:陈大鱼头
  • github:KRISACHAN

前言

在日常的开发中,我们对 ​scroll​ 这个单词肯定不陌生。

例如因为看不惯浏览器默认样式而用 JS 一顿猛如虎操作的 自定义滚动条

或者是嗖~一下就到顶的 回到顶部

又或者是想去哪点哪的 标题导航

忍法,scroll 翻滚之术!_动画效果

但是在过去的开发中,要实现这些功能并不是那么轻松的一件事情。

例如我们要实现一个有滚动效果的 回到顶部 功能,我们可能需要写下这些代码。

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。

(吃瓜群众:“很难嘛?是你太菜了吧,大叔!”)

忍法,scroll 翻滚之术!_css_02

但其实随着时间的推移, web api 以及 css 规范的不断改进,那些我们曾经认为实现起来很麻烦的功能也变得简单了起来。下面我们可以一起来探讨一下这些改进的内容。

Web API 中的 scroll 家族

我们来康康 scroll 家族 里有趣的 API。

scroll 与 scrollTo

​scroll()​​scrollTo​ 方法是用于在给定的元素中滚动到某个特定坐标的 Element 接口。

语法如下:

  1. ​scroll/scrollTo(x, y)​
  • ​x​​:元素要移动的位置横坐标。
  • ​y​​:元素要移动的位置纵坐标。
  • ​scroll/scrollTo(options)​​​​options​​支持属性有​​left​​,​​top​​以及​​behavior​
  • ​top​​:元素要移动的位置横坐标。
  • ​left​​:元素要移动的位置纵坐标。
  • ​behavior​​:元素的运动模式,如果是​​auto​​,则没有动画效果,如果是​​smooth​​,则是平滑滚动。

我们来康康栗子:

忍法,scroll 翻滚之术!_css_03

上面例子来自 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 接口。

语法如下:

  1. scrollBy(x, y)
  • ​x​​:元素要移动的位置横坐标。
  • ​y​​:元素要移动的位置纵坐标。
  • scrollBy(options)
    ​options​​支持属性有​​left​​,​​top​​以及​​behavior​
  • ​top​​:元素要移动的位置横坐标。
  • ​lef:​​:元素要移动的位置纵坐标。
  • ​behavior​​:元素的运动模式,如果是​​auto​​,则没有动画效果,如果是​​smooth​​,则是平滑滚动。

再举个栗子:

忍法,scroll 翻滚之术!_默认值_04

核心代码如下:

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​​而已。非常的机智~

忍法,scroll 翻滚之术!_默认值_05

我们再来看看上述三个API的兼容性:

忍法,scroll 翻滚之术!_css_06

Mmmm,兼容性挺好的。

(吃瓜群众:IE没有人权?????????)

Element.scrollIntoView

​Element.scrollIntoView()​ 方法可以让当前的元素滚动到浏览器窗口的可视区域内。

语法如下:

  1. scrollIntoView(alignToTop)
    ​alignToTop​​是一个布尔值,如果不填则默认为​​true​​。相当于​​{block: 'start', inline: ‘nearest‘}​​。
    如果值为​​true​​,则元素的顶端将和其所在滚动区的可视区域的顶端对齐。如果为​​false​​,则是底端对齐。相当于​​{block: 'end', inline: 'nearest'}​
  2. 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​​。

有点绕?快来康康栗子呀:https://codepen.io/krischan77/pen/mdJMQpz

忍法,scroll 翻滚之术!_默认值_07

好了,上述就是杀马特,哦不是,​​scroll​​家族的特性了。(废弃或准备废弃的就没往上写了。)

最后我们同样来看看兼容性:

忍法,scroll 翻滚之术!_动画效果_08

嗯,还行,IE都能用了。。。

至此,我们Web AP里的杀马特家族,哦不是,scroll家族已介绍完毕,像​​Element.scrollIntoViewIfNeed​​这种不染发(不是标准)的API,就没有介绍了,有兴趣的可以自己去看看它到底能干啥。

忍法,scroll 翻滚之术!_css_09

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 翻滚之术!_动画效果_08

Scroll Snap

CSS Scroll Snap 是 CSS 中一个比较新的独立模块,它的第一个正式版本CSS Scroll Snap 模块 Level 1也是在 2019 年 3 月 19 日才发布。

CSS Scroll Snap 模块 可以让页面容器停止滚动时,捕捉并让其自动滑动到指定元素的指定位置。

一给我哩 giaogiao!这可是非常了不起的特性啊~

忍法,scroll 翻滚之术!_css_11

它分了两部分,一部分作用于滚动容器上,一部分作用于相对的滚动子元素上,具体关系如下表:

作用于滚动容器

作用于滚动子元素

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​​ :一个靠谱点的值,只要有参数,停止滚动时就肯定能对齐。

我们来康康这玩意到底是啥效果:

忍法,scroll 翻滚之术!_css_12

以上 DEMO 来自于 MDN 的scroll-snap-type

scroll-snap-align

​scroll-snap-align​​属性指定捕捉容器要捕捉的捕捉子元素位置。可选的值如下:

  • ​none​​ :默认值,啥也不干 0.0。
  • ​start​​ :跟开始位置对齐。
  • ​end​​ :跟结束位置对齐。
  • ​center​​ :居中对齐。

效果如下:

忍法,scroll 翻滚之术!_动画效果_13

以上 DEMO 来自于 Andy Adams 的scroll-snap-align

scroll-snap-stop

因为 Scroll Snap 元素会有几个捕捉的位置,而​​scroll-snap-stop​​可以控制到达这些位置之后是否被捕获,还是到了指定的位置才被捕获。可选属性如下:

  • ​normal​​ :默认值,滚动的时候,可以忽略捕捉位置。
  • ​always​​ :滚动的时候,不能忽略捕捉位置,还必须定位到第一个捕捉元素的位置。

栗子如下:

忍法,scroll 翻滚之术!_css_14

以上 DEMO 来自于 MDN 的scroll-snap-stop

scroll-margin

​scroll-margin​​​是一个简写属性,跟​​margin​​一样,有不同的逻辑属性可以选。它可以设置元素跟滚动条之间的外边框大小。我们看两个动图对比下区别。

当我们点击​#hash​​跳时。

普通操作:

忍法,scroll 翻滚之术!_动画效果_15

h3 {
}

添加了​​scroll-margin-top​​:

忍法,scroll 翻滚之术!_动画效果_16

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:

忍法,scroll 翻滚之术!_默认值_17

以上 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:

忍法,scroll 翻滚之术!_默认值_18

以上 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兼容性如下:

忍法,scroll 翻滚之术!_css_19

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;​

忍法,scroll 翻滚之术!_动画效果_20

默认情况

忍法,scroll 翻滚之术!_默认值_21

兼容性如下:

忍法,scroll 翻滚之术!_默认值_22

课外姿势

新旧逻辑属性

不知道各位有没有注意上述各个属性的值,除了有常规的​​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 翻滚之术!_动画效果_23

最后来个特效

这是一个利用​​scroll-behavior: smooth​​的特性写出来的效果,之前跟朋友们一起出去玩,我们进行了许多的活动。其中有一项游戏就是“你比划我猜”,作为策划者的鱼头,自然不能放过这次机会,遂用技术小秀了一把。

我们先来看看效果。

忍法,scroll 翻滚之术!_动画效果_24

源码地址在这:

https://codepen.io/krischan77/pen/zYOjyry

由于代码太长,就不完全贴出来了,但是核心逻辑就是利用​​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;

大家也不妨尝试下用所掌握的姿势增添点生活情趣呀~

忍法,scroll 翻滚之术!_css_25

后记

吃瓜群众:我看完了整篇,没看到哪里有跟忍术相关的内容啊?骗我流量,赔钱。

鱼头:没有又咋啦?说好的宠我,你现在凶我是什么意思?

忍法,scroll 翻滚之术!_css_26


参考资料

1.scrollIntoView block vs inline[1]2.CSSOM View Module[2]3.Element.scrollIntoView()[3]4.CSS Scroll Snap Module Level 1[4]5.CSS TRICK scroll-snap-type[5]6.MDN scroll-snap-type[6]7.CSS TRICK scroll-snap-align[7]8.scroll-snap-stop[8]9.scroll-margin[9]10.scroll-padding[10]11.Fixed Headers and Jump Links? The Solution is scroll-margin-top[11]12.caniuse[12]

References

​[1]​​ scrollIntoView block vs inline: https://stackoverflow.com/questions/48634459/scrollintoview-block-vs-inline

​[2]​​ CSSOM View Module: https://www.w3.org/TR/cssom-view-1/

​[3]​​ Element.scrollIntoView(): https://developer.mozilla.org/zh-CN/docs/Web/API/Element/scrollIntoView

​[4]​​ CSS Scroll Snap Module Level 1: https://www.w3.org/TR/css-scroll-snap-1/

​[5]​​ CSS TRICK scroll-snap-type: https://css-tricks.com/almanac/properties/s/scroll-snap-type/

​[6]​​ MDN scroll-snap-type: https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type

​[7]​​ CSS TRICK scroll-snap-align: https://css-tricks.com/almanac/properties/s/scroll-snap-align/

​[8]​​ scroll-snap-stop: https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-stop

​[9]​​ scroll-margin: https://css-tricks.com/almanac/properties/s/scroll-margin/

​[10]​​ scroll-padding: https://css-tricks.com/almanac/properties/s/scroll-padding/

​[11]​​ Fixed Headers and Jump Links? The Solution is scroll-margin-top: https://css-tricks.com/fixed-headers-and-jump-links-the-solution-is-scroll-margin-top/

​[12]​​ caniuse: https://caniuse.com/


如果你、喜欢探讨技术,或者对本文有任何的意见或建议,你可以扫描下方二维码,关注微信公众号“ 鱼头的Web海洋 ”,随时与鱼头互动。欢迎!衷心希望可以遇见你。

忍法,scroll 翻滚之术!_css_27