@soichiro_nitta/motion
v5.1.3
Published
https://www.npmjs.com/package/@soichiro_nitta/motion
Readme
@soichiro_nitta/motion
https://www.npmjs.com/package/@soichiro_nitta/motion
軽量な DOM トランジションユーティリティ。要素の transform/CSS を手早くアニメーションさせるためのミニマル API を提供します。
インストール
pnpm add @soichiro_nitta/motionクイックスタート(Next.js 推奨構成)
- ID を共通モジュール(例:
app/id.ts)で定義
import { createId } from '@soichiro_nitta/motion'
export const ID = createId(['BOX', 'TITLE'])Next.js App Router ではこの ID モジュールを RSC/Client どちらからでも import できるため、ID を一箇所で管理できます。
- クライアント専用モジュールを用意(例:
app/motion.ts)
'use client'
import { createMotion } from '@soichiro_nitta/motion'
import { ID } from './id'
// `createMotion(ID)` により、第一引数の候補は `keyof typeof ID` になる
export const { motion } = createMotion(ID)- コンポーネントで利用
'use client'
import { useEffectAsync } from '@soichiro_nitta/motion'
import { ID } from '@/app/id'
import { motion } from '@/app/motion'
const Page = () => {
useEffectAsync(async () => {
await motion.delay(0.1)
await motion.to('BOX', 0.5, 'out', { opacity: '1' })
await motion.to('TITLE', 0.5, 'out', { opacity: '1', translateY: '0px' })
}, [])
return (
<div>
<div id={ID.BOX.N} style={{ opacity: 0 }}>
Hello
</div>
<h1 id={ID.TITLE.N}>Title</h1>
</div>
)
}
export default PageuseEffectAsyncはasync/awaitをそのまま書けるようにするラッパーで、内部でvoidを付けたり Promise を捨てたりする必要がありません。motion.runは即時実行のためのヘルパーで、(async () => { ... })()の代わりにmotion.run(async () => { ... })と書けます(返り値はそのまま Promise)。- Next.js 16 でのフル実装例(RSC + Client 分離)は検証リポジトリ motion-rsc-test を参照してください。
ID の使い方
createId や createMotion が返す ID はキーごとに N(id 文字列)と E()(DOM 参照)を持ち、motion.* で利用する第1引数のキーとも一致します。
ID.BOX.N: RSC でも安全なid文字列。<div id={ID.BOX.N} />のように使えます。ID.BOX.E(): ブラウザでHTMLElementを返す関数。'use client'なモジュール内でのみ利用できます。ID.BOX.E()は初回アクセス時にdocument.getElementByIdで取得した要素をキャッシュし、2 回目以降は同じ参照を返すため、繰り返し操作でも DOM 探索コストを抑えられます。motion.to('BOX', …)の'BOX'は上記キーのエイリアスで、DOM 探索をライブラリが肩代わりします。ID.BOX.E()で取得した要素を直接渡すことも可能です。
const elementBox = ID.BOX.E()
await motion.to(elementBox, 0.2, 'out', { opacity: '1' })
elementBox.style.borderRadius = '12px'素の DOM での利用
import { createMotion } from '@soichiro_nitta/motion'
const { ID, motion } = createMotion(['BOX'])
await motion.to('BOX', 0.3, 'inout', { translateX: '20px', opacity: '0.8' })API
createId(names: string[])- サーバー(RSC)でも安全に利用できる ID 辞書を返します。
- 返り値:
{ ID }(ID.NAME.Nのみ保持)
createMotion(names: string[])- 指定した
id名、またはcreateIdが返す ID 辞書を受け取り、DOM アクセス付きIDと操作関数群motionを返します。 - 返り値:
{ ID, motion }
- 指定した
ID[name]nameに紐づく要素参照のヘルパ。ID.BOX.Nはid文字列、ID.BOX.E()はHTMLElementを返します。createMotion(ID)を利用するとmotion.*の第一引数がkeyof typeof ID(例:'BOX' | 'TITLE') で補完されます。
motion.set(target, values)- 直ちにスタイルを適用します(トランジションなし)。
transitionDuration未指定時は0sを自動セットします。
- 直ちにスタイルを適用します(トランジションなし)。
motion.run(task: () => Promise<unknown>)- 即時実行用のユーティリティ。
(async () => { ... })()の代替として使えます。 await motion.run(async () => { await motion.to(...); })のようにawaitすれば、内部の最後のmotion.to完了まで待ってから次の行へ進みます。
- 即時実行用のユーティリティ。
即時関数パターンの置き換え例
useEffectAsync(async () => {
const st = refShapeTop.current
const tt = refTextTop.current
const sb = refShapeBottom.current
const tb = refTextBottom.current
const u = refUnderline.current
if (st && tt && sb && tb && u) {
motion.run(async () => {
await motion.to(st, 1, 'out', { scaleX: '1' })
motion.set(tt, { opacity: '1' })
motion.set(st, { transformOrigin: 'right center' })
await motion.to(st, 1, 'out', { scaleX: '0' })
})
await motion.delay(0.2)
motion.run(async () => {
await motion.to(sb, 1, 'out', { scaleX: '1' })
motion.set(tb, { opacity: '1' })
})
}
}, [])motion.to(target, duration, easing, values)- 指定秒数で目的のスタイルにトランジションします。
targetはHTMLElementかcreateMotion(ID)由来のキー列挙(例:'BOX')を受け取ります。easing:in | out | inout | bounce | linear- 変形は個別キーで指定します(例:
translateX,rotate,scale)。複合transformは渡さないでください。 - 任意:
options?: { signal?: AbortSignal }を渡すと、signal.abort()でawait motion.to(...)の待機を短絡できます(スタイル適用自体は行われます)。
motion.repeat(target, duration, values)- 同一トランジションを繰り返します。
{ pause, play, stop, destroy }を返します。 pause(): 一時停止(内部のタイマー/RAF をキャンセル)play(): 再開(短い間隔でpause()/play()をトグルしても多重ループ化しません)stop()/destroy(): 完全停止+必要な場合は元の style に復帰(destroyはstopのエイリアス)prefers-reduced-motionは利用側でガードしてください(例:if (matchMedia('(prefers-reduced-motion: reduce)').matches) return)
- 同一トランジションを繰り返します。
motion.get(target, property)- 計算後スタイルを取得します。
motion.delay(seconds)seconds秒待機するPromise<void>。
useEffectAsync(effect, deps)async/awaitをそのまま書けるuseEffectの薄いラッパー。effectがPromiseを返した場合も自動でハンドリングし、戻り値のクリーンアップがあれば通常のuseEffect同様に実行されます。
注意(Client-only)
createIdは RSC から参照可能ですが、createMotion/motion.*/ID.*.E()はブラウザ専用です。- ブラウザ環境でない場合にクライアント API を呼び出すと例外を投げます。クライアント用モジュールには
'use client'を付与してください。
