파일 저장 방법

토마스 슈타이너
토마스 슈타이너

파일 처리는 웹에서 앱의 가장 일반적인 작업 중 하나입니다. 이전에는 사용자가 파일을 업로드하고 변경한 후 다시 다운로드하여 복사본이 다운로드 폴더에 있어야 했습니다. File System Access API를 사용하면 이제 사용자가 직접 파일을 열고 수정하고 변경사항을 원래 파일에 다시 저장할 수 있습니다.

현대적인 방식

File System Access API의 showSaveFilePicker() 메서드 사용

파일을 저장하려면 showSaveFilePicker()를 호출합니다. 이 메서드는 FileSystemFileHandle를 사용하여 프로미스를 반환합니다. 원하는 파일 이름을 메서드에 { suggestedName: 'example.txt' }로 전달할 수 있습니다.

브라우저 지원

  • 86
  • 86
  • x
  • x

소스

기존의 방식

<a download> 요소 사용

페이지의 <a download> 요소를 사용하면 사용자가 요소를 클릭하여 파일을 다운로드할 수 있습니다. 이 방법은 자바스크립트를 사용하여 페이지에 보이지 않게 요소를 삽입하고 프로그래매틱 방식으로 클릭하는 것입니다.

브라우저 지원

  • 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);
};

추가 자료

데모

HTML

<!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>

CSS


        :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;
}
        

JS


        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);
});
        

달리 명시되지 않는 한 이 페이지의 콘텐츠에는 Creative Commons Attribution 4.0 라이선스에 따라 라이선스가 부여되며, 코드 샘플에는 Apache 2.0 라이선스에 따라 라이선스가 부여됩니다. 자세한 내용은 Google Developers 사이트 정책을 참조하세요. 자바는 Oracle 및/또는 Oracle 계열사의 등록 상표입니다.

최종 업데이트: 2023-10-25(UTC)