Lidar com arquivos é uma das operações mais comuns para apps na Web. Tradicionalmente, os usuários precisavam fazer upload de um arquivo, fazer algumas alterações e, em seguida, fazer o download novamente, o que resultava em uma cópia na pasta "Downloads". Com a API File System Access, os usuários agora podem abrir arquivos diretamente, fazer modificações e salvar as alterações no arquivo original.
O jeito mais moderno
Como usar o método showOpenFilePicker()
da API File System Access
Para abrir um arquivo, chame showOpenFilePicker()
, que retorna uma promessa com uma matriz dos arquivos escolhidos. Se você precisar de vários arquivos, transmita { multiple: true, }
para o método.
A forma clássica
Como usar o elemento <input type="file">
O elemento <input type="file">
em uma página permite que o usuário clique nele e abra
um ou vários arquivos. Agora, o truque consiste em inserir o elemento de forma invisível em uma página com JavaScript e clicar nele de maneira programática.
Aprimoramento progressivo
O método abaixo usa a API File System Access quando há suporte
e, em outros casos, retoma a abordagem clássica. Em ambos os casos, a função
retorna uma matriz de arquivos, mas, se houver suporte para a API File System Access,
cada objeto de arquivo também tem um FileSystemFileHandle
armazenado na
propriedade handle
. Assim, você tem a opção de serializar o identificador para disco.
const openFileOrFiles = async (multiple = false) => {
// Feature detection. The API needs to be supported
// and the app not run in an iframe.
const supportsFileSystemAccess =
"showOpenFilePicker" in window &&
(() => {
try {
return window.self === window.top;
} catch {
return false;
}
})();
// If the File System Access API is supported…
if (supportsFileSystemAccess) {
let fileOrFiles = undefined;
try {
// Show the file picker, optionally allowing multiple files.
const handles = await showOpenFilePicker({ multiple });
// Only one file is requested.
if (!multiple) {
// Add the `FileSystemFileHandle` as `.handle`.
fileOrFiles = await handles[0].getFile();
fileOrFiles.handle = handles[0];
} else {
fileOrFiles = await Promise.all(
handles.map(async (handle) => {
const file = await handle.getFile();
// Add the `FileSystemFileHandle` as `.handle`.
file.handle = handle;
return file;
})
);
}
} catch (err) {
// Fail silently if the user has simply canceled the dialog.
if (err.name !== 'AbortError') {
console.error(err.name, err.message);
}
}
return fileOrFiles;
}
// Fallback if the File System Access API is not supported.
return new Promise((resolve) => {
// Append a new `<input type="file" multiple? />` and hide it.
const input = document.createElement('input');
input.style.display = 'none';
input.type = 'file';
document.body.append(input);
if (multiple) {
input.multiple = true;
}
// The `change` event fires when the user interacts with the dialog.
input.addEventListener('change', () => {
// Remove the `<input type="file" multiple? />` again from the DOM.
input.remove();
// If no files were selected, return.
if (!input.files) {
return;
}
// Return all files or just one file.
resolve(multiple ? Array.from(input.files) : input.files[0]);
});
// Show the picker.
if ('showPicker' in HTMLInputElement.prototype) {
input.showPicker();
} else {
input.click();
}
});
};
Leia mais
Demonstração
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 open one or multiple files</title>
</head>
<body>
<h1>How to open one or multiple files</h1>
<button type="button">Open file</button>
<button class="multiple" type="button">Open files</button>
<pre></pre>
</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;
}
button {
margin: 1rem;
}
JS
const button = document.querySelector('button');
const buttonMultiple = document.querySelector('button.multiple');
const pre = document.querySelector('pre');
const openFileOrFiles = async (multiple = false) => {
// Feature detection. The API needs to be supported
// and the app not run in an iframe.
const supportsFileSystemAccess =
"showOpenFilePicker" in window &&
(() => {
try {
return window.self === window.top;
} catch {
return false;
}
})();
// If the File System Access API is supported…
if (supportsFileSystemAccess) {
let fileOrFiles = undefined;
try {
// Show the file picker, optionally allowing multiple files.
fileOrFiles = await showOpenFilePicker({ multiple });
if (!multiple) {
// Only one file is requested.
fileOrFiles = fileOrFiles[0];
}
} catch (err) {
// Fail silently if the user has simply canceled the dialog.
if (err.name !== 'AbortError') {
console.error(err.name, err.message);
}
}
return fileOrFiles;
}
// Fallback if the File System Access API is not supported.
return new Promise((resolve) => {
// Append a new `` and hide it.
const input = document.createElement('input');
input.style.display = 'none';
input.type = 'file';
document.body.append(input);
if (multiple) {
input.multiple = true;
}
// The `change` event fires when the user interacts with the dialog.
input.addEventListener('change', () => {
// Remove the `` again from the DOM.
input.remove();
// If no files were selected, return.
if (!input.files) {
return;
}
// Return all files or just one file.
resolve(multiple ? input.files : input.files[0]);
});
// Show the picker.
if ('showPicker' in HTMLInputElement.prototype) {
input.showPicker();
} else {
input.click();
}
});
};
button.addEventListener('click', async () => {
const file = await openFileOrFiles();
if (!file) {
return;
}
pre.textContent += `${file.name}\n`;
});
buttonMultiple.addEventListener('click', async () => {
const files = await openFileOrFiles(true);
if (!files) {
return;
}
Array.from(files).forEach((file) => (pre.textContent += `${file.name}\n`));
});