滾動錨定(Scroll Anchoring)- 讓視口內容再也不因視口上方 DOM 元素的高度變化而產生跳動

不知道你有沒有經歷過這樣的場景:當你打開一張「多圖殺貓」的頁面後,正一張圖一張圖邊滾邊看,在你剛準備定睛看某一張圖的時候,這張圖忽然被它上面的內容擠到了視口下方,而後你趕忙把滾動條往下拉,試圖追趕這張沒看完的圖,當你剛剛追上的時候,這張圖又一次被擠到了你看不見的地方。html

發生這種狀況的緣由是由於在不少場景下(好比論壇裏),你無法事先知道一張圖的高度,因此你無法事先給這張圖佔位,在網速不理想的狀況下,可能就會發生我上面描述的這種因頁面靠上的圖片比靠下的圖片晚加載出來而致使用戶當前瀏覽的內容被頻繁擠出視口的狀況。前端

我經過在定時器回調裏向頁面上方插入圖片來模擬一下剛纔描述的這種狀況:node

<style>
  img {
    display: block;
    margin: 0 auto;
  }
</style>
<img src="https://aecpm.alicdn.com/tfscom/TB1.52aPFXXXXa0XXXXXXXXXXXX.jpg">
<img src="https://aecpm.alicdn.com/tfscom/TB1_utRPVXXXXapXVXXXXXXXXXX.png">
<img src="https://static.dingtalk.com/media/lAHOuOFd_czSzQEn_295_210.gif">
<img src="https://aecpm.alicdn.com/tfscom/TB1f1xwQpXXXXXBXVXXXXXXXXXX.jpg">
<img src="https://gtms03.alicdn.com/tps/i3/TB1eSxvJVXXXXaKXFXXYoAvIXXX-220-50.png">
<img src="https://gw.alicdn.com/bao/uploaded/TB1EGvvPVXXXXX3aXXXXXXXXXXX-200-200.jpg">
<img src="https://gw.alicdn.com/tfscom/TB1CLTHNFXXXXaDXpXXXXXXXXXX">
<script>
  const urls = `
https://asearch.alicdn.com/bao/uploaded/i1/1381306006414474986/TB2_gZAlNtmpuFjSZFqXXbHFpXa_!!0-saturn_solar.jpg
https://asearch.alicdn.com/bao/uploaded/i1/153360285303496277/TB2SO.Wa4vzQeBjSZFEXXbYEpXa_!!0-saturn_solar.jpg
https://asearch.alicdn.com/bao/uploaded/i1/188050339412916381/TB2geTXaypnpuFjSZFkXXc4ZpXa_!!0-saturn_solar.jpg
https://asearch.alicdn.com/bao/uploaded/i2/181720289489216985/TB2UFz6amjz11Bjy0FnXXcnxXXa_!!0-saturn_solar.jpg
https://asearch.alicdn.com/bao/uploaded/i3/108480250457898935/TB28r5osFXXXXbrXXXXXXXXXXXX_!!0-saturn_solar.jpg
https://asearch.alicdn.com/bao/uploaded/i3/111180208599309441/TB2kAsQnVXXXXXcXFXXXXXXXXXX_!!0-saturn_solar.jpg
https://asearch.alicdn.com/bao/uploaded/i3/171530328819399773/TB2rgtke9iK.eBjSZFsXXbxZpXa_!!0-saturn_solar.jpg
https://asearch.alicdn.com/bao/uploaded/i3/1880505035634435666/TB2bToNiHXlpuFjSszfXXcSGXXa_!!0-saturn_solar.jpg
https://asearch.alicdn.com/bao/uploaded/i4/1519305020726924733/TB2I2VuhNhmpuFjSZFyXXcLdFXa_!!0-saturn_solar.jpg
`.split("\n")
  let i = 0
  setInterval(() => {
    if (i === urls.length) i = 0
    let img = new Image()
    img.src = urls[i++]
    document.body.prepend(img)
  }, 2000)
  onscroll = function(argument) {
    console.log("scrollY:" + scrollY)
  }
