diff --git a/README.ja.md b/README.ja.md index 440dd1e..e83642c 100644 --- a/README.ja.md +++ b/README.ja.md @@ -52,7 +52,12 @@ requestAnimationFrame(loop); createWindowTracker(options?: WindowTrackerOptions): WindowTrackerObj ``` -トラッカーのインスタンスを生成します。引数は省略可能で、各プロパティも省略可能です。戻り値は `update` メソッドを1つ持つオブジェクト(以下の `WindowTrackerObj` )です。 +トラッカーのインスタンスを生成します。引数は省略可能で、各プロパティも省略可能です。戻り値は以下のプロパティを持つ `WindowTrackerObj` です。 + +| プロパティ | 型 | 詳細 | +| ------------------ | ---------- | -------------------------------------------------------------------------------------------------------------------------------------- | +| `supportsWinertia` | `boolean` | winertia が有効なデバイスかどうか。SSRでない・`screenX`/`screenY`が取得可能・タッチデバイスでない、の3条件をすべて満たす場合に`true`。 | +| `update` | `Function` | トラッカーを1フレーム進めます。詳細は以下を参照。 | | オプション | 型 | デフォルト | 詳細 | | -------------------- | -------- | ---------- | ----------------------------------------------------- | @@ -74,7 +79,7 @@ update(now: number, shakeAccelThreshold: number): MotionStateObj | null | `now` | `number` | 現在のタイムスタンプ。通常は`performance.now()`。前回呼び出しからの経過時間の計算に使う。 | | `shakeAccelThreshold` | `number` | シェイクと判定する加速度の大きさ (px/s²)。 | -前回の呼び出しからの経過時間が0以下の場合(同じタイムスタンプで`update`が2回呼ばれた場合など)は`null`を返します。それ以外は`MotionStateObj`を返します。 +`supportsWinertia` が `false` の場合、または前回の呼び出しからの経過時間が0以下の場合(同じタイムスタンプで`update`が2回呼ばれた場合など)は`null`を返します。それ以外は`MotionStateObj`を返します。 | フィールド | 型 | 説明 | | --------------- | ---------------------------------- | -------------------------------------------------------------------- | diff --git a/README.md b/README.md index 925e441..b2cdf41 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,12 @@ requestAnimationFrame(loop); createWindowTracker(options?: WindowTrackerOptions): WindowTrackerObj ``` -Creates a tracker instance. The `options` argument is optional, and so is each of its properties. Returns an object exposing a single `update` method (`WindowTrackerObj`, see below). +Creates a tracker instance. The `options` argument is optional, and so is each of its properties. Returns a `WindowTrackerObj` with the following properties: + +| Property | Type | Description | +| ------------------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `supportsWinertia` | `boolean` | Whether the current environment supports winertia — requires a non-SSR context, `screenX`/`screenY` availability, and a non-touch (coarse pointer) device. | +| `update` | `Function` | Advances the tracker by one frame. See below. | | Option | Type | Default | Description | | -------------------- | -------- | ------- | ----------------------------------------------------------------------------------------- | @@ -74,7 +79,7 @@ Advances the tracker by one frame and returns the current state. Call this every | `now` | `number` | Current timestamp, typically `performance.now()`. Used to compute elapsed time since the previous call. | | `shakeAccelThreshold` | `number` | Acceleration magnitude (px/s²) above which a shake is detected. | -Returns `null` if the elapsed time since the previous call is zero or negative (e.g. `update` called twice with the same timestamp). Otherwise returns a `MotionStateObj`: +Returns `null` if `supportsWinertia` is `false`, or if the elapsed time since the previous call is zero or negative (e.g. `update` called twice with the same timestamp). Otherwise returns a `MotionStateObj`: | Field | Type | Description | | --------------- | ---------------------------------- | ------------------------------------------------------------------------------ | diff --git a/src/types.ts b/src/types.ts index 6208657..12b2f16 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,6 +11,8 @@ export type WindowTrackerOptions = { }; export type WindowTrackerObj = { + /** winertia が有効なデバイスかどうか */ + supportsWinertia: boolean; /** トラッカーを1フレーム進めて新しい状態を返す。dtが0以下の場合はnull */ update: (now: number, shakeAccelThreshold: number) => MotionStateObj | null; }; diff --git a/src/windowTracker.test.ts b/src/windowTracker.test.ts index 9da6e50..9953854 100644 --- a/src/windowTracker.test.ts +++ b/src/windowTracker.test.ts @@ -11,8 +11,15 @@ const setScreenCoord = (x: number, y: number) => { beforeEach(() => { currentNow = 0; + vi.spyOn(performance, "now").mockImplementation(() => currentNow); + setScreenCoord(0, 0); + + Object.defineProperty(window, "matchMedia", { + value: vi.fn().mockReturnValue({ matches: false }), + configurable: true, + }); }); afterEach(() => { @@ -112,24 +119,17 @@ describe("createWindowTracker", () => { expect(third?.shakeCount).toBe(2); }); - it("historyはhistoryLength件を超えない、かつ返り値は以後のupdateで変化しないコピーである", () => { - const tracker = createWindowTracker({ historyLength: 3 }); - - let lastState = tracker.update(0, 999999); // 経過時間0なのでnull想定 - for (let i = 1; i <= 5; i++) { - currentNow = i * 100; - setScreenCoord(i * 10, 0); - lastState = tracker.update(currentNow, 999999); - } - - expect(lastState?.history).toHaveLength(3); - const snapshot = [...(lastState?.history ?? [])]; + it("supportsWinertia が false の場合、update は常に null を返す", () => { + Object.defineProperty(window, "matchMedia", { + value: vi.fn().mockReturnValue({ matches: true }), // タッチデバイス扱い + configurable: true, + }); + const tracker = createWindowTracker(); - // さらに update しても、以前取得した history は変化しない - currentNow = 600; - setScreenCoord(60, 0); - tracker.update(currentNow, 999999); + expect(tracker.supportsWinertia).toBe(false); - expect(lastState?.history).toEqual(snapshot); + currentNow = 1000; + setScreenCoord(100, 0); + expect(tracker.update(currentNow, 999999)).toBeNull(); }); }); diff --git a/src/windowTracker.ts b/src/windowTracker.ts index f6b556a..9f4e74f 100644 --- a/src/windowTracker.ts +++ b/src/windowTracker.ts @@ -27,7 +27,15 @@ export const createWindowTracker = ({ const history: AccelerationObj[] = []; + const supportsWinertia: boolean = + typeof window !== "undefined" && + "screenX" in window && + "screenY" in window && + !window.matchMedia("(pointer: coarse)").matches; + const update = (now: number, shakeAccelThreshold: number): MotionStateObj | null => { + if (!supportsWinertia) return null; + // 前フレームからの経過時間(秒)。0以下なら同一/逆行タイムスタンプなのでスキップ const dt = (now - lastTime) / 1000; if (dt <= 0) return null; @@ -106,5 +114,5 @@ export const createWindowTracker = ({ return result; }; - return { update }; + return { supportsWinertia, update }; };