ViewData["Thumbnail"] に代入されるため、アップロード後のレスポンスサイズが
画像データのサイズぶん大きくなります。レスポンスの増加を避けたい場合は、POSTによる画像の送信ではなく、AJAX化してWeb APIを非同期で呼び出すことで、
ページ切り替えが発生しないため、画像データの再取得が不要になり、レスポンスサイズが大きくならずに実装できます。Webアプリで画像をドロップするとプレビュー画像が表示されるアップロード枠を実装する場合、
枠自体はdiv等のタグで記述します。
アップロードはダイアログ等を表示することも考慮して、inputタグにするのが一般的ですので、input type="file" を枠内に記述します。
画像がドロップされると、inputタグに画像ファイルを設定し、imgタグをドロップ枠内に作成して画像を表示します。
以下のHTMLファイル,CSSファイルを作成します。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<link rel="stylesheet" href="SimpleImageDragDrop.css" />
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function () {
const dropArea = document.getElementById('ImageDropArea');
const preview = document.getElementById('gallery');
const fileElem = document.getElementById('fileElem');
const dropText = document.getElementById('dropText');
// preventDefaults:全イベントでデフォルトを止める
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
// 枠クリックでファイル選択ダイアログを開く
dropArea.addEventListener('click', () => {
fileElem.click();
});
// dragover をキャンセルしないと drop が発火しない
// 対象要素に一括でイベント登録
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, preventDefaults, false);
});
dropArea.addEventListener('dragover', () => dropArea.classList.add('highlight'));
dropArea.addEventListener('dragleave', () => dropArea.classList.remove('highlight'));
dropArea.addEventListener('drop', () => dropArea.classList.remove('highlight'));
// これを追加しないと、ダイアログから選択しても showPreview が呼ばれない
fileElem.addEventListener('change', e => {
if (e.target.files.length) {
showPreview(e.target.files[0]);
}
});
// ドロップ時
dropArea.addEventListener('drop', e => {
const dt = e.dataTransfer;
if (dt.files.length) {
showPreview(dt.files[0]);
//
const trans = new DataTransfer();
trans.items.add(dt.files[0]);
// これで input.files が上書きできる
fileElem.files = trans.files;
}
});
// プレビュー表示(1枚のみ)
function showPreview(file) {
// instruction を非表示
dropText.style.display = 'none';
if (!file.type.startsWith('image/')) return;
const reader = new FileReader();
reader.onload = () => {
// 既存プレビューをクリア
preview.innerHTML = '';
// 画像要素を生成
const img = document.createElement('img');
img.src = reader.result;
preview.appendChild(img);
};
reader.readAsDataURL(file);
}
});
</script>
</head>
<body>
<h1>画像のドラッグ・アンド・ドロップ枠のデモ</h1>
<form id="uploadForm" action="/upload" method="POST" enctype="multipart/form-data">
<div id="ImageDropArea" class="ImageDropAreaStyle">
<p id="dropText">ここに画像ファイルをドロップしてください</p>
<input type="file" id="fileElem" accept="image/*" hidden />
<div id="gallery"></div>
</div>
<button type="submit">送信</button>
</form>
</body>
</html>
.ImageDropAreaStyle {
width: 480px;
border: 2px dashed #303030;
margin:1rem 0 1rem 0;
}
.ImageDropAreaStyle.highlight {
border: 2px dashed #00e1da;
}
.ImageDropAreaStyle img {
width:100%;
}
preventDefault() stopPropagation() メソッドを呼び出してデフォルトのイベントを止めます。
この処理がないと、dropイベントが発火しません。また、ドロップでブラウザの新しいウィンドウ/タブが開いて画像が表示されてしまいます。
// preventDefaults:全イベントでデフォルトを止める
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
// dragover をキャンセルしないと drop が発火しない
// 対象要素に一括でイベント登録
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, preventDefaults, false);
});
こちらのコードで画像をドラッグして枠に重ねた際の見栄えを変更します。
dropArea.addEventListener('dragover', () => dropArea.classList.add('highlight'));
dropArea.addEventListener('dragleave', () => dropArea.classList.remove('highlight'));
dropArea.addEventListener('drop', () => dropArea.classList.remove('highlight'));
枠をクリックするとファイル選択ダイアログを開きます。
// 枠クリックでファイル選択ダイアログを開く
dropArea.addEventListener('click', () => {
fileElem.click();
});
画像が枠にドロップされると以下のコードを実行します。プレビュー画像の表示とinputタグへの設定をします。
// ドロップ時
dropArea.addEventListener('drop', e => {
const dt = e.dataTransfer;
if (dt.files.length) {
showPreview(dt.files[0]);
//
const trans = new DataTransfer();
trans.items.add(e.target.files[0]);
// これで input.files が上書きできる
fileElem.files = trans.files;
}
});
ダイアログから選択した場合は、プレビューの表示をします。
fileElem.addEventListener('change', e => {
if (e.target.files.length) {
showPreview(e.target.files[0]);
}
});
// プレビュー表示(1枚のみ)
function showPreview(file) {
// instruction を非表示
dropText.style.display = 'none';
if (!file.type.startsWith('image/')) return;
const reader = new FileReader();
reader.onload = () => {
// 既存プレビューをクリア
preview.innerHTML = '';
// 画像要素を生成
const img = document.createElement('img');
img.src = reader.result;
preview.appendChild(img);
};
reader.readAsDataURL(file);
}
上記のページを表示します。下図の画面が表示されます。
画像ファイルをドラッグします。
画像ファイルを枠に重ねると枠の色が変わります。
画像をドロップすると、画像のサムネイルが枠内に表示されます。
なお、枠をクリックすると、ファイル選択ダイアログが表示されます。
ダイアログで選択したファイルのサムネイルが枠に表示されます。
上記のドラッグ&ドロップ枠を利用して、RazorPagesでファイルをアップロードするコードを紹介します。
@page
@model FileUploadDnD.Pages.UploadModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
}
<html>
<head>
<meta charset="utf-8" />
<title></title>
<link rel="stylesheet" href="AppStyle.css" />
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function () {
const dropArea = document.getElementById('ImageDropArea');
const preview = document.getElementById('gallery');
const fileElem = document.getElementById('fileElem');
const dropText = document.getElementById('dropText');
@if (ViewData["Thumbnail"] != null) {
@: showUploadPreview();
}
// preventDefaults:全イベントでデフォルトを止める
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
// 枠クリックでファイル選択ダイアログを開く
dropArea.addEventListener('click', () => {
fileElem.click();
});
// dragover をキャンセルしないと drop が発火しない
// 対象要素に一括でイベント登録
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, preventDefaults, false);
});
dropArea.addEventListener('dragover', () => dropArea.classList.add('highlight'));
dropArea.addEventListener('dragleave', () => dropArea.classList.remove('highlight'));
dropArea.addEventListener('drop', () => dropArea.classList.remove('highlight'));
// これを追加しないと、ダイアログから選択しても showPreview が呼ばれない
fileElem.addEventListener('change', e => {
if (e.target.files.length) {
showPreview(e.target.files[0]);
}
});
// ドロップ時
dropArea.addEventListener('drop', e => {
const dt = e.dataTransfer;
if (dt.files.length) {
showPreview(dt.files[0]);
//
const trans = new DataTransfer();
trans.items.add(dt.files[0]);
// これで input.files が上書きできる
fileElem.files = trans.files;
}
});
// プレビュー表示(1枚のみ)
function showPreview(file) {
// instruction を非表示
dropText.style.display = 'none';
if (!file.type.startsWith('image/')) return;
const reader = new FileReader();
reader.onload = () => {
// 既存プレビューをクリア
preview.innerHTML = '';
// 画像要素を生成
const img = document.createElement('img');
img.src = reader.result;
preview.appendChild(img);
};
reader.readAsDataURL(file);
}
function showUploadPreview() {
// instruction を非表示
dropText.style.display = 'none';
//アップロードされた画像データを表示
const img = document.createElement('img');
img.src = '@Html.Raw(ViewData["Thumbnail"])';
preview.appendChild(img);
}
});
</script>
</head>
<body>
<h1>画像のドラッグ・アンド・ドロップによるアップロード</h1>
<form id="uploadForm" action="" method="POST" enctype="multipart/form-data">
<div id="ImageDropArea" class="ImageDropAreaStyle">
<p id="dropText">ここに画像ファイルをドロップしてください</p>
<input type="file" id="fileElem" accept="image/*" asp-for="FileData" hidden />
<div id="gallery"></div>
</div>
<button type="submit">送信</button>
</form>
<hr />
<div>ファイルサイズ:@Model.FileSize</div>
</body>
</html>
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using static System.Net.Mime.MediaTypeNames;
namespace FileUploadDnD.Pages
{
public class UploadModel : PageModel
{
[BindProperty]
public IFormFile FileData { get; set; }
public int FileSize { get; set; }
public void OnGet()
{
}
public IActionResult OnPost()
{
if (FileData == null || FileData.Length == 0) {
ModelState.AddModelError("FileData", "ファイルが選択されていません。");
return Page();
}
else {
try {
// サムネイル用のBase64エンコード
using var memoryStream = new MemoryStream();
FileData.CopyTo(memoryStream); // IFormFile の内容をメモリストリームにコピー
byte[] fileBytes = memoryStream.ToArray(); // バイト配列に変換
var base64Image = Convert.ToBase64String(memoryStream.ToArray());
ViewData["Thumbnail"] = $"data:{FileData.ContentType};base64,{base64Image}";
}
catch (Exception ex) {
ModelState.AddModelError(string.Empty, $"エラーが発生しました: {ex.Message}");
return Page();
}
FileSize = (int)FileData.Length;
return Page();
}
}
}
}
.ImageDropAreaStyle {
width: 480px;
height: 320px;
border: 2px dashed #303030;
margin: 1rem 0 1rem 0;
padding: 0.5rem 0.5rem 0.5rem 0.5rem;
/*padding: 0 0 0 0;*/
}
.ImageDropAreaStyle.highlight {
border: 2px dashed #00e1da;
}
#gallery {
width: 100%;
height: 100%;
}
.ImageDropAreaStyle img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
namespace FileUploadDnD
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
var app = builder.Build();
app.UseStaticFiles();
app.UseRouting();
app.MapRazorPages();
app.Run();
}
}
}
JavaScript部分は先に紹介したコードと同様です。
アップロード後に画像が枠内に表示されるよう、アップロードされた画像のデータは ViewData["Thumbnail"] に代入します。
代入する際の書式は以下になります。
data:image/png;base64,(base64形式でエンコードされた画像データ)
data:image/jpeg;base64,(base64形式でエンコードされた画像データ)
public IActionResult OnPost()
{
if (FileData == null || FileData.Length == 0) {
ModelState.AddModelError("FileData", "ファイルが選択されていません。");
return Page();
}
else {
try {
// サムネイル用のBase64エンコード
using var memoryStream = new MemoryStream();
FileData.CopyTo(memoryStream); // IFormFile の内容をメモリストリームにコピー
byte[] fileBytes = memoryStream.ToArray(); // バイト配列に変換
var base64Image = Convert.ToBase64String(memoryStream.ToArray());
ViewData["Thumbnail"] = $"data:{FileData.ContentType};base64,{base64Image}";
}
catch (Exception ex) {
ModelState.AddModelError(string.Empty, $"エラーが発生しました: {ex.Message}");
return Page();
}
FileSize = (int)FileData.Length;
return Page();
}
}
ViewData["Thumbnail"] に値が設定されている場合は、showUploadPreview()関数を呼び出すコードをページ表示時に作成し、
ページ表示時に、showUploadPreview関数を呼び出し、枠内にアップロードした画像を表示します。
@if (ViewData["Thumbnail"] != null) {
@: showUploadPreview();
}
ViewData["Thumbnail"] に代入されるため、アップロード後のレスポンスサイズが
画像データのサイズぶん大きくなります。レスポンスの増加を避けたい場合は、POSTによる画像の送信ではなく、AJAX化してWeb APIを非同期で呼び出すことで、
ページ切り替えが発生しないため、画像データの再取得が不要になり、レスポンスサイズが大きくならずに実装できます。プロジェクトを実行し、(アプリケーションルートURL)/Upload にアクセスします。下図のページが表示されます。
画像を枠内にドラッグしてドロップします。ドロップすると枠内に画像が表示されます。
[送信]ボタンをクリックします。
画像がアップロードされ、画像のファイルサイズがページ下部に表示されます。