忍法,scroll 翻滾之術!

前言

在日常的開發中,我們對 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 接口。

語法如下:

  1. scroll/scrollTo(x, y)
  • x:元素要移動的位置橫座標。
  • y:元素要移動的位置縱座標。
  1. scroll/scrollTo(options)

options支持屬性有lefttop以及behavior

  • top:元素要移動的位置橫座標。
  • lef::元素要移動的位置縱座標。
  • 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 接口。

語法如下:

  1. scrollBy(x, y)
  • x:元素要移動的位置橫座標。
  • y:元素要移動的位置縱座標。
  1. scrollBy(options)

options支持屬性有lefttop以及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而已。非常的機智~

Element.scrollIntoView

Element.scrollIntoView() 方法可以讓當前的元素滾動到瀏覽器窗口的可視區域內。

語法如下:

  1. scrollIntoView(alignToTop)

alignToTop是一個布爾值,如果不填則默認爲true。相當於{block: 'start', inline: ‘nearest‘}

如果值爲true,則元素的頂端將和其所在滾動區的可視區域的頂端對齊。如果爲false,則是底端對齊。相當於{block: 'end', inline: 'nearest'}

  1. scrollIntoView(scrollIntoViewOptions)

scrollIntoViewOptions包含下列屬性的對象:

  • behavior:元素的運動模式,如果是auto,則沒有動畫效果,如果是smooth,則是平滑滾動。
  • block:定義垂直方向的對齊方式,值可以是 startcenterendnearest之一。默認爲 nearest
  • inline:定義水平方向的對齊方式,值可以是 startcenterendnearest之一。默認爲 nearest

來來來,我給大家解釋一下blockinline的可選值到底是怎麼回事:

  • start:跟當前元素它爹的頭髮(頂部)對齊。
  • center:跟當前元素它爹的肚子(中間)對齊。
  • end:跟當前元素它爹的 jio(底部)對齊。
  • nearest:就近原則,挨哪裏近去哪,如果在中間就不動。

如果是block,就當元素是站着的(從上往下),如果是inline,就當元素是躺着的(從左到右)。當然,前提是默認的writing-mode: horizontal-tb

有點繞?快來康康栗子呀:https://codepen.io/krischan77/pen/mdJMQpz

好了,上述就是殺馬特,哦不是,scroll家族的特性了。(廢棄或準備廢棄的就沒往上寫了。)

CSS scroll

分享完 JS 中的 scroll ,我們再來了解下 CSS 中的 scroll

scroll-behavior

我們上面在講這個 JS 中的 scroll 時,多次提到一個單詞叫“behavior”。

Mmmm,所以你們猜猜這個scroll-behavior跟 JS 裏的behavior有木有關係?

嗯,沒錯,你們猜對啦,是有關係的。

(吃瓜羣衆:“都沒人理你~”)

scroll-behavior跟上述各個scrollAPI 裏的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-paddingscroll-margin類型,只不過跟paddingmargin的一樣,有內外邊距的區別。

來個 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

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;

默認情況

課外姿勢

新舊邏輯屬性

不知道各位有沒有注意上述各個屬性的值,除了有常規的xytoprightbottomleft之外,還有四個比較少見的值blockinlinestartend

所以這到底是什麼呢?

其實是因爲 W3C 爲了照顧到非西文排序國家的書寫習慣,特意修改了 CSS 的邏輯屬性。

對於像我們國家或者是美國這樣,文檔排列是從上到下,從左到右的,toprightbottomleft就很好理解。

但是像日本或者阿拉伯等書寫排列跟我們不一樣的國家,在邏輯上就會有不合理的地方,例如:

  • 在阿拉伯,他們的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的特性寫出來的效果,之前跟朋友們一起出去玩,我們進行了許多的活動。其中有一項遊戲就是“你比劃我猜”,作爲策劃者的魚頭,自然不能放過這次機會,遂用技術小秀了一把。

我們先來看看效果。

源碼地址在這:

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;

大家也不妨嘗試下用所掌握的姿勢增添點生活情趣呀~

後記

吃瓜羣衆:我看完了整篇,沒看到哪裏有跟忍術相關的內容啊?騙我流量,賠錢。

魚頭:沒有又咋啦?說好的寵我,你現在兇我是什麼意思?

參考資料

  1. scrollIntoView block vs inline

  2. CSSOM View Module

  3. Element.scrollIntoView()

  4. CSS Scroll Snap Module Level 1

  5. CSS TRICK scroll-snap-type

  6. MDN scroll-snap-type

  7. CSS TRICK scroll-snap-align

  8. scroll-snap-stop

  9. scroll-margin

  10. scroll-padding

  11. Fixed Headers and Jump Links? The Solution is scroll-margin-top

如果你喜歡探討技術,或者對本文有任何的意見或建議,非常歡迎加魚頭微信好友一起探討,當然,魚頭也非常希望能跟你一起聊生活,聊愛好,談天說地。
魚頭的微信號是:krisChans95
也可以掃碼添加好友,備註“csdn”就行
https://fish-pond-1253945200.cos.ap-guangzhou.myqcloud.com/img/base/wx-qrcode1.jpg

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章