はじめに
ファイルアップロード機能、「とりあえず<input type="file">を置いて終わり」にしていませんか。実際にはドラッグ&ドロップ対応、進捗表示、複数ファイル選択、通信が切れたときの再開処理など、まともに作り込もうとすると意外と工数がかかる領域です。しかもGoogle DriveやWebカメラからのアップロードまで求められると、自前実装はあっという間に破綻してしまいます。
Uppyは、こうしたファイルアップロードUIの面倒な部分をまとめて肩代わりしてくれるJavaScriptライブラリです。本記事では、Uppyの特徴からインストール、実際に手を動かせるコード例までを紹介します。
とはいえ、読むより触った方が早いと思います。Dashboard UIとファイル選択のイベントフックをその場で動かせるサンプルを用意したので、先に挙動を見たい方はこちらからどうぞ。
Uppyとは
Uppyは、Transloaditチームが開発しているオープンソース(MITライセンス)のモジュール型ファイルアップローダーです。GitHub上で3万を超えるスターを獲得しており、React・Vue・Svelte・Angularといった主要フレームワークへの公式対応も進んでいる、活発に開発が続くプロジェクトです。
コアとなる@uppy/coreに、UIを提供する@uppy/dashboard、アップロード先ごとのプラグイン(@uppy/tusや@uppy/aws-s3など)を組み合わせて使う設計になっており、必要な機能だけを取捨選択できるのが大きな強みです。
主な特徴
- プラグインベースの設計 - コアは最小限に保たれており、UI・アップロード先・取得元をプラグインとして自由に組み合わせられる
- レジューマブルアップロード - tusプロトコルに対応しており、通信が途切れても続きから再開できる
- 多様なファイル取得元 - ローカルディスクだけでなく、Google Drive、Dropbox、Webカメラなどからの取得にも対応
- フレームワーク対応の柔軟さ - React・Vue・Svelte向けにコンポーネント、ヘッドレスAPI、フックの3通りの使い方が用意されている
インストール
npmまたはyarnで、コアとDashboard、アップロード先プラグイン(ここではtusを例にします)をまとめてインストールします。
npm install @uppy/core @uppy/dashboard @uppy/tus
DashboardのUIにはスタイルシートも必要です。
npm install @uppy/core @uppy/dashboard @uppy/tus @uppy/webcam
CSSはJavaScript側でインポートするか、CDN経由で読み込みます。
import '@uppy/core/dist/style.min.css';
import '@uppy/dashboard/dist/style.min.css';
Uppyのサンプルを動かす
@uppy/coreと@uppy/dashboardを組み合わせると、ファイル選択のUIをまるごと作らずに済みます。下のサンプルでは、Uppyインスタンスにrestrictions(ファイルサイズ・形式・点数の上限)を設定したうえでDashboardプラグインを埋め込み、ファイルが選択されるたびに発火するfile-addedイベントと、取り消されたときのfile-removedイベントをフックして、選択中のファイル名とサイズを画面下のリストに反映しています。アップロード先のプラグイン(tusやXHRUpload)はあえて外しているので、この場では通信は一切発生しません。
要点だけを抜き出すと、Dashboardの初期化とrestrictionsの指定は次のような形です。
import Uppy from '@uppy/core'
import Dashboard from '@uppy/dashboard'
const uppy = new Uppy({
restrictions: {
maxFileSize: 5 * 1024 * 1024, // 5MB
maxNumberOfFiles: 3,
allowedFileTypes: ['image/*', 'application/pdf'],
},
}).use(Dashboard, {
inline: true,
target: '#uppy-dashboard',
height: 300,
})
// ファイルが追加・削除されるたびに呼ばれる
uppy.on('file-added', (file) => console.log('追加:', file.name))
uppy.on('file-removed', (file) => console.log('削除:', file.name))
実際に動かせるものが下です。画像やPDF以外のファイルを選ぼうとしたり、4つ目のファイルを追加しようとすると、restrictionsのバリデーションに引っかかってDashboard上にエラーメッセージが表示されます。
restrictionsのallowedFileTypesを['image/png']のように絞り込むと、Dashboardのファイル選択ダイアログやドラッグ&ドロップの受け付け条件がその場で変わります。maxNumberOfFilesを1に変更すれば、2つ目のファイルを追加しようとした瞬間にrestriction-failedイベントが発火し、選択が弾かれる挙動も確認できます。
基本的な使い方
まずは最小構成として、Dashboard UIを表示し、tusサーバーへアップロードする例です。
import Uppy from '@uppy/core';
import Dashboard from '@uppy/dashboard';
import Tus from '@uppy/tus';
const uppy = new Uppy({
restrictions: {
maxFileSize: 10 * 1024 * 1024, // 10MB
maxNumberOfFiles: 5,
allowedFileTypes: ['image/*', 'application/pdf'],
},
})
.use(Dashboard, {
inline: true,
target: '#uppy-dashboard',
height: 400,
})
.use(Tus, {
endpoint: 'https://tusd.example.com/files/',
});
uppy.on('complete', (result) => {
console.log('アップロード成功:', result.successful);
console.log('アップロード失敗:', result.failed);
});
HTML側にはDashboardを描画するコンテナを用意するだけです。
<div id="uppy-dashboard"></div>
restrictionsでファイルサイズや形式、点数の上限を設定できるため、バリデーションを個別に実装する手間も省けます。
実践的なユースケース
React環境では、公式が提供する@uppy/reactを使うことで、Dashboardをコンポーネントとして扱えます。プロフィール画像のアップロードフォームを例に見てみましょう。
import { useState } from 'react';
import Uppy from '@uppy/core';
import { Dashboard } from '@uppy/react';
import Tus from '@uppy/tus';
function ProfileImageUploader() {
const [uppy] = useState(() =>
new Uppy({
restrictions: {
maxNumberOfFiles: 1,
allowedFileTypes: ['image/*'],
},
}).use(Tus, {
endpoint: '/api/uploads', // 自前のバックエンドのtusエンドポイント
})
);
uppy.on('complete', (result) => {
const uploadedFile = result.successful[0];
if (uploadedFile) {
console.log('アップロード完了:', uploadedFile.uploadURL);
}
});
return (
<Dashboard
uppy={uppy}
proudlyDisplayPoweredByUppy={false}
height={300}
note="JPEG・PNG形式、1枚のみアップロード可能です"
/>
);
}
export default ProfileImageUploader;
外出先での通信環境を想定するアプリであれば、Golden Retrieverプラグインを追加しておくと、ブラウザがクラッシュしたりタブを誤って閉じたりしても、アップロード状態をローカルに保持して復旧できます。
import GoldenRetriever from '@uppy/golden-retriever';
uppy.use(GoldenRetriever, { serviceWorker: false });
イベント検収管理システムの提出書類アップロードや、ECサイトの商品画像登録フォームなど、「ユーザーがアップロードを中断されても困らない」ことが求められる場面で特に効果を発揮します。
Drag & DropでUppyの選択エリアを自作する
Dashboard全体を出すほどではないけれど、既存フォームの一部だけドラッグ&ドロップに対応させたい、という場面もあります。そんなときは@uppy/dashboardを使わず、@uppy/drag-dropだけを組み込めば、シンプルなドロップエリアだけを作れます。以下はDragDropプラグインを#drop-areaにマウントし、file-addedイベントでドロップ・選択されたファイルを一覧表示する例です。
import Uppy from '@uppy/core'
import DragDrop from '@uppy/drag-drop'
const uppy = new Uppy({
restrictions: { allowedFileTypes: ['image/*'], maxNumberOfFiles: 5 },
}).use(DragDrop, {
target: '#drop-area',
note: 'ここに画像をドラッグ、またはクリックして選択',
})
uppy.on('file-added', (file) => {
console.log('選択されたファイル:', file.name)
})
実際に動かせるものが下です。画像ファイルをドロップまたはクリックで選択すると、下のリストに追加され、「削除」ボタンからuppy.removeFile()で取り消せます。
DragDropのnoteオプションを書き換えると、ドロップエリア内の案内文がその場で変わります。restrictions.allowedFileTypesを['image/png']のように絞り込めば、対応していない拡張子のファイルをドロップした瞬間に選択が弾かれる挙動も確認できます。実際のアップロードには、このDragDropに@uppy/tusや@uppy/xhr-uploadを追加するだけで、ドラッグ&ドロップ対応のアップロードフォームが完成します。
Uppyのイベントフックでプレビューと合計サイズを表示する
Uppyはfile-added・file-removedのほか、@uppy/tusや@uppy/xhr-uploadのようなアップロード先プラグインを組み込むとupload-progressやupload-successといったイベントも発火します。ここではネットワーク通信を伴わない範囲で、file-added・file-removedをフックし、選択された画像のサムネイルと合計サイズをリアルタイムに表示する例を紹介します。プレビュー生成にはURL.createObjectURL()を使い、Uppyが保持するfile.data(選択されたファイルの実体)をそのまま渡しています。
import Uppy from '@uppy/core'
import FileInput from '@uppy/file-input'
const uppy = new Uppy({
restrictions: { allowedFileTypes: ['image/*'], maxNumberOfFiles: 5 },
}).use(FileInput, { target: '#file-input' })
uppy.on('file-added', (file) => {
const previewUrl = URL.createObjectURL(file.data)
console.log(file.name, previewUrl)
})
uppy.on('file-removed', (file) => {
console.log('削除されました:', file.name)
})
実際に動かせるものが下です。「ファイルを選択」ボタンから画像を選ぶと、サムネイルと合計サイズがその場で更新されます。
file-addedのコールバック内でURL.createObjectURL()を呼んでいる部分を、実際のアップロード時に発火するupload-successイベントの中に移せば、「アップロードが完了した画像だけをプレビューに表示する」という挙動に変えられます。@uppy/tusを.use()で追加し、uppy.on('upload-success', (file, response) => { ... })のようにハンドラを書けば、サーバーが返したURLをそのままプレビューに使うことも可能です。
まとめ
Uppyを使うことで、ドラッグ&ドロップ対応、進捗表示、複数ファイル取得元、通信断からの再開といった、ファイルアップロードUIにまつわる面倒な実装をまとめて任せられることが分かりました。プラグイン構成のおかげで、必要な機能だけを選んで軽量に導入できるのも実用的なポイントです。
まずは@uppy/coreと@uppy/dashboardだけの最小構成で動かしてみて、そこからtusやAWS S3、Webカメラといったプラグインを少しずつ追加していくのがおすすめです。公式ドキュメントには各プラグインのオプションが詳しく載っているので、自分のアプリに合わせてカスタマイズしてみてください。