実装

【React】ファイル選択ボタンをカスタマイズする

【実装】スタイリングされたファイル選択ボタンを作成する
nanoha.creator@gmail.com

プロフィール編集ページでアイコン画像を設定する場合など、ローカルにあるファイルを選択できるようにする必要があります。
ファイル選択ボタンを実装する方法とスタイルのカスタマイズ方法についてまとめました。

実装するもの

以下のようなファイル選択ボタンのコンポーネントを実装解説したいと思います。

プロフィールアイコンボタンをクリックするとファイルが選択できるようになって、ファイルを選択すると、プロフィールアイコンの画像が切り替わるようにします。

画像選択ボタンのデモ

環境

実行環境のバージョンは、以下の通りです。
スタイリングには、tailwindcssを使用します。

Next.js
14.2.3
React18.3.1
tailwindcss3.4.4

実装

以下の手順で実装していきます。

  1. ファイルを選択できるようにする
  2. ボタンのスタイルをカスタマイズする
  3. 画像を画面に表示する

①ファイルを選択できるようにする

以下のコードで、ファイルを選択できるようになります。

<input
  type="file"      // ファイルを選択できるようにする
  accept="image/*" // 選択できるファイルの種類を画像ファイルに限定する
/>

テキスト入力などでよく使用されるinput要素のtype属性にfileを指定することで、ローカルのファイルを選択できるようになります。

詳細については、以下をご参照ください。

上記のコードで、以下が表示されます。

ファイルを選択できるようにする

「Choose File」ボタンをクリックすると、ファイルが選択できるようになり、ファイルを選択すると「No file chosen」のところにファイル名が表示されます。

②ボタンのスタイルをカスタマイズする

ファイルが選択できるようになりましたが、input要素にスタイルを指定して希望通りの見た目にすることは難しいので、以下のようにしてスタイルをカスタマイズします。

  1. input要素をhidden属性で隠す
  2. ボタンを新たに作成する
  3. ボタンがクリックされたときにinput要素のクリックイベントを発火させる

input要素をhidden属性で隠す

input要素をhidden属性で要素を隠すことができます。

<input
  type="file"
  accept="image/*"
  hidden           // 画面上に表示させない
/>

②ボタンを新たに作成する

希望の見た目になるようにスタイリングをしたボタンを作成します。
tailwindcssを使用しました。

<div className="relative w-[80px] h-auto aspect-square mx-auto bg-gray-500 rounded-full">
  <button
    type="button"
    className="absolute w-full h-full hover:opacity-50 transition-all rounded-full hover:bg-gray-600"
  />
  <input
    type="file"
    accept="image/*"
    hidden
  />
  <CameraIcon className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-8 h-auto text-white pointer-events-none" />
</div>

アイコンは、heroiconsを使用しました。
heroiconsは、classNameでスタイル定義できるので便利です。

③ボタンがクリックされたときにinput要素のクリックイベントを発火させる

useRefを使用して、ボタンがクリックされたときにinput要素のクリックイベントを発火させます。

useRefについては、以下をご参照ください。

以下のとおり、変数を定義します。

const fileInputRef = useRef<HTMLInputElement | null>(null);

button要素にonClick属性、input要素にref属性を追加します。

<div className="relative w-[80px] h-auto aspect-square mx-autobg-gray-500 rounded-full">
  <button
    type="button"
    onClick={() => !!fileInputRef.current && fileInputRef.current.click()}
    className="absolute w-full h-full hover:opacity-50 transition-all rounded-full hover:bg-gray-600"
  />
  <input
    type="file"
    accept="image/*"
    hidden
    ref={fileInputRef}
  />
  <CameraIcon className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-8 h-auto text-white pointer-events-none" />
</div>

ここまでで、希望の見た目のボタンができて、クリックするとファイルが選択できるようになりました。

③画像を画面に表示する

以下のようにして、選択した画像を画面に表示します。

  1. input要素にonChange属性を追加する
  2. 画像を表示させる

input要素にonChange属性を追加する

input要素にonChange属性を追加して、ファイルが選択されたときの処理を作成します。

<div className="relative w-[80px] h-auto aspect-square mx-autobg-gray-500 rounded-full">
  <button
    type="button"
    onClick={() => !!fileInputRef.current && fileInputRef.current.click()}
    className="absolute w-full h-full hover:opacity-50 transition-all rounded-full hover:bg-gray-600"
  />
  <input
    type="file"
    accept="image/*"
    hidden
    ref={fileInputRef}
    onChange={handleChangeProfileImg}
  />
  <CameraIcon className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-8 h-auto text-white pointer-events-none" />
</div>

ファイルが選択されたときの処理は、以下のようにしています。

const [profileSrc, setProfileSrc] = useState<string>("");

// ファイルが選択されたらオブジェクトURLを生成する
const handleChangeProfileImg = (
  e: React.ChangeEvent<HTMLInputElement>
) => {
  const { files } = e.target;
  if (!!files && !!files[0]) {
    setProfileSrc(window.URL.createObjectURL(files[0]));
  }
};

// ページが切り替わるときにオブジェクトURLを開放する
useEffect(() => {
  return () => {
    if (profileSrc) {
      window.URL.revokeObjectURL(profileSrc);
    }
  };
}, [profileSrc]);

createObjectURL()については、以下をご参照ください。

選択されたファイルから、一時的なURLを生成することができます。
不要になったときに開放する必要があります。

②画像を表示させる

profileSrcに画像URLが入っていたら、表示させるようにします。

<div className="relative w-[80px] h-auto aspect-squaremx-autobg-gray-500 rounded-full">
  {!!profileSrc && (
    <Image
      src={profileSrc}
      alt="プロフィール画像"
      fill={true}
      style={{ objectFit: "cover" }}
      className="rounded-full"
    />
  )}
  <button
    type="button"
    onClick={() => !!fileInputRef.current && fileInputRef.current.click()}
    className="absolute w-full h-full hover:opacity-50 transition-all rounded-full hover:bg-gray-600"
  />
  <input
    type="file"
    accept="image/*"
    hidden
    ref={fileInputRef}
    onChange={handleChangeProfileImg}
  />
  <CameraIcon className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-8 h-auto text-white pointer-events-none" />
</div>

Next.jsのImageコンポーネントを使用しています。
fill属性とstyle属性を上記のように指定することで、アスペクト比を維持しつつ、親要素に合わせて画像をトリミングしてくれます。

詳細は、以下をご参照ください。

以上で、スタイリングされたファイル選択ボタンの完成です。

ABOUT ME
Haruna Abe
Haruna Abe
Webエンジニア
フロントエンドエンジニア2年目|ReactやTypeScriptなど、フロントエンドの情報を発信していきます
CONTACT

お問い合わせ

制作のお見積もりやご依頼、その他ご相談等ございましたら
お気軽にお問い合わせください。

    記事URLをコピーしました