
Creas una lista de documentos para un cliente y el cliente pregunta: "¿puedo ver las páginas antes de descargarlas?". Y así, sin más, te haces cargo de una función de renderizado que no habías planificado. La misma petición aparece por todas partes: una tira de miniaturas bajo una bandeja de entrada, una tarjeta de vista previa pegada en un chat, una diapositiva extraída de una presentación y publicada en redes sociales. En todos los casos un PDF no se puede mostrar en línea, pero una imagen de sus páginas sí.
Una vez hecho ese cambio, cuatro decisiones determinan si el trabajo es utilizable: qué páginas, qué formato, con qué nitidez y cómo llegan los archivos al usuario. La mayoría de los ejemplos de conversión de PDF a imagen solo responden a la primera y te dejan descubrir el resto en producción.
Spire.PDF for JavaScript renderiza páginas en el navegador mediante WebAssembly y te entrega cada página como un flujo de imagen. Las cuatro decisiones son tuyas, y cada una consiste en un solo argumento o en cambiar una extensión de archivo. Los archivos entran y salen a través de un sistema de archivos virtual (VFS), así que no se sube nada a ningún sitio.
En este artículo aprenderás a:
- Renderizar todas las páginas de un PDF a PNG y descargar el conjunto como un ZIP
- Limitar la conversión a una sola página o a un rango de páginas
- Alternar entre PNG, JPEG y BMP según el uso que se le vaya a dar a la imagen
- Equilibrar la nitidez y el tamaño de archivo mediante los DPI
- Entregar el resultado como descarga, como archivos individuales o directamente en tu interfaz de React
- Corregir texto ilegible o en blanco causado por fuentes ausentes
Requisitos previos
Este tutorial supone un proyecto de React con Spire.PDF for JavaScript instalado y el módulo WASM inicializado. Para la configuración, consulta Integración de Spire.PDF for JavaScript en un proyecto de React.
Necesitarás:
- Un PDF cargado en el VFS
- El módulo WASM accesible en
window.wasmModule.spirepdf - El paquete
jszip, si quieres agrupar varias páginas en una sola descarga (npm install jszip)
Convertir todas las páginas a PNG
El caso del documento completo es el que usarás con más frecuencia: abrir el PDF, recorrer cada página, renderizar cada una a un flujo, escribir cada flujo en una carpeta del VFS, comprimir esa carpeta en un ZIP y entregarla al navegador como descarga.
import JSZip from "jszip";
function App() {
const convertToImage = async () => {
// Get the Spire.PDF WASM module
const pdfModule = window.wasmModule?.spirepdf;
// Check if the WASM module is ready
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// Load the PDF file and fonts into VFS
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'Flowers.pdf';
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);
// Create PdfDocument object and load the PDF document
let doc = new pdfModule.PdfDocument();
doc.LoadFromFile(inputFileName);
// Create an output directory to hold the converted images
let outputDirectoryName = "ImagesFolders/";
window.dotnetRuntime.Module.FS.mkdirTree(outputDirectoryName);
// Loop through each page and save it as an image
for (let i = 0; i < doc.Pages.Count; i++) {
const outputFileName = outputDirectoryName + "ConvertedImages_" + i + ".png";
let stream = doc.SaveAsImage({ pageIndex: i });
stream.Save(outputFileName);
stream.Dispose();
}
doc.Dispose();
// Read the converted files from VFS and trigger download
const zip = new JSZip();
let items = await window.dotnetRuntime.Module.FS.readdir(outputDirectoryName);
items = items.filter((item) => item !== "." && item !== "..");
for (const item of items) {
const itemPath = `${outputDirectoryName}/${item}`;
const fileData = await window.dotnetRuntime.Module.FS.readFile(itemPath);
zip.file(item, fileData);
}
// Convert the ZIP to a Blob and trigger the browser download
const zipBlob = await zip.generateAsync({ type: "blob" });
const url = URL.createObjectURL(zipBlob);
const a = document.createElement('a');
a.href = url;
a.download = 'ImagesFolders';
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Convert PDF To Image</h1>
<button onClick={convertToImage}>
Generate
</button>
</div>
);
}
export default App;
Cada página del PDF exportada como una imagen PNG mediante SaveAsImage y empaquetada en un archivo ZIP para su descarga

