Crea archivos ZIP en el navegador con JSZip
Pasa los objetos File del navegador a JSZip, genera un
Blob ZIP y entrega ese Blob a un enlace de descarga. La página completa que
aparece a continuación permite elegir o soltar varios archivos locales y descargarlos como
archive.zip, con información de progreso y un límite de tamaño total de entrada.
Este ejemplo no sube los archivos seleccionados ni requiere un servidor de compresión. Carga JSZip desde una CDN, por lo que se necesita una conexión a internet para cargar la biblioteca. La compresión y su consumo de memoria se mantienen en el navegador; este enfoque es adecuado para conjuntos pequeños de archivos que ya están en el dispositivo del usuario.
Ejecuta el ejemplo completo en el navegador
Guarda este contenido como un nuevo index.html en una carpeta vacía y luego ábrelo
en Chrome de escritorio. No hay ningún paso de compilación ni servidor local. El script fija la
versión 3.10.2 de JSZip; este ejemplo se probó en Chrome 153 en macOS.
Elige archivos individuales con el selector o suéltalos dentro del conjunto de campos. Cada selección reemplaza el conjunto anterior; los nombres aparecen debajo del selector. No se incluye el recorrido de carpetas. Haz clic en Create ZIP, espera la solicitud de descarga y luego abre el ZIP desde las descargas del 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 una comprobación rápida, selecciona un archivo de texto y una imagen con nombres diferentes.
El ZIP descargado debe contener ambos en su raíz, con su contenido intacto. Genéralo de nuevo para
solicitar otra descarga. archive.zip es un nombre sugerido: el navegador controla
la ubicación de guardado y cualquier aviso para cambiar el nombre o reemplazar el archivo cuando
ese nombre ya existe. La página no fuerza la sobrescritura.
Añade archivos directamente e informa del progreso de generación
Tanto un campo de selección de archivos como la acción de soltar archivos proporcionan objetos
File mediante la API File. El
método file(name, data) de JSZip
los acepta directamente porque un File es un tipo de
Blob. No necesitas un contenedor FileReader
independiente ni una conversión a base64 para añadir archivos locales al archivo ZIP.
generateAsync()
devuelve una promesa que se resuelve con el archivo ZIP completo. Aquí produce un Blob y usa DEFLATE
con el nivel seis. El valor percent de la función de retorno describe la
generación del archivo ZIP, no el progreso de una descarga. Una vez que el Blob está listo, una URL
temporal lo pone a disposición del enlace; el temporizador libera esa URL después de la solicitud
de descarga.
«ZIP download requested» es deliberadamente distinto de «guardado». La
propiedad download del enlace
no confirma que se haya realizado una descarga. La configuración del navegador puede bloquearla,
o el usuario puede cancelar el diálogo de guardado. Revisa el panel de descargas si no aparece
ningún archivo.
Mantén activa una sola tarea de creación de ZIP
La variable de control busy se activa antes de que comience la compresión.
Mientras la promesa está pendiente, el conjunto de campos deshabilita el selector y el botón, y
selectFiles() ignora nuevos archivos soltados o eventos de cambio. La tarea conserva
su selección original, por lo que un segundo clic no puede iniciar otro archivo ZIP ni reemplazar
el mensaje de progreso. Tanto si tiene éxito como si falla, finally restablece
los controles. Para usar una selección diferente, espera a que termine la tarea actual y luego
selecciona o suelta los nuevos archivos.
JSZip actualiza una entrada existente cuando se vuelve a añadir el mismo nombre. La comprobación de duplicados evita que un archivo seleccionado reemplace a otro sin aviso, lo cual es importante cuando archivos de carpetas diferentes comparten el mismo nombre base. Cambia el nombre de uno antes de volver a intentarlo. Los nombres que solo difieren en mayúsculas y minúsculas aún pueden entrar en conflicto al extraerse en un sistema de archivos que no las distinga.
Si no se puede cargar el recurso de la CDN, la página te pide que compruebes la conexión. Si falla la lectura o la compresión, te pide que vuelvas a seleccionar los archivos. Una tarea fallida no solicita una descarga. Si la página debe funcionar sin conexión, sirve el paquete de la versión fijada de JSZip junto con tu propia página en lugar de depender de la CDN.
Manejo de archivos grandes y consideraciones de rendimiento
La comprobación de 100 MiB es un ejemplo de política de entrada, no una garantía de capacidad del
navegador. En JSZip,
generateAsync() mantiene el resultado completo en memoria,
y leer y comprimir los archivos de entrada también requiere memoria. Muchos archivos pequeños
también añaden sobrecarga. Elige un límite menor si los dispositivos de destino no pueden manejar
esa carga de trabajo con holgura.
DEFLATE puede reducir el tamaño del texto repetitivo, pero las imágenes y los videos ya comprimidos
pueden obtener poco beneficio. Si solo necesitas agrupar esos archivos, JSZip también admite
compression: 'STORE', que omite la compresión. Trasladar la compresión a un Web Worker puede
mantener esa tarea fuera del hilo principal de la página, pero no hace que
generateAsync({ type: 'blob' }) transmita su resultado directamente al disco. Las exportaciones
grandes necesitan una estrategia diferente de memoria y salida.
Compatibilidad con navegadores
El entorno verificado aquí es Chrome de escritorio en macOS. Otros navegadores necesitan la API
File, promesas, async/await, URL de Blob y soporte
para descargar desde esas URL. Conserva el selector de archivos para dispositivos que no permitan
arrastrarlos y prueba la selección, la recuperación de errores y las descargas reales en cada
navegador que quieras admitir. La compatibilidad de la biblioteca JSZip por sí sola no demuestra
que todo el flujo de descarga de la página funcione en esos navegadores.
