iT邦幫忙

2024 iThome 鐵人賽

DAY 20
0
自我挑戰組

30 天 vueuse 原始碼閱讀與實作系列 第 20

[Day 20] useScroll - X position, Y position

  • 分享至 

  • xImage
  •  

官方 Demo:https://vueuse.org/core/useScroll/#usescroll

這個 API 原始碼有點長,會先盡量依照功能拆分出來看,不然一次看一整坨可能有點吃不消 XD

X position, Y position

x、y position:水平、垂直捲軸捲動的距離。

參考官方 Demo,跟 xy 有關的功能主要有兩個:

  1. xy 被外層更新的時候,指定 element 內部要跟著滾動到該位置。
  2. 在指定 element 上滾動的時候,要能及時更新 xy 數值。

先來看第一個功能,外層更新可以參考官方 Demo,X、Y Position 有 input 框可以做輸入,這邊就略過 Demo code,直接看 useScroll:

// src/compositions/useScroll.js
import { computed, ref } from 'vue'
import { toValue, unrefElement } from '@/helper'
import { useEventListener } from '@/compositions/useEventListener'

export function useScroll(element, options = {}) {
  const {
    behavior = 'auto',
    eventListenerOptions = {
      capture: false,
      passive: true,
    },
  } = options

  const internalX = ref(0)
  const internalY = ref(0)

  const x = computed({
    get() {
      return internalX.value
    },
    set(x) {
      scrollTo(x, undefined)
    },
  })

  const y = computed({
    get() {
      return internalY.value
    },
    set(y) {
      scrollTo(undefined, y)
    },
  })

  function scrollTo(_x, _y) {
    if (!window)
      return

    const _element = toValue(element)
    if (!_element)
      return

    (_element instanceof Document ? window.document.body : _element)?.scrollTo({
      top: toValue(_y) ?? y.value,
      left: toValue(_x) ?? x.value,
      behavior: toValue(behavior),
    })
    const scrollContainer
      = (_element)?.document?.documentElement // window
      || (_element)?.documentElement // document
      || (_element)
    if (x != null)
      internalX.value = scrollContainer.scrollLeft
    if (y != null)
      internalY.value = scrollContainer.scrollTop
  }

  return {
    x,
    y,
  }
}

先看資料流,useScroll return xy 出去,這個 xy 是帶有 getter & setter 的 computed,也就是說當上層修改 xy 值的時候,xy 的 setter 會被觸發,接著就會執行 scrollTo function。

scrollTo function 執行的時候,會呼叫 window 的 scrollTo 方法,把捲軸位置滾動到我們指定的數值。滾動到指定位置後,接下來做的事情是,讓 xy 數值跟滾動後的捲軸位置做同步。
這邊額外提一個小東西,x != null 是用寬鬆方式比較,等同於 x !== null && x !== undefined

到這邊第一個功能就看完了,接下來看第二個,在指定 element 上滾動的時候,要能及時更新 xy 數值:

// src/compositions/useScroll.js
// ... 略
export function useScroll(element, options = {}) {
  // ... 略
  import { useEventListener } from '@/compositions/useEventListener'

  function scrollTo(_x, _y) {
    // ... 略
  }

  const setArrivedState = (target) => {
    if (!window)
      return

    const el = (
      (target)?.document?.documentElement
      || (target)?.documentElement
      || unrefElement(target)
    )

    const scrollLeft = el.scrollLeft

    internalX.value = scrollLeft

    let scrollTop = el.scrollTop

    // patch for mobile compatible
    if (target === window.document && !scrollTop)
      scrollTop = window.document.body.scrollTop

    internalY.value = scrollTop
  }

  const onScrollHandler = (e) => {
    if (!window)
      return

    const eventTarget = (
      (e.target).documentElement ?? e.target
    )

    setArrivedState(eventTarget)
  }

  useEventListener(
    element,
    'scroll',
    onScrollHandler,
    eventListenerOptions,
  )

  return {
    x,
    y,
  }
}

可以看到在 scroll 事件觸發時,會執行 onScrollHandler 這個 function,接著執行 setArrivedState function,這兩個 function 之後都還會做其他事情,這邊先聚焦在目前這個功能上。

主要邏輯在 setArrivedState,目前做的事滿單純的,就是把 target 的 scrollLeft 數值設定給 x、scrollTop 數值設定給 y

比較值得注意的是這段:

// patch for mobile compatible
if (target === window.document && !scrollTop)
  scrollTop = window.document.body.scrollTop

這段上網查了一下,mobile 裝置似乎無法使用 document.documentElement.scrollTop 取值,要改用 window.document.body.scrollTop。

GitHub PR:https://github.com/RhinoLee/30days_vue/pull/19/files


今天講了 useScroll X position, Y position 的部分,明天會從 useScroll 的 arrivedState 開始看,順利的話也許明天可以看完。不過 useScroll 比較複雜一點,如果出現 part3 的話不是我在擠牙膏,是真的有點吃力 XD


上一篇
[Day 19] useDebounceFn - unit test
下一篇
[Day 21] useScroll - arrivedState
系列文
30 天 vueuse 原始碼閱讀與實作30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言