Qué hace el código:
-
doc.SaveAsImage({ pageIndex: i })renderiza una página y devuelve un flujo de imagen. Los índices de página empiezan en cero, por lo quepageIndex: 0es la primera página. -
stream.Save(outputFileName)escribe ese flujo en el VFS. La extensión que indicas aquí determina el formato; hablaremos de ello más abajo. -
stream.Dispose()libera el flujo antes de renderizar la página siguiente. Omitirlo en un documento largo desperdicia memoria sin ningún beneficio. - La secuencia
FS.mkdirTree→FS.readdir→FS.readFileexiste porque el módulo WASM escribe en su propio sistema de archivos, no en la carpeta de descargas del navegador. JSZip es lo que convierte esa carpeta en algo que el usuario puede guardar. - El renderizado es de solo lectura. El PDF de origen se abre, se lee y se libera; nunca se modifica.
Nota: Esto produce una imagen de cada página, que es lo que quieres para vistas previas, miniaturas y para compartir. Si en cambio quieres los archivos de imagen originales incrustados dentro del PDF —el gráfico o la foto reales en su resolución nativa, no una captura de la página en la que se encuentran—, esa es otra operación: consulta Extraer imágenes de un PDF en JavaScript (React).
Convertir solo páginas específicas
Renderizar un documento de 200 páginas para producir tres miniaturas es trabajo desperdiciado. Dado que el recorrido de páginas no es más que un bucle for sobre doc.Pages.Count, acotarlo es cuestión de cambiar los límites.
Una sola página: la portada o un anexo concreto:
// Render only the first page
const outputFileName = outputDirectoryName + "cover.png";
let stream = doc.SaveAsImage({ pageIndex: 0 });
stream.Save(outputFileName);
stream.Dispose();
Un rango contiguo: de la página 3 a la 5, usando los índices basados en cero 2 a 4:
const startPage = 2;
const endPage = 4;
for (let i = startPage; i <= endPage && i < doc.Pages.Count; i++) {
let stream = doc.SaveAsImage({ pageIndex: i });
stream.Save(outputDirectoryName + "page_" + (i + 1) + ".png");
stream.Dispose();
}
Un conjunto elegido a mano: páginas que no son consecutivas:
const pagesToRender = [0, 7, 12];
for (const pageIndex of pagesToRender) {
if (pageIndex >= doc.Pages.Count) continue;
let stream = doc.SaveAsImage({ pageIndex });
stream.Save(outputDirectoryName + "page_" + (pageIndex + 1) + ".png");
stream.Dispose();
}
La comprobación i < doc.Pages.Count importa cuando la lista de páginas proviene de la entrada del usuario y no de una constante codificada; un índice fuera de rango es un error en tiempo de ejecución que preferirás no publicar.
Fíjate en el (i + 1) de los nombres de archivo. Los índices basados en cero son correctos para la API e incorrectos para las personas; nombrar los archivos según el número de página que ve el usuario evita toda una clase de incidencias de soporte más adelante.
Artículo recomendado: cuando necesitas extraer del PDF los objetos de imagen originales en lugar de una página renderizada —el gráfico o la foto incrustados en su resolución nativa—, consulta Extraer imágenes de un PDF en JavaScript (React).
Elegir un formato de salida
El formato lo determina por completo la extensión que pasas a stream.Save. Nada más en el proceso cambia.
stream.Save(outputDirectoryName + "page_1.png"); // PNG
stream.Save(outputDirectoryName + "page_1.jpg"); // JPEG
stream.Save(outputDirectoryName + "page_1.bmp"); // BMP
| Formato | Extensión | Qué obtienes | Cuándo usarlo |
|---|---|---|---|
| PNG | .png |
Sin pérdida. Los bordes del texto y las líneas finas se mantienen nítidos. Archivos más grandes. | Páginas de documentos, vistas previas de la interfaz, cualquier cosa con texto o diagramas |
| JPEG | .jpg |
Con pérdida. Mucho más pequeños. Sin transparencia, y los artefactos de compresión se aprecian alrededor del texto pequeño. | Páginas fotográficas o escaneadas, archivos adjuntos de correo, entregas con limitaciones de tamaño |
| BMP | .bmp |
Sin comprimir. Muy grandes. | Flujos de trabajo heredados que exigen específicamente mapas de bits sin comprimir |
La regla práctica: si la página es principalmente texto, usa PNG. La compresión de JPEG está ajustada para contenido fotográfico y, en una página con mucho texto, produce manchas visibles alrededor de los bordes de los glifos que ningún ajuste de calidad elimina del todo. Si la página es una foto escaneada, JPEG reducirá drásticamente el tamaño del archivo con un coste visible mínimo.
Establecer la resolución (DPI)
SaveAsImage renderiza a 96 DPI de forma predeterminada, lo que coincide con una pantalla típica. Esa es la configuración adecuada para miniaturas y vistas previas en pantalla. No es suficiente cuando alguien amplía la imagen, imprime la página o introduce la imagen en un OCR.
Pasa dpiX y dpiY para cambiarla:
// Export each page as an image at 150 DPI
for (let i = 0; i < doc.Pages.Count; i++) {
let stream = doc.SaveAsImage({ pageIndex: i, dpiX: 150, dpiY: 150 });
stream.Save(outputDirectoryName + "highres_" + i + ".png");
stream.Dispose();
}
La resolución no es una mejora gratuita: la cantidad de píxeles escala con el cuadrado de los DPI, así que el coste sube rápidamente:
| DPI | Cantidad relativa de píxeles frente a 96 DPI | Comparable a | Razonable para |
|---|---|---|---|
| 96 (predeterminado) | 1× | Pantalla estándar | Miniaturas, vistas previas en línea, tiras de galería |
| 150 | ~2.4× | Pantalla Retina, zoom ligero | Archivos adjuntos de correo, vistas previas que el usuario puede ampliar, impresión de borradores |
| 300 | ~9.8× | Calidad de impresión | Salida para impresión, entrada para OCR, originales de archivo |
De esa tabla se deducen dos cosas. Primera: pasar de 96 a 300 multiplica el trabajo y el almacenamiento por aproximadamente diez, así que no recurras a 300 en un documento de 200 páginas a menos que alguien vaya realmente a imprimirlo. Segunda: dpiX y dpiY son argumentos separados por una razón; normalmente se establecen con el mismo valor, y establecerlos de forma distinta deformará la página.
Empaquetar o previsualizar las imágenes
Renderizar las páginas es la mitad del trabajo; la otra mitad es hacerlas llegar al usuario. Tres patrones cubren casi todo.
Agrupar todo en un solo ZIP: el enfoque del primer ejemplo. Es lo mejor cuando el usuario quiere una carpeta de archivos, y la única opción que escala a docenas de páginas sin saturar el navegador con peticiones de descarga.
Descargar una sola imagen directamente: no se necesita JSZip cuando solo hay un archivo:
const outputFileName = 'cover.png';
let stream = doc.SaveAsImage({ pageIndex: 0 });
stream.Save(outputFileName);
stream.Dispose();
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'image/png' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
Mantener las imágenes dentro de tu interfaz de React: sin ninguna descarga. Esta es la elección correcta para paneles de vista previa y tiras de miniaturas, donde las imágenes son temporales y una carpeta de descargas llena de archivos page_1.png solo sería un estorbo:
const renderPreviewStrip = async () => {
const pdfModule = window.wasmModule?.spirepdf;
if (!pdfModule) return;
await window.spire.FetchFileToVFS('Flowers.pdf', "", `${process.env.PUBLIC_URL}/data/`);
let doc = new pdfModule.PdfDocument();
doc.LoadFromFile('Flowers.pdf');
const previewUrls = [];
for (let i = 0; i < doc.Pages.Count; i++) {
const tempName = `preview_${i}.png`;
let stream = doc.SaveAsImage({ pageIndex: i, dpiX: 72, dpiY: 72 });
stream.Save(tempName);
stream.Dispose();
const fileArray = window.dotnetRuntime.Module.FS.readFile(tempName);
const blob = new Blob([fileArray], { type: 'image/png' });
previewUrls.push(URL.createObjectURL(blob));
}
doc.Dispose();
// previewUrls can now be rendered as <img src={url} /> in a thumbnail strip
return previewUrls;
};
Fíjate en dpiX: 72 en el caso de la vista previa: las miniaturas no necesitan resolución completa, y renderizarlas más pequeñas mantiene la tira ágil. Recuerda llamar a URL.revokeObjectURL(url) para cada URL cuando el componente se desmonte, o tendrás fugas de memoria en cada re-renderizado.
También te puede interesar: ¿Trabajas a partir de un conjunto de imágenes en lugar de un PDF, reuniendo escaneos o fotos en un solo documento? Eso es Convertir imágenes a PDF en JavaScript (React).
Corregir texto ilegible: cargar fuentes
Un PDF normalmente incorpora las fuentes que utiliza, pero no siempre, y cuando no lo hace, el renderizador tiene que sustituirlas por algo. En el navegador no hay una biblioteca de fuentes del sistema operativo a la que recurrir, así que una fuente ausente se convierte en espacios en blanco o en cuadros.
La solución es colocar el archivo de fuente donde el entorno de ejecución WASM espera encontrarlo antes de renderizar:
// Load a TrueType font into the VFS font directory before converting
await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
Merece la pena conocer ARIALUNI.TTF: cubre caracteres CJK comunes además del latino, lo que la convierte en la opción predeterminada más segura cuando tus usuarios suben documentos en idiomas que no has enumerado. Si solo trabajas con documentos en inglés, una fuente más pequeña mantiene la descarga más ligera.
Como esto se ejecuta antes de que se renderice cualquier página, basta con una comprobación de fuentes ausentes al principio de tu función de conversión; no necesitas volver a cargar las fuentes en cada página.
Preguntas frecuentes
¿Convertir un PDF a imágenes modifica el PDF original?
No. SaveAsImage renderiza páginas y devuelve nuevos datos de imagen; el documento de origen se abre en modo de solo lectura y se libera después. Tu PDF original no se modifica.
¿Cómo convierto solo la primera página?
Llama a SaveAsImage una sola vez con pageIndex: 0 en lugar de usar un bucle. Consulta Convertir solo páginas específicas.
¿Por qué mi resultado es más grande que el PDF original?
Porque una página renderizada almacena píxeles, mientras que un PDF almacena instrucciones de dibujo. Una página de PDF con mucho texto suele ocupar unos pocos kilobytes; un PNG a 300 DPI de esa misma página puede ocupar megabytes. Baja a 96–150 DPI o cambia a JPEG si el tamaño importa más que la fidelidad.
¿Puedo mostrar las imágenes en mi aplicación en lugar de descargarlas?
Sí: crea URL de objeto a partir de los datos de imagen y úsalas como orígenes de <img>. Consulta Empaquetar o previsualizar las imágenes. Solo recuerda revocar las URL cuando termines.
¿Un documento muy largo causará problemas?
Las páginas se renderizan de una en una dentro del bucle, así que el módulo WASM mantiene aproximadamente una imagen de página a la vez en lugar de todo el documento. Para archivos extremadamente largos, convierte por fragmentos y ve añadiéndolos al archivo comprimido sobre la marcha en lugar de mantener todas las imágenes en un array.
¿Cuál es la diferencia entre convertir una página en una imagen y extraer imágenes de un PDF?
La conversión renderiza toda la página —texto, diseño, imágenes, fondos— como una sola imagen plana. La extracción saca los objetos de imagen originales incrustados en el documento en su resolución nativa. Usa la conversión para vistas previas y para compartir; usa la extracción cuando necesites el gráfico original en sí.
¿Adónde van realmente los archivos convertidos?
Al sistema de archivos virtual de WASM, no a tu servidor ni al disco del usuario. Nada sale del navegador; la única forma de que los archivos lleguen al usuario es el paso de descarga o de URL de objeto que tú mismo escribes.
Ver también
¿Buscas la dirección inversa, convertir imágenes de nuevo en un PDF? Eso es Convertir imágenes a PDF en JavaScript (React). El resto del flujo de trabajo con páginas PDF en React: