見出し画像

cycle関数を使った循環するUIの作り方

まえがき


こんにちわ。nap5です。

cycle関数を使った循環するUIの作り方の紹介です。


function cycle(start: number, end: number, value: number) {
  if (value < start) return end;
  if (value > end) return start;
  return value;
}


利用シーン


この`cycle`関数は、与えられた値が特定の範囲内に収まるように調整するためのものです。ナビメニューのフォーカス管理に使う例が考えられますが、他にもさまざまなユースケースが考えられます。

  1. 画像スライダーまたはカルーセル:ユーザーが最後の画像から次に進むとき、または最初の画像に戻るときに、スライダーが循環するようにする。

  2. リストや配列の項目選択:選択可能な項目のリストがある場合、ユーザーがリストの終端に達した時に最初に戻るようにしたり、逆に最初の項目から前に進むときにリストの最後にジャンプするようにする。

  3. ゲーム開発:キャラクターやオブジェクトが画面の端に到達したときに、画面の反対側から再登場させる(例:古典的な「パックマン」ゲームのように)。

  4. 日付や時間の処理:特定の範囲内で日付や時間を循環させる。例えば、時間を24時間形式で扱う場合に、`23`時から`1`時間進むと`0`時に戻るようにする。

  5. UIコンポーネントの状態管理:タブ、ステップ、または進行状況インジケーターのようなUIコンポーネントの状態を管理する際に、最後の状態からさらに進もうとすると最初の状態に戻るようにする。

その基本的な概念は、ある範囲の限界に達したときに値を反対側の限界に「循環」させることです。これにより、ユーザーインターフェイスやゲーム、その他のアプリケーションの設計において、より滑らかで直感的な体験を提供できます。


デモコード


ボタン駆動でアウトラインが移動する例になります


demo code.

https://codesandbox.io/s/jd8ss2


import { FC, PropsWithChildren, useState } from "react";

const Box: FC<PropsWithChildren<{ isActive?: boolean }>> = ({
  children,
  isActive,
}) => (
  <div
    style={{
      padding: "2.5rem",
      border: `2px solid #cccccc`,
      ...(isActive && {
        outline: `2px solid #093eb0`,
      }),
    }}
  >
    {children}
  </div>
);

const data = ["A", "B", "C"];

function cycle(start: number, end: number, value: number) {
  if (value < start) return end;
  if (value > end) return start;
  return value;
}

const INITIAL_INDEX = -1;

export const Cowboy = () => {
  const [activeIndex, setActiveIndex] = useState<number>(INITIAL_INDEX);

  const handleReset = () => {
    setActiveIndex(INITIAL_INDEX);
  };

  const handlePrev = () => {
    setActiveIndex((prevIndex) => cycle(0, data.length - 1, prevIndex - 1));
  };

  const handleNext = () => {
    setActiveIndex((prevIndex) => cycle(0, data.length - 1, prevIndex + 1));
  };

  return (
    <div>
      <div
        style={{
          display: "grid",
          gridTemplateColumns: `repeat(${data.length}, 1fr)`,
          gap: "16px",
          marginBottom: "16px",
        }}
      >
        {data.map((d, index) => (
          <Box key={d} isActive={activeIndex === index}>
            {d}
          </Box>
        ))}
      </div>
      <button onClick={handlePrev}>Prev</button>
      <button onClick={handleReset}>Reset</button>
      <button onClick={handleNext}>Next</button>
    </div>
  );
};


呼び出し側で以下のように使用します

import "./App.css";
import { Cowboy } from "./Cowboy";

function App() {
  return (
    <main>
      <section>
        <h1>Vite + React</h1>
        <Cowboy  />
      </section>
    </main>
  );
}

export default App;



プレビュー



プレビューになります


あとがき


簡単ですが、以上です。


この記事が気に入ったらサポートをしてみませんか?