Blazor WebAssembly でのファイル ドラッグ&ドロップの実装

2024/10/06
★★

Blazor WebAssembly でのファイル ドラッグ&ドロップの実装の検討

Blazor WebAssembly では、JavaScript との相互運用ができるため公開されている情報や各種 JavaScript のライブラリを使用すれば、ファイル ドラッグ&ドロップの実装ができます。
JavaScript を使用すれば、ほとんどのことは実現できると思いますが、今回は、あえて JavaScript の力を借りずに、Blazor WebAssembly で提供されている範囲で実装する方法を順を追って検討し実現していきたいと思います。

InputFile コンポーネント

Blazor WebAssembly 標準では、ファイルの操作に InputFile コンポーネントが使用できます。

<InputFile OnChange="OnChange" multiple />

@code {
    private void OnChange(InputFileChangeEventArgs e)
    {
        // 処理
    }
}

ユーザーが [ファイルの選択] を選択すると、ファイル選択ダイアログが起動します。ダイアログからファイルを選択すると、OnChange イベントが発火します。
また、このコンポーネントの領域にファイルをドラッグしてファイルをドロップすることで、OnChange イベントを発火させることもできます。
ただし、この InputFile コンポーネントの外観を加工することは難しく、質素な見た目になります。

alt text

InputFile コンポーネントを配置すると、<input type="file"> がレンダリングされます。

alt text

InputFile コンポーネントのファイル操作

InputFile.OnChange プロパティの型は、EventCallback<InputFileChangeEventArgs> となっており、ユーザーがファイルを選択した際に、このコールバックが起動します。

コールバックの引数に InputFileChangeEventArgs をとることができ、この引数の InputFileChangeEventArgs.File からユーザーの選択したファイルの情報取得、アクセスができます。

private void OnChange(InputFileChangeEventArgs e)
{
	var files =  e.GetMultipleFiles();

	foreach (var file in files)
	{
		_logger.LogInformation(
			"ファイル名: {filename}, サイズ: {size}, 更新日時: {lastModified}",
			file.Name, // ファイル名
			file.Size, // ファイル サイズ
			file.LastModified // ファイル更新日時
		);

		// ファイル ストリームのオープン
		using var stream = file.OpenReadStream();

	}
}

IBrowserFile.OpenReadStream でファイルのストリームも取得できるので、これを使用しアップロード処理につなげることができます。
ファイル アップロードの処理については、以下の記事に記載があります。

ファイル ドラッグのカスタム実装の検討

Blazor WASM の InputFile コンポーネントでは、見た目の加工の自由度が低いためカスタムでファイルのドロップ フィールドを実装することを考えます。 以下のコードは、DIV 要素にファイルをドロップできるように実装した例です。

<div class="row bg-secondary align-items-center" style="min-height: 100px;"
	 @ondragenter:stopPropagation @ondragenter:preventDefault
	 @ondragover:stopPropagation @ondragover:preventDefault
	 @ondrop:stopPropagation @ondrop:preventDefault @ondrop="OnDrop">
	<div class="col text-center">
		ここにファイルをドロップしてください。
	</div>
</div>

@code {
    private async Task OnDrop(DragEventArgs args)
    {	
	    var files = args.DataTransfer.Files;

        foreach (var file in files)
        {
            var f = file;
        }
    }
}

DIV 要素で @ondrop を宣言し、ファイル ドラッグ イベントを受信できるようにしています。 また、@ondragenter:stopPropagation, @ondragenter:preventDefault, @ondragover:stopPropagation, @ondragover:preventDefault, @ondrop:stopPropagation, @ondrop:preventDefault を宣言しドラッグ及びドロップ操作のデフォルトの動作を無効化しています。
JavaScript では、以下のように stopPropagation, preventDefault をイベント引数で実装しますが、Blazor WASM では、対象の要素内で宣言します。

function dragover(e) {
  e.stopPropagation();
  e.preventDefault();
}

@ondrop の実装では、DragEventArgs を受け取ります。DragEventArgs.Datatransfer.Files にドロップされたファイルの情報が入るのですが、このプロパティの型は string[] であり、残念ながらファイルのバイナリにはアクセスできません。

Blazor の組み込みのイベント引数のサイトにも以下の記載があり、JavaScript での実装が必要となります。

alt text

JavaScript によるファイル ドラッグ&ドロップの実装は、多くの例が説明されています。

よって、ここでは、別の方法での実装を考えたいと思います。

InputFile コンポーネントの活用

あらためて InputFile コンポーネントを使用した実装を考えたいと思います。

InputFile コンポーネントに高さ、幅を指定することで、ファイルをドロップできる領域を広げることができます。
広げた領域をクリックした場合に、ファイル ダイアログが起動するので、これを防ぐために @onclick:preventDefault で、クリック イベントに反応しないようにします。

<h3 class="mb-4">InputFile 高さ 100px</h3>
<div class="row bg-secondary align-items-center mb-5">
	<InputFile class="col w-100" style="min-height: 100px;"
		@onclick:preventDefault OnChange="OnChange" />
</div>

@code {
    private void OnChange(InputFileChangeEventArgs e)
    {
        // 処理
    }
}

alt text

この状態だとコントロールが表示されているので、InputFile コントロールを透明にしてコントロールが表示されないようにします。
スタイルで opacity: 0(bootstrap では、opacity-0) を指定することで、コントロールの透明度を 100% (不透明度を 0%) にできます。
透明度を 100% にすることで、コントロールを非表示にできユーザー操作に反応することもなくなります。ただし、ドロップ操作には反応します。

<h3 class="mb-4">InputFile 高さ 100px, 透明度 100%</h3>
<div class="row bg-secondary align-items-center mb-5">
	<InputFile class="col w-100 opacity-0" style="min-height: 100px;"
		@onclick:preventDefault OnChange="OnChange" />
</div>

@code {
    private void OnChange(InputFileChangeEventArgs e)
    {
        // 処理
    }
}

alt text

さらに、文字列を描画するには、親要素を position: relative(bootstrap では、position-relative) に設定し、そこを基準に文字列領域を position: absolute(bootstrap では、position-absolute) で、InputFile コンポーネントの背面に重ねて描画します。

<h3 class="mb-4">InputFile 高さ 100px, 透明度 100%, キャプション</h3>
<div class="row position-relative bg-secondary align-items-center mb-5">
	<div class="col position-absolute text-center">
		<div>ここにファイルをドロップしてください。</div>
	</div>
	<InputFile class="col w-100 opacity-0" style="min-height: 100px;"
		@onclick:preventDefault OnChange="OnChange" />	
</div>

@code {
    private void OnChange(InputFileChangeEventArgs e)
    {
        // 処理
    }
}

alt text

DragEventArgs.Datatransfer.Files で取得できる値が、InputFileChangeEventArgs.File であれば、JavaScript と同様の実装ができますが、現状ではサポートされていないため、このような別のアプローチをとる必要があります。
フロントエンドでのファイル操作は、一般的な機能として実装する機会が多いので、この点が改善されることを期待します。

コメント (0)

コメントの投稿