iT邦幫忙

2024 iThome 鐵人賽

DAY 16
0
自我挑戰組

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

[Day 16] useParallax - source code

  • 分享至 

  • xImage
  •  

今天先看原始碼的部分,Demo 那個有趣的特效,明天來實做看看~

官方 Demo:https://vueuse.org/core/useParallax/#useparallax

useParallax 參數

// src/compositions/useParallax.js

export function useParallax(target, options = {}) {
  const {
    deviceOrientationTiltAdjust = i => i,
    deviceOrientationRollAdjust = i => i,
    mouseTiltAdjust = i => i,
    mouseRollAdjust = i => i,
    window = defaultWindow,
  } = options
  
  // ...略
}

roll、tilt 稍後會看到,現在先不理他。

  • target:要觸發視差效果的 element
  • deviceOrientationTiltAdjust = i => i:裝置的 tilt 值幅度,EX:
    deviceOrientationTiltAdjust: (i: number) => 20 * i 是 20 倍。
  • deviceOrientationRollAdjust = i => i,:裝置的 roll 值幅度。
  • mouseTiltAdjust = i => i,:滑鼠事件的 tilt 值幅度。
  • mouseRollAdjust = i => i,:滑鼠事件的 roll 值幅度。

useParallax 回傳值

// src/compositions/useParallax.js

export function useParallax(target, options = {}) {
    // ...略
    
    return { roll, tilt, source }
}
  • roll
    • deviceOrientation:範圍 -2 ~ 2,平放在桌上為 0,豎直向上為負,豎直向下為正。
    • mouse:範圍 -0.5 ~ 5,原點為 0(element 中心),往上移動為為正,往下移動為負。
  • tilt
    • deviceOrientation:範圍 -1 ~ 1,平放在桌上為 0,往左翻轉為負,往右翻轉為正。
    • mouse:-0.5 ~ 0.5 以滑鼠移動來說,原點為 0(element 中心),往右移動為為正,往左移動為負。
  • source:觸發的事件類型。deviceOrientation 或是 mouse。

mouse 跟 deviceOrientation 觸發的 code 可以完全分開看,先從 mouse 開始~

useParallax mouse 觸發

// src/compositions/useParallax.js

import { computed, reactive } from 'vue'
import { useDeviceOrientation } from '@/compositions/useDeviceOrientation'
import { useMouseInElement } from '@/compositions/useMouseInElement'
import { useScreenOrientation } from '@/compositions/useScreenOrientation'
import { defaultWindow } from '@/helper'

export function useParallax(target, options = {}) {
  const {
    deviceOrientationTiltAdjust = i => i,
    deviceOrientationRollAdjust = i => i,
    mouseTiltAdjust = i => i,
    mouseRollAdjust = i => i,
    window = defaultWindow,
  } = options

  const orientation = reactive(useDeviceOrientation({ window }))
  const screenOrientation = reactive(useScreenOrientation({ window }))
  const {
    elementX: x,
    elementY: y,
    elementWidth: width,
    elementHeight: height,
  } = useMouseInElement(target, { handleOutside: false, window })

  const source = computed(() => {
    if (orientation.isSupported
      && ((orientation.alpha != null && orientation.alpha !== 0) || (orientation.gamma != null && orientation.gamma !== 0))
    ) {
      return 'deviceOrientation'
    }
    return 'mouse'
  })

  const roll = computed(() => {
    if (source.value === 'deviceOrientation') {
      // deviceOrientation source
    }
    else {
      // (y.value - height.value / 2) 這段是為了讓滑鼠在畫面中央時,roll 值為 0
      // 除以 height.value 是為了換算成百分比,也因為前面有除 2,所以這邊會是 -0.5 ~ 0.5
      // 負號是因為滑鼠往下滑時,y 值會變大,但 roll 值要變小
      const value = -(y.value - height.value / 2) / height.value
      return mouseRollAdjust(value)
    }
  })

  const tilt = computed(() => {
    if (source.value === 'deviceOrientation') {
      // deviceOrientation source
    }
    else {
      const value = (x.value - width.value / 2) / width.value
      return mouseTiltAdjust(value)
    }
  })

  return { roll, tilt, source }
}

可以看到 source computed 回傳 deviceOrientation or mouse,不過我不確定為什麼要同時拿 orientation.alpha & orientation.gamma 來判斷就是了 XD,以瀏覽器來說,這兩個都是 null。

計算部分可以參考 roll computed 中的註解,tilt 的話可以套用 roll 的算法,只是從 y 軸的計算變成 x 軸的計算,概念一樣。

useParallax deviceOrientation 觸發

跟上面 mouse 觸發的程式碼,只有在 computed 有差異:

// src/compositions/useParallax.js

// ... 略
const roll = computed(() => {
if (source.value === 'deviceOrientation') {
  let value
  switch (screenOrientation.orientation) {
    case 'landscape-primary':
      value = orientation.gamma / 90
      break
    case 'landscape-secondary':
      value = -orientation.gamma / 90
      break
    case 'portrait-primary':
      value = -orientation.beta / 90
      break
    case 'portrait-secondary':
      value = orientation.beta / 90
      break
    default:
      value = -orientation.beta / 90
  }
  return deviceOrientationRollAdjust(value)
}

// ...略
})

const tilt = computed(() => {
if (source.value === 'deviceOrientation') {
  let value
  switch (screenOrientation.orientation) {
    case 'landscape-primary':
      value = orientation.beta / 90
      break
    case 'landscape-secondary':
      value = -orientation.beta / 90
      break
    case 'portrait-primary':
      value = orientation.gamma / 90
      break
    case 'portrait-secondary':
      value = -orientation.gamma / 90
      break
    default:
      value = orientation.gamma / 90
  }
  return deviceOrientationTiltAdjust(value)
}

// ...略
})

關於 landscape-primarylandscape-secondaryportrait-primaryportrait-secondary 裝置方向以及gammabeta,可以參考昨天的圖文解釋比較清楚~

一樣拿 roll 來看,可以看到根據裝置方向的不同,會分別拿 gamma、beta 來做計算。這邊在看的時候卡了一陣子,後來發現關鍵在於,不管設備如何換方向,內部坐標系都不會變。也就是說,當我們在 portrait-primary 方向以 x 軸為主軸,前後翻轉裝置,gamma 值會跟著變,這時候把方向往順時針 90 度轉成 landscape-primary,我們想要做跟剛剛一樣的前後翻轉時,主軸變成裝置內部坐標系的 y 軸,也因為是繞著 y 軸轉,所以這時候跟著改變的並不是剛剛的 gamma 值,而是 bata 值。

所以這邊的計算,關於方向或是正負號的判斷,可以讓我們裝置在四個不同的方向使用這個 API,都可以拿到一樣的結果。

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


useParallax 的 source code 就到這邊告一段落,這兩天看這些跟空間有關的東西,頭都痛了起來 XD
明天會想看 useParallax 官方 Demo 的那個特效是怎麼透過 useParallax API 實作出來的~


上一篇
[Day 15] useDeviceOrientation & useScreenOrientation
下一篇
[Day 17] useParallax - 官網文件 Demo 效果實作
系列文
30 天 vueuse 原始碼閱讀與實作30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言