【React】ファイル選択ボタンをカスタマイズする
プロフィール編集ページでアイコン画像を設定する場合など、ローカルにあるファイルを選択できるようにする必要があります。
ファイル選択ボタンを実装する方法とスタイルのカスタマイズ方法についてまとめました。
実装するもの
以下のようなファイル選択ボタンのコンポーネントを実装解説したいと思います。
プロフィールアイコンボタンをクリックするとファイルが選択できるようになって、ファイルを選択すると、プロフィールアイコンの画像が切り替わるようにします。
環境
実行環境のバージョンは、以下の通りです。
スタイリングには、tailwindcssを使用します。
Next.js | 14.2.3 |
React | 18.3.1 |
tailwindcss | 3.4.4 |
実装
以下の手順で実装していきます。
- ファイルを選択できるようにする
- ボタンのスタイルをカスタマイズする
- 画像を画面に表示する
①ファイルを選択できるようにする
以下のコードで、ファイルを選択できるようになります。
<input
type="file" // ファイルを選択できるようにする
accept="image/*" // 選択できるファイルの種類を画像ファイルに限定する
/>
テキスト入力などでよく使用されるinput
要素のtype
属性にfile
を指定することで、ローカルのファイルを選択できるようになります。
詳細については、以下をご参照ください。
上記のコードで、以下が表示されます。
「Choose File」ボタンをクリックすると、ファイルが選択できるようになり、ファイルを選択すると「No file chosen」のところにファイル名が表示されます。
②ボタンのスタイルをカスタマイズする
ファイルが選択できるようになりましたが、input
要素にスタイルを指定して希望通りの見た目にすることは難しいので、以下のようにしてスタイルをカスタマイズします。
input
要素をhidden
属性で隠す- ボタンを新たに作成する
- ボタンがクリックされたときに
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>
ここまでで、希望の見た目のボタンができて、クリックするとファイルが選択できるようになりました。
③画像を画面に表示する
以下のようにして、選択した画像を画面に表示します。
input
要素にonChange
属性を追加する- 画像を表示させる
①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
属性を上記のように指定することで、アスペクト比を維持しつつ、親要素に合わせて画像をトリミングしてくれます。
詳細は、以下をご参照ください。
以上で、スタイリングされたファイル選択ボタンの完成です。