</script>

上面這個 demo 裏,假設我一直「追趕」的那張圖是「金凱瑞搖頭三人組」那張 GIF,那麼在 Chrome 56 以前的版本以及在其它的瀏覽器中,你看到的會是下面這樣的場景:git

 

爲了得到更好的用戶體驗,Chrome 從 56 開始,開啓了一個叫作「滾動錨定(Scroll Anchoring)」的優化,效果就是,當頁面在視口上方的部分忽然變高了 x 像素,那麼瀏覽器會爲你自動向下滾動 x 像素,從而保證視口內容徹底不變:github

 

瀏覽器自動爲你向下滾動 x 像素,就意味着瀏覽器本身會觸發一次 scroll 事件,也意味着 scrollY 的值會增長 x,你能夠經過上面的 demo 驗證這一點。chrome

有些同窗可能會有疑問,「這種場景多嗎?」、「我怎麼歷來沒注意到?」、「有必要把事情搞複雜嗎?」。 從 Chrome 官方的統計能夠看到,這個特性被觸發(替你滾動頁面)的機率大概爲 1%,並很少,但也算不上是極端狀況,因此優化仍是有必要的。可能由於近些年網絡條件愈來愈好,圖片加載的速度比你滾動頁面的速度還要快,因此不太容易遇到因網速慢致使的這類場景了(尤爲在 WIFI 網絡下)。瀏覽器

不過這個優化的確不是個簡單的改動,Chrome 從去年 3 月份開始實現這個特性,直到一年多後的今天,仍然有一些因這個優化致使的 bug 存在,這些 bug 多表現爲頁面異常滾動,甚至像永動機同樣無限抖動,從這方面看,事情的確有一些被搞的複雜了。但幸虧有一個 CSS 屬性能夠關掉這個優化:overflow-anchor: none,你能夠把這個屬性添加到發生 bug 的容器元素上,甚至加到 body 元素上也行,而後該元素及其它的全部後代節點就都不會被應用「滾動錨定」的優化了。除了做爲瀏覽器 bug 的臨時 fix,我想不到其它使用這個屬性的場景了。網絡

這個優化不只限於看圖片的時候,任何元素節點,甚至文本節點也一樣適用。好比你在某新聞網站瀏覽一段文字的時候,視口上方忽然異步插入了一個未事先佔位的 iframe 廣告(微博輸入框下方就有這麼一個廣告),若是你使用了 Chrome 56 及以上版本的話,你徹底察覺不到這一變化,你的閱讀不會被打斷。app

頁面在視口上方的高度增長 x 像素,瀏覽器會爲你向下滾動 x 像素;反過來,頁面在視口上方的高度減小 x 像素,瀏覽器也會爲你向上滾動 x 像素,但這種狀況更少見了。異步

該優化一樣適用於元素級別的滾動條,我也寫了一個 demo:

<style>
  div {
    width: 300px;
    height: 300px;
  }

  #container {
    background: red;
    overflow: scroll;
  }

  #aboveViewport {
    background: blue;
  }

  #anchorNode {
    background: green;
  }
</style>
<div id="container">
  向下滾動到底
  <div id="aboveAnchorNode"></div>
  <div id="anchorNode"></div>
  這段文字一旦出現就會始終在視口內
</div>
<script>
  let height = 100
  setInterval(() => {
    aboveAnchorNode.style.height = height += 10
  }, 1000)
</script>

因爲本文講的是一個瀏覽器的優化,即使是前端開發者也沒有深究的必要,因此我故意省略了一些內容,好比什麼是錨定節點(anchor node )以及瀏覽器如何選定一個錨點節點?以及哪些樣式改動會把錨定節點擠出視口但不會觸發優化(Suppression Triggers),若是你想深究,能夠從規範裏找到答案。

相關文章
相關標籤/搜索