Criar arquivos ZIP no navegador com JSZip
Passe os objetos File do navegador para o JSZip, gere um Blob de ZIP e entregue esse Blob a
um link de download. A página completa abaixo permite que os usuários escolham ou soltem vários
arquivos locais e os baixem como archive.zip, com indicação de progresso e um limite para o tamanho
total de entrada.
Este exemplo não faz upload dos arquivos selecionados nem exige um servidor de compactação. Ele carrega o JSZip de uma CDN, então é preciso uma conexão com a internet para carregar a biblioteca. O trabalho de compactação e seu custo de memória ficam no navegador; essa abordagem é adequada para conjuntos modestos de arquivos que já estão no dispositivo do usuário.
Executar o exemplo completo no navegador
Salve isto como um novo index.html em uma pasta vazia e depois abra-o no Chrome para desktop. Não há
etapa de build nem servidor local. O script fixa o JSZip 3.10.2; este exemplo foi testado no
Chrome 153 no macOS.
Escolha arquivos individuais com o seletor ou solte-os dentro do fieldset. Cada seleção substitui o conjunto anterior, cujos nomes aparecem abaixo do seletor. A navegação por pastas não está incluída. Clique em Create ZIP, aguarde a solicitação de download e depois abra o ZIP nos downloads do seu navegador.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Create a ZIP with JSZip</title>
</head>
<body>
<h1>Create a ZIP</h1>
<fieldset id="dropZone">
<legend>Choose files or drop them here</legend>
<label for="fileInput">Files to archive</label>
<input type="file" id="fileInput" multiple />
<p id="selection">No files selected.</p>
<button type="button" id="zipButton">Create ZIP</button>
</fieldset>
<p id="progress" role="status">Select files totaling at most 100 MiB.</p>
<script src="https://cdn.jsdelivr.net/npm/jszip@3.10.2/dist/jszip.min.js"></script>
<script>
const fileInput = document.getElementById('fileInput')
const dropZone = document.getElementById('dropZone')
const selection = document.getElementById('selection')
const progress = document.getElementById('progress')
let selectedFiles = []
let busy = false
function selectFiles(files) {
if (busy) return
selectedFiles = Array.from(files)
// The names below remain visible; clearing the picker allows reselection.
fileInput.value = ''
selection.textContent = selectedFiles.length
? `Selected files: ${selectedFiles.map((file) => file.name).join(', ')}`
: 'No files selected.'
progress.textContent = 'Ready to create ZIP.'
}
fileInput.addEventListener('change', () => selectFiles(fileInput.files))
dropZone.addEventListener('dragover', (event) => {
event.preventDefault()
event.dataTransfer.dropEffect = busy ? 'none' : 'copy'
})
dropZone.addEventListener('drop', (event) => {
event.preventDefault()
selectFiles(event.dataTransfer.files)
})
async function createZip() {
if (busy) return
const files = selectedFiles.slice()
if (files.length === 0) {
progress.textContent = 'Select or drop files first.'
return
}
if (typeof JSZip === 'undefined') {
progress.textContent = 'JSZip could not load. Check your connection and reload.'
return
}
const totalSize = files.reduce((sum, file) => sum + file.size, 0)
if (totalSize > 100 * 1024 * 1024) {
progress.textContent = 'Select at most 100 MiB of files.'
return
}
const names = new Set()
for (const file of files) {
if (names.has(file.name)) {
progress.textContent = `Duplicate filename: ${file.name}. Rename it first.`
return
}
names.add(file.name)
}
busy = true
dropZone.disabled = true
progress.textContent = 'Generating ZIP…'
try {
const zip = new JSZip()
for (const file of files) zip.file(file.name, file)
const blob = await zip.generateAsync(
{
type: 'blob',
compression: 'DEFLATE',
compressionOptions: { level: 6 },
},
({ percent }) => {
progress.textContent = `Generating ZIP: ${Math.round(percent)}%`
},
)
const url = URL.createObjectURL(blob)
// Leave time for the browser to consume the URL before releasing it.
setTimeout(() => URL.revokeObjectURL(url), 60_000)
const link = document.createElement('a')
link.href = url
link.download = 'archive.zip'
document.body.appendChild(link)
link.click()
link.remove()
progress.textContent = 'ZIP download requested. Check your browser’s downloads.'
} catch {
progress.textContent = 'Could not create ZIP. Reselect the files and try again.'
} finally {
busy = false
dropZone.disabled = false
}
}
document.getElementById('zipButton').addEventListener('click', createZip)
</script>
</body>
</html>
Para uma verificação rápida, selecione um arquivo de texto e uma imagem com nomes diferentes. O ZIP
baixado deve conter os dois na raiz, com o conteúdo inalterado. Gere-o novamente para solicitar
outro download. archive.zip é um nome sugerido: o navegador controla o local de salvamento e qualquer
pergunta sobre renomear ou substituir quando esse nome já existe. A página não força a substituição.
Adicionar arquivos diretamente e informar o progresso da geração
Tanto um elemento input de arquivo quanto o recurso de soltar arquivos fornecem objetos File por
meio da File API. O método file(name, data)
do JSZip os aceita diretamente, porque um File é um tipo de Blob. Você não precisa de um wrapper
FileReader separado nem de uma conversão para base64 para adicionar arquivos locais ao ZIP.
generateAsync()
retorna uma promise para o arquivo ZIP completo. Aqui, ele produz um Blob e usa DEFLATE no nível
seis. O percent do callback descreve a geração do ZIP, não o progresso de um download. Quando o
Blob fica pronto, uma URL temporária o disponibiliza para o link; o timer libera essa URL após a
solicitação de download.
“ZIP download requested” é deliberadamente diferente de “salvo”. A
propriedade download
do link não confirma que um download aconteceu. As configurações do navegador podem bloqueá-lo, ou
o usuário pode cancelar a caixa de diálogo de salvamento. Confira o painel de downloads se nenhum
arquivo aparecer.
Manter apenas uma tarefa de ZIP ativa
A proteção busy é ativada antes do início da compactação. Enquanto a promise está pendente, o
fieldset desativa o seletor e o botão, e selectFiles() ignora novos eventos de soltar ou de alteração. A
tarefa mantém a seleção original, então um segundo clique não consegue iniciar outro ZIP nem
substituir a mensagem de progresso. Após sucesso ou falha, finally restaura os controles. Para usar
outra seleção, aguarde a tarefa atual terminar e depois selecione ou solte os novos arquivos.
O JSZip atualiza uma entrada existente quando o mesmo nome é adicionado novamente. A verificação de duplicatas impede que um arquivo selecionado substitua outro silenciosamente, o que importa quando arquivos de pastas diferentes têm o mesmo nome base. Renomeie um deles antes de tentar novamente. Nomes que diferem apenas em maiúsculas e minúsculas ainda podem colidir quando extraídos em um sistema de arquivos que não diferencia maiúsculas de minúsculas.
Se a CDN não carregar, a página pede que você verifique a conexão. Se a leitura ou a compactação falhar, ela pede que você selecione os arquivos novamente. Uma tarefa com falha não solicita download. Para uma página que precisa funcionar offline, sirva o bundle fixado do JSZip junto com a sua própria página em vez de depender da CDN.
Tratamento de arquivos grandes e considerações de desempenho
A verificação de 100 MiB é um exemplo de política de entrada, não uma garantia de capacidade do
navegador. No JSZip,
generateAsync() mantém o resultado completo na memória,
e ler e compactar as entradas também exige memória. Muitos arquivos pequenos também adicionam
overhead. Escolha um limite menor se os seus dispositivos-alvo não conseguirem lidar
confortavelmente com essa carga de trabalho.
O DEFLATE pode reduzir textos repetitivos, mas imagens e vídeos já compactados podem ter pouco
benefício. Se você só precisa agrupar esses arquivos, o JSZip também oferece suporte a compression: 'STORE', que
pula a compactação. Mover a compactação para um Web Worker pode manter esse trabalho fora da thread
principal da página, mas não faz o generateAsync({ type: 'blob' }) transmitir o resultado em streaming para o disco.
Exportações grandes precisam de outra estratégia de memória e de saída.
Compatibilidade com navegadores
O ambiente verificado aqui é o Chrome para desktop no macOS. Outros navegadores precisam da File
API, de promises, de async/await, de URLs de Blob e de suporte a downloads a partir dessas URLs.
Mantenha o seletor de arquivos para dispositivos sem suporte a arrastar arquivos e teste a seleção,
a recuperação de erros e os downloads reais em cada navegador que você pretende suportar. O suporte
da biblioteca JSZip por si só não comprova que todo o fluxo de download da página funcione nesses
navegadores.
