Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -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フレーム進めます。詳細は以下を参照。 |

| オプション | 型 | デフォルト | 詳細 |
| -------------------- | -------- | ---------- | ----------------------------------------------------- |
Expand All @@ -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`を返します。

| フィールド | 型 | 説明 |
| --------------- | ---------------------------------- | -------------------------------------------------------------------- |
Expand Down
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
| -------------------- | -------- | ------- | ----------------------------------------------------------------------------------------- |
Expand All @@ -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 |
| --------------- | ---------------------------------- | ------------------------------------------------------------------------------ |
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export type WindowTrackerOptions = {
};

export type WindowTrackerObj = {
/** winertia が有効なデバイスかどうか */
supportsWinertia: boolean;
/** トラッカーを1フレーム進めて新しい状態を返す。dtが0以下の場合はnull */
update: (now: number, shakeAccelThreshold: number) => MotionStateObj | null;
};
Expand Down
34 changes: 17 additions & 17 deletions src/windowTracker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down Expand Up @@ -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();
});
});
10 changes: 9 additions & 1 deletion src/windowTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -106,5 +114,5 @@ export const createWindowTracker = ({
return result;
};

return { update };
return { supportsWinertia, update };
};