motion 元件都會使用到 Motion value,主要用來訂閱 (subscribe) 動畫或運動狀態的值,一般來說 motion 元件自動會建立這些值,另外也提供一些 Hooks 可以更細微控制。
useMotionValue
useMotionValue
useMotionTemplate
useMotionValue
的使用很簡單 :
import { motion, useMotionValue } from "framer-motion"
export function MyComponent() {
const x = useMotionValue(0) // 給予初始值
// 應用在 style 上,這邊是 物件 key 跟值同名的省略 { x : x }
return <motion.div style={{ x }} />
}
透過手動建立 MotionValue
,可以結合其他的事 :
useTransform
Hooks 串聯多個 motion value。以第 4 點來看,我就會想到 useRef
,同樣更新也不會觸發 re-render,看一下 原始碼 確實是以 useRef
來實作 :
// 內部使用的 useConstant
export function useConstant<T>(init: Init<T>) {
const ref = useRef<T | null>(null)
if (ref.current === null) {
ref.current = init()
}
return ref.current
}
在開發環境下的嚴格模式 (strict mode) React 在初始化包含 useMemo
會執行兩次 render ,但 ref 不會,可以避免這樣的情形發生。
一般我們寫 inline style,style
是物件,避免每次 render 重新產生,要嘛提到 function Component 外用一般物件建立,要麼在 function 內部將物件給記住,但 useMemo
也會被渲染機制影響,所以選擇 ref ,另外也要當下的最新值,同步畫面。
motion value 可以傳給其他的元件,當值更新時,所有用到該值的元件都會一併更新。
set()
: 更新值,如上面所說的是使用 ref 為基底,所以並不會觸發 React re-renderget()
: 讀取值,motion value 可以是 字串或是數字
getVelocity()
: 回傳 數字類型 的速度值,如果不是數字,回傳 0 。isAnimating()
: 是不是正在播放動畫中stop()
: 暫停目前正在執行的動畫onChange((lastest)=> {})
: 訂閱目前動畫的數值。通常在 useEffect
使用destroy()
: 取消訂閱 (clean up)const x = useMotionValue(0)
x.set(10)
x.get()
// 訂閱目前的值
useEffect(() => {
x.onChange(latest => {})
}, [])
// 使用在一般 html 標籤的 style 上
<motion.div style={{ x }} />
// 使用在 SVG
<motion.circle cx={x} />
const opacity = useMotionValue(0);
useEffect(() => {
opacity.onChange((latest) => {
console.log(latest);
});
console.log(opacity);
}, []);
<motion.div
ref={boxRef}
style={{
width: 100,
height: 100,
border: "1px solid black",
opacity: opacity,
}}
animate={{
opacity: 1,
}}
>
印出 opacity
的內容物,可以看到除了最新值,也可以透過 getPrevious()
方法拿到物件裡面的 prev。
onChange
訂閱事件中可以看到每次動畫按照 16.6 ms 更新時的 opacity 的值useMotionTemplate
一樣也可以建立 motion value ,差別在使用樣板字面值 ( Template literals
) 表示,回傳值就是最新的運動狀態值,並且也是樣板字面值。只要 template 一變動,就會更新成最新的值。
useMotionTemplate``
useMotionValue
import { useMotionTemplate } from "framer-motion" // 引入
const x = useMotionValue(100)
// 等同於 transform.get() === transform(100px)
const transform = useMotionTemplate`transform(${x}px)`
// 直接插入,不用 `x : x` 這樣
<motion.div style={{ transform }} />