ファイルを保存する方法

ファイルの処理は、ウェブ上のアプリでよく行われる操作の一つです。これまでは、ユーザーがファイルをアップロードし、変更を加えてから再度ダウンロードすることで、ダウンロード フォルダにコピーする必要がありました。File System Access API を使用すると、ユーザーはファイルを直接開き、変更を加えたり、元のファイルに変更を保存したりできるようになります。

ファイルを保存するには showSaveFilePicker() を呼び出します。これにより、FileSystemFileHandle を含む Promise が返されます。必要なファイル名を { suggestedName: 'example.txt' } としてメソッドに渡すことができます。

対応ブラウザ

  • 86
  • 86
  • x
  • x

ソース

従来のやり方

<a download> 要素の使用

ページ上の <a download> 要素を使用すると、ユーザーはクリックしてファイルをダウンロードできます。このコツは、JavaScript を使って見えない形でページに要素を挿入し、プログラムでそれをクリックするというものです。

対応ブラウザ

  • 15
  • 13
  • 20
  • 10.1

ソース

段階的な補正

以下のメソッドでは、サポートされている場合は File System Access API を使用し、それ以外の場合は従来のアプローチにフォールバックします。どちらの場合もファイルが保存されますが、File System Access API がサポートされている場合は、ファイル保存ダイアログが表示され、ファイルの保存先を選択できます。

const saveFile = async (blob, suggestedName) => {
 
// Feature detection. The API needs to be supported
 
// and the app not run in an iframe.
 
const supportsFileSystemAccess =
   
'showSaveFilePicker' in window &&
   
(() => {
     
try {
       
return window.self === window.top;
     
} catch {
       
return false;
     
}
   
})();
 
// If the File System Access API is supported…
 
if (supportsFileSystemAccess) {
   
try {
     
// Show the file save dialog.
     
const handle = await showSaveFilePicker({
        suggestedName
,
     
});
     
// Write the blob to the file.
     
const writable = await handle.createWritable();
      await writable
.write(blob);
      await writable
.close();
     
return;
   
} catch (err) {
     
// Fail silently if the user has simply canceled the dialog.
     
if (err.name !== 'AbortError') {
        console
.error(err.name, err.message);
       
return;
     
}
   
}
 
}
 
// Fallback if the File System Access API is not supported…
 
// Create the blob URL.
 
const blobURL = URL.createObjectURL(blob);
 
// Create the `<a download>` element and append it invisibly.
 
const a = document.createElement('a');
  a
.href = blobURL;
  a
.download = suggestedName;
  a
.style.display = 'none';
  document
.body.append(a);
 
// Programmatically click the element.
  a
.click();
 
// Revoke the blob URL and remove the element.
  setTimeout
(() => {
    URL
.revokeObjectURL(blobURL);
    a
.remove();
 
}, 1000);
};

参考資料

デモ

<!DOCTYPE html>
<html lang="en">
 
<head>
   
<meta charset="utf-8" />
   
<meta name="viewport" content="width=device-width, initial-scale=1" />
    <link
      rel="icon"
      href="data:image/svg+xml,
<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎉</text></svg>"
    />
   
<title>How to save a file</title>
 
</head>
 
<body>
   
<h1>How to save a file</h1>

   
<label
     
>Text to save
     
<textarea rows="3">
Some sample text for you to save. Feel free to edit this.
</textarea
     
>
   
</label>
   
<label>File name <input class="text" value="example.txt" /></label>
   
<button class="text" type="button">Save text</button>

   
<label
     
>Image to save
     
<img
       
src="https://cdn.glitch.global/75170424-3d76-41d7-ae77-72d0efb0401b/AVIF%20Test%20picture%20(JPEG%20converted%20to%20AVIF%20with%20Convertio).avif?v=1658240752363"
       
alt="Blue flower."
       
width="630"
       
height="420"
   
/></label>
   
<label>File name <input class="img" value="example.avif" /></label>
   
<button class="img" type="button">Save image</button>
 
</body>
</html>

       
:root {
 
color-scheme: dark light;
}

html
{
 
box-sizing: border-box;
}

*,
*:before,
*:after {
 
box-sizing: inherit;
}

body
{
 
margin: 1rem;
 
font-family: system-ui, sans-serif;
}

img
{
 
max-width: 320px;
 
height: auto;
}

label
,
button
,
textarea
,
input
,
img
{
 
display: block;
 
margin-block: 1rem;
}
       

       
const textarea = document.querySelector('textarea');
const textInput = document.querySelector('input.text');
const textButton = document.querySelector('button.text');

const img = document.querySelector('img');
const imgInput = document.querySelector('input.img');
const imgButton = document.querySelector('button.img');

const saveFile = async (blob, suggestedName) => {
 
// Feature detection. The API needs to be supported
 
// and the app not run in an iframe.
 
const supportsFileSystemAccess =
   
'showSaveFilePicker' in window &&
   
(() => {
     
try {
       
return window.self === window.top;
     
} catch {
       
return false;
     
}
   
})();
 
// If the File System Access API is supported…
 
if (supportsFileSystemAccess) {
   
try {
     
// Show the file save dialog.
     
const handle = await showSaveFilePicker({
        suggestedName
,
     
});
     
// Write the blob to the file.
     
const writable = await handle.createWritable();
      await writable
.write(blob);
      await writable
.close();
     
return;
   
} catch (err) {
     
// Fail silently if the user has simply canceled the dialog.
     
if (err.name !== 'AbortError') {
        console
.error(err.name, err.message);
       
return;
     
}
   
}
 
}
 
// Fallback if the File System Access API is not supported…
 
// Create the blob URL.
 
const blobURL = URL.createObjectURL(blob);
 
// Create the `` element and append it invisibly.
 
const a = document.createElement('a');
  a
.href = blobURL;
  a
.download = suggestedName;
  a
.style.display = 'none';
  document
.body.append(a);
 
// Click the element.
  a
.click();
 
// Revoke the blob URL and remove the element.
  setTimeout
(() => {
    URL
.revokeObjectURL(blobURL);
    a
.remove();
 
}, 1000);
};

textButton
.addEventListener('click', async () => {
 
const blob = new Blob([textarea.value], { type: 'text/plain' });
  await saveFile
(blob, textInput.value);
});

imgButton
.addEventListener('click', async () => {
 
const blob = await fetch(img.src).then((response) => response.blob());
  await saveFile
(blob, imgInput.value);
});
       

特に記載のない限り、このページのコンテンツはクリエイティブ・コモンズの表示 4.0 ライセンスにより使用許諾されます。コードサンプルは Apache 2.0 ライセンスにより使用許諾されます。詳しくは、Google Developers サイトのポリシーをご覧ください。Java は Oracle および関連会社の登録商標です。

最終更新日 2023-10-25 UTC。