SVG to PNG JavaScript: Complete Implementation Guide
Master SVG to PNG conversion in JavaScript with Canvas API deep dive, 15+ working examples, performance optimization, and production-ready code implementations.
Converting SVG to PNG using JavaScript is essential for web applications that need to generate downloadable images, create thumbnails, or process vector graphics client-side. Whether you're working with graphics from our AI SVG generator or custom designs, this comprehensive guide covers everything from basic Canvas API usage to advanced techniques for production applications.
🚀 Need instant SVG to PNG conversion?
Skip the coding and use our free SVG to PNG converter - paste your SVG code and get high-quality PNG downloads instantly.
Understanding Canvas API for SVG Conversion
The HTML5 Canvas API provides the foundation for client-side SVG to PNG conversion. The process involves creating an Image object from SVG data, drawing it to a Canvas element, and extracting the raster data as a PNG. For complex SVG editing before conversion, try our SVG code editor to fine-tune your graphics.
Core Conversion Mechanism
The conversion process follows these steps:
- Create a Blob from SVG string with proper MIME type
- Generate an object URL from the Blob
- Load the SVG into an Image element
- Draw the Image to a Canvas context
- Extract PNG data using toDataURL() or toBlob()
Basic SVG to PNG Conversion
Simple Function Implementation
function svgToPng(svgElement, width, height, callback) {
// Get SVG data
const svgData = new XMLSerializer().serializeToString(svgElement);
// Create canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Set canvas size
canvas.width = width;
canvas.height = height;
// Create image from SVG
const img = new Image();
img.onload = function() {
// Clear canvas and draw image
ctx.clearRect(0, 0, width, height);
ctx.drawImage(img, 0, 0, width, height);
// Convert to PNG
const pngDataUrl = canvas.toDataURL('image/png');
callback(pngDataUrl);
};
// Create blob and object URL
const svgBlob = new Blob([svgData], {
type: 'image/svg+xml;charset=utf-8'
});
const url = URL.createObjectURL(svgBlob);
img.src = url;
// Clean up after loading
img.onload = function() {
ctx.clearRect(0, 0, width, height);
ctx.drawImage(img, 0, 0, width, height);
const pngDataUrl = canvas.toDataURL('image/png');
URL.revokeObjectURL(url); // Important: clean up memory
callback(pngDataUrl);
};
}
// Usage example
const svgElement = document.getElementById('my-svg');
svgToPng(svgElement, 800, 600, function(pngDataUrl) {
const downloadLink = document.createElement('a');
downloadLink.href = pngDataUrl;
downloadLink.download = 'converted.png';
downloadLink.click();
});
Promise-Based Implementation
function svgToPngAsync(svgElement, options = {}) {
const defaultOptions = {
width: 800,
height: 600,
backgroundColor: 'transparent',
scale: 1
};
const config = { ...defaultOptions, ...options };
return new Promise((resolve, reject) => {
try {
// Serialize SVG
const svgData = new XMLSerializer().serializeToString(svgElement);
// Create canvas with scaling
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = config.width * config.scale;
canvas.height = config.height * config.scale;
// Scale context for high-DPI displays
if (config.scale !== 1) {
ctx.scale(config.scale, config.scale);
}
const img = new Image();
img.onload = () => {
try {
// Set background color if specified
if (config.backgroundColor !== 'transparent') {
ctx.fillStyle = config.backgroundColor;
ctx.fillRect(0, 0, config.width, config.height);
}
// Enable high-quality rendering
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
// Draw the SVG
ctx.drawImage(img, 0, 0, config.width, config.height);
// Convert to PNG
canvas.toBlob((blob) => {
resolve({
blob: blob,
dataUrl: canvas.toDataURL('image/png'),
width: canvas.width,
height: canvas.height
});
}, 'image/png');
} catch (error) {
reject(new Error('Canvas rendering failed: ' + error.message));
}
};
img.onerror = () => {
reject(new Error('Failed to load SVG image'));
};
// Create object URL from SVG
const svgBlob = new Blob([svgData], {
type: 'image/svg+xml;charset=utf-8'
});
const url = URL.createObjectURL(svgBlob);
img.src = url;
// Clean up URL after a delay
setTimeout(() => URL.revokeObjectURL(url), 1000);
} catch (error) {
reject(new Error('SVG serialization failed: ' + error.message));
}
});
}
// Usage with async/await
async function convertAndDownload() {
try {
const svgElement = document.querySelector('#my-svg');
const result = await svgToPngAsync(svgElement, {
width: 1200,
height: 800,
backgroundColor: '#ffffff',
scale: 2 // For retina displays
});
// Create download link
const url = URL.createObjectURL(result.blob);
const a = document.createElement('a');
a.href = url;
a.download = 'high-quality.png';
a.click();
URL.revokeObjectURL(url);
} catch (error) {
console.error('Conversion failed:', error);
}
}
Advanced SVG to PNG Converter Class
For production applications, a comprehensive converter class provides better error handling, configuration options, and reusability.
class AdvancedSvgToPngConverter {
constructor(defaultOptions = {}) {
this.defaultOptions = {
width: 800,
height: 600,
backgroundColor: 'transparent',
scale: 1,
quality: 0.92,
format: 'png',
imageSmoothingEnabled: true,
imageSmoothingQuality: 'high'
};
this.options = { ...this.defaultOptions, ...defaultOptions };
}
// Convert SVG element to PNG
async convertElement(svgElement, customOptions = {}) {
const options = { ...this.options, ...customOptions };
if (!svgElement || svgElement.tagName.toLowerCase() !== 'svg') {
throw new Error('Invalid SVG element provided');
}
const svgString = new XMLSerializer().serializeToString(svgElement);
return this.convertString(svgString, options);
}
// Convert SVG string to PNG
async convertString(svgString, customOptions = {}) {
const options = { ...this.options, ...customOptions };
// Validate and clean SVG string
const cleanedSvg = this.preprocessSvg(svgString);
return new Promise((resolve, reject) => {
const canvas = this.createCanvas(options);
const ctx = canvas.getContext('2d');
this.setupCanvasContext(ctx, options);
const img = new Image();
img.onload = () => {
try {
this.renderToCanvas(ctx, img, options);
this.exportFromCanvas(canvas, options)
.then(resolve)
.catch(reject);
} catch (error) {
reject(new Error('Rendering failed: ' + error.message));
}
};
img.onerror = () => {
reject(new Error('Failed to load SVG into image element'));
};
this.loadSvgIntoImage(img, cleanedSvg);
});
}
// Preprocess SVG for better compatibility
preprocessSvg(svgString) {
let processed = svgString;
// Ensure SVG has proper namespace
if (!processed.includes('xmlns=')) {
processed = processed.replace('<svg', '<svg xmlns="http://www.w3.org/2000/svg"');
}
// Remove potential security risks
processed = processed.replace(/<script[^>]*>.*?<\/script>/gi, '');
processed = processed.replace(/on\w+="[^"]*"/gi, '');
// Ensure proper encoding
processed = processed.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, '');
return processed;
}
// Create properly sized canvas
createCanvas(options) {
const canvas = document.createElement('canvas');
canvas.width = options.width * options.scale;
canvas.height = options.height * options.scale;
return canvas;
}
// Setup canvas context with optimal settings
setupCanvasContext(ctx, options) {
// Scale for high-DPI displays
if (options.scale !== 1) {
ctx.scale(options.scale, options.scale);
}
// Configure image smoothing
ctx.imageSmoothingEnabled = options.imageSmoothingEnabled;
if (ctx.imageSmoothingQuality) {
ctx.imageSmoothingQuality = options.imageSmoothingQuality;
}
// Set background if specified
if (options.backgroundColor !== 'transparent') {
ctx.fillStyle = options.backgroundColor;
ctx.fillRect(0, 0, options.width, options.height);
}
}
// Render image to canvas
renderToCanvas(ctx, img, options) {
ctx.drawImage(img, 0, 0, options.width, options.height);
}
// Export canvas content
async exportFromCanvas(canvas, options) {
const result = {
width: canvas.width,
height: canvas.height,
format: options.format
};
if (options.format === 'blob') {
return new Promise((resolve, reject) => {
canvas.toBlob((blob) => {
if (blob) {
result.blob = blob;
resolve(result);
} else {
reject(new Error('Failed to create blob'));
}
}, 'image/png');
});
} else {
result.dataUrl = canvas.toDataURL('image/png', options.quality);
return result;
}
}
// Load SVG into image element
loadSvgIntoImage(img, svgString) {
const svgBlob = new Blob([svgString], {
type: 'image/svg+xml;charset=utf-8'
});
const url = URL.createObjectURL(svgBlob);
img.src = url;
// Clean up URL after loading
const cleanup = () => {
setTimeout(() => URL.revokeObjectURL(url), 100);
};
img.addEventListener('load', cleanup, { once: true });
img.addEventListener('error', cleanup, { once: true });
}
// Utility method to download result
async download(svgInput, filename = 'converted.png', options = {}) {
try {
const downloadOptions = { ...options, format: 'blob' };
let result;
if (typeof svgInput === 'string') {
result = await this.convertString(svgInput, downloadOptions);
} else {
result = await this.convertElement(svgInput, downloadOptions);
}
const url = URL.createObjectURL(result.blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
return result;
} catch (error) {
throw new Error('Download failed: ' + error.message);
}
}
// Batch conversion method
async convertBatch(svgInputs, options = {}) {
const results = [];
const errors = [];
for (let i = 0; i < svgInputs.length; i++) {
try {
const input = svgInputs[i];
let result;
if (typeof input === 'string') {
result = await this.convertString(input, options);
} else if (input.element) {
result = await this.convertElement(input.element, input.options || options);
} else {
result = await this.convertElement(input, options);
}
results.push({
index: i,
success: true,
result: result
});
} catch (error) {
errors.push({
index: i,
error: error.message
});
results.push({
index: i,
success: false,
error: error.message
});
}
}
return {
results: results,
errors: errors,
successCount: results.filter(r => r.success).length,
errorCount: errors.length
};
}
}
// Usage examples
const converter = new AdvancedSvgToPngConverter({
width: 1000,
height: 800,
scale: 2,
backgroundColor: '#ffffff'
});
// Convert single SVG element
const svgElement = document.querySelector('#my-svg');
converter.convertElement(svgElement)
.then(result => {
console.log('Conversion successful:', result);
// Use result.dataUrl or result.blob
})
.catch(error => {
console.error('Conversion failed:', error);
});
// Convert and download
converter.download(svgElement, 'my-graphic.png', {
width: 1200,
height: 900,
backgroundColor: '#f0f0f0'
});
// Batch conversion
const svgElements = document.querySelectorAll('.convert-svg');
converter.convertBatch(Array.from(svgElements))
.then(batchResult => {
console.log(`Converted ${batchResult.successCount} of ${svgElements.length} SVGs`);
batchResult.results.forEach((result, index) => {
if (result.success) {
// Process successful conversion
console.log(`SVG ${index} converted successfully`);
}
});
});
Handling Complex SVGs
External Resources and Fonts
class SvgProcessor {
// Handle SVGs with external resources
static async processExternalResources(svgString) {
let processedSvg = svgString;
// Find external stylesheets
const linkRegex = /<link[^>]+href=["']([^"']+.css)["'][^>]*>/g;
const stylePromises = [];
let match;
while ((match = linkRegex.exec(svgString)) !== null) {
const cssUrl = match[1];
stylePromises.push(this.fetchAndInlineCSS(cssUrl));
}
// Wait for all CSS to be fetched
const cssContents = await Promise.allSettled(stylePromises);
// Inline CSS
cssContents.forEach((result, index) => {
if (result.status === 'fulfilled') {
const inlineStyle = `<style>${result.value}</style>`;
processedSvg = processedSvg.replace(linkRegex, inlineStyle);
}
});
// Handle web fonts
processedSvg = await this.processWebFonts(processedSvg);
return processedSvg;
}
static async fetchAndInlineCSS(url) {
try {
const response = await fetch(url);
return await response.text();
} catch (error) {
console.warn(`Failed to fetch CSS: ${url}`, error);
return '';
}
}
static async processWebFonts(svgString) {
// Find Google Fonts or other web font URLs
const fontRegex = /@import\s+url\(["']([^"']+)["']\)/g;
let match;
while ((match = fontRegex.exec(svgString)) !== null) {
const fontUrl = match[1];
try {
const fontCSS = await fetch(fontUrl).then(r => r.text());
svgString = svgString.replace(match[0], fontCSS);
} catch (error) {
console.warn(`Failed to load font: ${fontUrl}`);
}
}
return svgString;
}
}
// Enhanced converter with resource processing
class EnhancedSvgToPngConverter extends AdvancedSvgToPngConverter {
async convertString(svgString, customOptions = {}) {
const options = { ...this.options, ...customOptions };
// Process external resources if enabled
if (options.processExternalResources) {
svgString = await SvgProcessor.processExternalResources(svgString);
}
return super.convertString(svgString, options);
}
}
// Usage with external resource processing
const enhancedConverter = new EnhancedSvgToPngConverter();
enhancedConverter.convertString(complexSvgWithFonts, {
processExternalResources: true,
width: 1200,
height: 800
}).then(result => {
console.log('Complex SVG converted successfully');
});
Animation Frame Capture
class AnimatedSvgToPngConverter {
constructor() {
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d');
}
// Capture specific frame from animated SVG
async captureFrame(svgElement, timeMs = 0, options = {}) {
const defaultOptions = {
width: 800,
height: 600,
backgroundColor: 'transparent'
};
const config = { ...defaultOptions, ...options };
return new Promise((resolve, reject) => {
// Clone SVG to avoid modifying original
const svgClone = svgElement.cloneNode(true);
// Pause animations at specific time
if (svgClone.pauseAnimations) {
svgClone.pauseAnimations();
svgClone.setCurrentTime(timeMs / 1000);
}
// Convert paused SVG to PNG
this.convertElementAtTime(svgClone, config)
.then(resolve)
.catch(reject);
});
}
async convertElementAtTime(svgElement, options) {
const svgData = new XMLSerializer().serializeToString(svgElement);
this.canvas.width = options.width;
this.canvas.height = options.height;
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
if (options.backgroundColor !== 'transparent') {
this.ctx.fillStyle = options.backgroundColor;
this.ctx.fillRect(0, 0, options.width, options.height);
}
this.ctx.drawImage(img, 0, 0, options.width, options.height);
const dataUrl = this.canvas.toDataURL('image/png');
resolve(dataUrl);
};
img.onerror = () => reject(new Error('Failed to load SVG frame'));
const svgBlob = new Blob([svgData], {
type: 'image/svg+xml;charset=utf-8'
});
img.src = URL.createObjectURL(svgBlob);
});
}
// Generate multiple frames from animation (for SVGs created with animation tools)
async generateFrames(svgElement, frameCount = 10, duration = 1000) {
const frames = [];
const interval = duration / frameCount;
for (let i = 0; i < frameCount; i++) {
const timeMs = i * interval;
try {
const frameData = await this.captureFrame(svgElement, timeMs);
frames.push({
time: timeMs,
dataUrl: frameData
});
} catch (error) {
console.warn(`Failed to capture frame at ${timeMs}ms:`, error);
}
}
return frames;
}
}
// Usage for animated SVGs (create animations with our animation tool)
const animConverter = new AnimatedSvgToPngConverter();
const animatedSvg = document.querySelector('#animated-svg');
// Capture frame at 500ms
animConverter.captureFrame(animatedSvg, 500, {
width: 1000,
height: 600
}).then(frameData => {
// Use the captured frame
const img = document.createElement('img');
img.src = frameData;
document.body.appendChild(img);
});
// Generate 20 frames over 2 seconds
animConverter.generateFrames(animatedSvg, 20, 2000)
.then(frames => {
console.log(`Generated ${frames.length} frames`);
frames.forEach((frame, index) => {
// Process each frame
console.log(`Frame ${index} at ${frame.time}ms`);
});
});
Performance Optimization Techniques
Web Workers for Heavy Processing
// worker-svg-converter.js - Web Worker file
self.onmessage = function(e) {
const { svgString, options, id } = e.data;
try {
// Create OffscreenCanvas for worker thread
const canvas = new OffscreenCanvas(options.width, options.height);
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = function() {
if (options.backgroundColor !== 'transparent') {
ctx.fillStyle = options.backgroundColor;
ctx.fillRect(0, 0, options.width, options.height);
}
ctx.drawImage(img, 0, 0, options.width, options.height);
// Convert to blob
canvas.convertToBlob().then(blob => {
self.postMessage({
id: id,
success: true,
blob: blob
});
});
};
img.onerror = function() {
self.postMessage({
id: id,
success: false,
error: 'Failed to load image in worker'
});
};
const svgBlob = new Blob([svgString], {
type: 'image/svg+xml;charset=utf-8'
});
img.src = URL.createObjectURL(svgBlob);
} catch (error) {
self.postMessage({
id: id,
success: false,
error: error.message
});
}
};
// Main thread usage
class WorkerSvgConverter {
constructor() {
this.worker = new Worker('worker-svg-converter.js');
this.pendingConversions = new Map();
this.setupWorkerListeners();
}
setupWorkerListeners() {
this.worker.onmessage = (e) => {
const { id, success, blob, error } = e.data;
const pending = this.pendingConversions.get(id);
if (pending) {
if (success) {
pending.resolve(blob);
} else {
pending.reject(new Error(error));
}
this.pendingConversions.delete(id);
}
};
}
async convert(svgString, options = {}) {
const id = Date.now() + Math.random();
return new Promise((resolve, reject) => {
this.pendingConversions.set(id, { resolve, reject });
this.worker.postMessage({
svgString: svgString,
options: options,
id: id
});
// Timeout after 30 seconds
setTimeout(() => {
if (this.pendingConversions.has(id)) {
this.pendingConversions.delete(id);
reject(new Error('Conversion timeout'));
}
}, 30000);
});
}
terminate() {
this.worker.terminate();
this.pendingConversions.clear();
}
}
// Usage
const workerConverter = new WorkerSvgConverter();
workerConverter.convert(largeSvgString, {
width: 2000,
height: 1500,
backgroundColor: '#ffffff'
}).then(blob => {
console.log('Worker conversion completed:', blob);
}).catch(error => {
console.error('Worker conversion failed:', error);
});
Memory Management and Cleanup
class MemoryEfficientConverter {
constructor() {
this.canvasPool = [];
this.maxPoolSize = 5;
this.activeConversions = 0;
this.maxConcurrent = 3;
}
// Get canvas from pool or create new one
getCanvas(width, height) {
let canvas = this.canvasPool.pop();
if (!canvas) {
canvas = document.createElement('canvas');
}
canvas.width = width;
canvas.height = height;
return canvas;
}
// Return canvas to pool
returnCanvas(canvas) {
if (this.canvasPool.length < this.maxPoolSize) {
// Clear canvas for reuse
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
this.canvasPool.push(canvas);
}
}
// Convert with memory management
async convert(svgString, options = {}) {
// Limit concurrent conversions
if (this.activeConversions >= this.maxConcurrent) {
await this.waitForSlot();
}
this.activeConversions++;
try {
const result = await this.performConversion(svgString, options);
return result;
} finally {
this.activeConversions--;
}
}
async performConversion(svgString, options) {
const canvas = this.getCanvas(options.width || 800, options.height || 600);
const ctx = canvas.getContext('2d');
try {
const result = await new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
try {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
canvas.toBlob((blob) => {
resolve({
blob: blob,
width: canvas.width,
height: canvas.height
});
}, 'image/png');
} catch (error) {
reject(error);
}
};
img.onerror = () => reject(new Error('Image load failed'));
// Create object URL with automatic cleanup
const blob = new Blob([svgString], {
type: 'image/svg+xml;charset=utf-8'
});
const url = URL.createObjectURL(blob);
img.src = url;
// Clean up URL after loading
const cleanup = () => {
setTimeout(() => URL.revokeObjectURL(url), 100);
};
img.addEventListener('load', cleanup, { once: true });
img.addEventListener('error', cleanup, { once: true });
});
return result;
} finally {
this.returnCanvas(canvas);
}
}
async waitForSlot() {
return new Promise((resolve) => {
const checkSlot = () => {
if (this.activeConversions < this.maxConcurrent) {
resolve();
} else {
setTimeout(checkSlot, 50);
}
};
checkSlot();
});
}
// Clean up resources
destroy() {
this.canvasPool = [];
this.activeConversions = 0;
}
}
// Usage
const efficientConverter = new MemoryEfficientConverter();
// Convert multiple SVGs efficiently
const svgStrings = [/* array of SVG strings */];
Promise.all(
svgStrings.map(svg =>
efficientConverter.convert(svg, { width: 800, height: 600 })
)
).then(results => {
console.log(`Converted ${results.length} SVGs efficiently`);
}).catch(error => {
console.error('Batch conversion failed:', error);
});
Real-World Implementation Examples
React Component for SVG to PNG Conversion
import React, { useState, useRef, useCallback } from 'react';
const SvgToPngConverter = ({ svgContent, onConvert, className }) => {
const [isConverting, setIsConverting] = useState(false);
const [result, setResult] = useState(null);
const canvasRef = useRef(null);
const convertSvg = useCallback(async (options = {}) => {
if (!svgContent) return;
setIsConverting(true);
try {
const defaultOptions = {
width: 800,
height: 600,
backgroundColor: 'transparent',
scale: 1,
quality: 0.92
};
const config = { ...defaultOptions, ...options };
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
canvas.width = config.width * config.scale;
canvas.height = config.height * config.scale;
if (config.scale !== 1) {
ctx.scale(config.scale, config.scale);
}
const conversionResult = await new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
try {
if (config.backgroundColor !== 'transparent') {
ctx.fillStyle = config.backgroundColor;
ctx.fillRect(0, 0, config.width, config.height);
}
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
ctx.drawImage(img, 0, 0, config.width, config.height);
canvas.toBlob((blob) => {
resolve({
blob: blob,
dataUrl: canvas.toDataURL('image/png', config.quality),
width: canvas.width,
height: canvas.height
});
}, 'image/png', config.quality);
} catch (error) {
reject(error);
}
};
img.onerror = () => reject(new Error('Failed to load SVG'));
const svgBlob = new Blob([svgContent], {
type: 'image/svg+xml;charset=utf-8'
});
const url = URL.createObjectURL(svgBlob);
img.src = url;
setTimeout(() => URL.revokeObjectURL(url), 1000);
});
setResult(conversionResult);
if (onConvert) {
onConvert(conversionResult);
}
} catch (error) {
console.error('Conversion failed:', error);
setResult({ error: error.message });
} finally {
setIsConverting(false);
}
}, [svgContent, onConvert]);
const downloadPng = useCallback(() => {
if (!result || !result.blob) return;
const url = URL.createObjectURL(result.blob);
const a = document.createElement('a');
a.href = url;
a.download = 'converted.png';
a.click();
URL.revokeObjectURL(url);
}, [result]);
return (
<div className={className}>
<canvas
ref={canvasRef}
style={{ display: 'none' }}
/>
<div className="controls">
<button
onClick={() => convertSvg()}
disabled={isConverting || !svgContent}
className="convert-btn"
>
{isConverting ? 'Converting...' : 'Convert to PNG'}
</button>
<button
onClick={() => convertSvg({ scale: 2, width: 1600, height: 1200 })}
disabled={isConverting || !svgContent}
className="convert-hd-btn"
>
Convert HD (2x)
</button>
</div>
{result && !result.error && (
<div className="result">
<img src={result.dataUrl} alt="Converted PNG" className="preview" />
<button onClick={downloadPng} className="download-btn">
Download PNG
</button>
<div className="info">
Dimensions: {result.width} × {result.height}px
</div>
</div>
)}
{result && result.error && (
<div className="error">
Error: {result.error}
</div>
)}
</div>
);
};
// Usage
const App = () => {
const [svgContent, setSvgContent] = useState('');
const handleConvert = (result) => {
console.log('Conversion completed:', result);
};
return (
<div>
<textarea
value={svgContent}
onChange={(e) => setSvgContent(e.target.value)}
placeholder="Paste your SVG code here..."
rows={10}
cols={50}
/>
<SvgToPngConverter
svgContent={svgContent}
onConvert={handleConvert}
className="converter"
/>
</div>
);
};
export default SvgToPngConverter;
Node.js Express API Endpoint
const express = require('express');
const sharp = require('sharp');
const puppeteer = require('puppeteer');
const rateLimit = require('express-rate-limit');
const app = express();
// Rate limiting
const convertLimit = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many conversion requests, please try again later.'
});
app.use(express.json({ limit: '10mb' }));
app.use('/api/convert', convertLimit);
// Browser instance for Puppeteer
let browser = null;
async function initBrowser() {
if (!browser) {
browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
}
return browser;
}
// SVG to PNG conversion endpoint
app.post('/api/convert/svg-to-png', async (req, res) => {
try {
const { svgContent, options = {} } = req.body;
if (!svgContent) {
return res.status(400).json({ error: 'SVG content is required' });
}
const defaultOptions = {
width: 800,
height: 600,
format: 'png',
quality: 90,
backgroundColor: 'transparent',
method: 'sharp' // or 'puppeteer'
};
const config = { ...defaultOptions, ...options };
let buffer;
if (config.method === 'sharp') {
buffer = await convertWithSharp(svgContent, config);
} else {
buffer = await convertWithPuppeteer(svgContent, config);
}
// Set appropriate headers
res.set({
'Content-Type': `image/${config.format}`,
'Content-Length': buffer.length,
'Content-Disposition': `attachment; filename="converted.${config.format}"`
});
res.send(buffer);
} catch (error) {
console.error('Conversion error:', error);
res.status(500).json({
error: 'Conversion failed',
details: error.message
});
}
});
async function convertWithSharp(svgContent, options) {
try {
let sharpInstance = sharp(Buffer.from(svgContent))
.resize(options.width, options.height)
.density(300);
if (options.backgroundColor !== 'transparent') {
const bgColor = parseColor(options.backgroundColor);
sharpInstance = sharpInstance.flatten({ background: bgColor });
}
if (options.format === 'jpeg') {
sharpInstance = sharpInstance.jpeg({ quality: options.quality });
} else if (options.format === 'webp') {
sharpInstance = sharpInstance.webp({ quality: options.quality });
} else {
sharpInstance = sharpInstance.png({ compressionLevel: 6 });
}
return await sharpInstance.toBuffer();
} catch (error) {
throw new Error('Sharp conversion failed: ' + error.message);
}
}
async function convertWithPuppeteer(svgContent, options) {
const browserInstance = await initBrowser();
const page = await browserInstance.newPage();
try {
await page.setViewport({
width: options.width,
height: options.height,
deviceScaleFactor: 2
});
const html = `
<!DOCTYPE html>
<html>
<head>
<style>
body { margin: 0; padding: 0; background: ${options.backgroundColor}; }
svg { width: 100%; height: 100%; }
</style>
</head>
<body>${svgContent}</body>
</html>
`;
await page.setContent(html);
await page.waitForTimeout(500);
const screenshotOptions = {
type: options.format,
quality: options.format === 'jpeg' ? options.quality : undefined,
fullPage: false,
clip: {
x: 0,
y: 0,
width: options.width,
height: options.height
}
};
return await page.screenshot(screenshotOptions);
} finally {
await page.close();
}
}
function parseColor(colorString) {
// Simple color parser for hex colors
if (colorString.startsWith('#')) {
const hex = colorString.slice(1);
const r = parseInt(hex.slice(0, 2), 16);
const g = parseInt(hex.slice(2, 4), 16);
const b = parseInt(hex.slice(4, 6), 16);
return { r, g, b, alpha: 1 };
}
return { r: 255, g: 255, b: 255, alpha: 1 };
}
// Graceful shutdown
process.on('SIGINT', async () => {
console.log('Shutting down gracefully...');
if (browser) {
await browser.close();
}
process.exit(0);
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`SVG conversion API running on port ${PORT}`);
});
Troubleshooting Common Issues
Security and CORS Issues
// Handle CORS and security restrictions
class SecureSvgConverter {
static sanitizeSvg(svgString) {
// Remove potentially dangerous elements
let cleaned = svgString
.replace(/<script[^>]*>.*?<\/script>/gi, '')
.replace(/<object[^>]*>.*?<\/object>/gi, '')
.replace(/<embed[^>]*>.*?<\/embed>/gi, '')
.replace(/<iframe[^>]*>.*?<\/iframe>/gi, '')
.replace(/on\w+="[^"]*"/gi, '')
.replace(/javascript:/gi, '')
.replace(/data:text\/html/gi, '');
// Ensure proper namespace
if (!cleaned.includes('xmlns=')) {
cleaned = cleaned.replace('<svg', '<svg xmlns="http://www.w3.org/2000/svg"');
}
return cleaned;
}
static async convertSecurely(svgString, options = {}) {
// Sanitize input
const safeSvg = this.sanitizeSvg(svgString);
// Validate SVG structure
try {
const parser = new DOMParser();
const doc = parser.parseFromString(safeSvg, 'image/svg+xml');
const parseError = doc.querySelector('parsererror');
if (parseError) {
throw new Error('Invalid SVG structure');
}
} catch (error) {
throw new Error('SVG validation failed: ' + error.message);
}
// Proceed with conversion using standard method
return await this.standardConvert(safeSvg, options);
}
static async standardConvert(svgString, options) {
// Implementation here...
return new Promise((resolve, reject) => {
// Standard conversion logic
});
}
}
// Handle CORS for external SVG resources
class CorsHandler {
static async fetchWithProxy(url) {
try {
// Use CORS proxy service
const proxyUrl = `https://cors-anywhere.herokuapp.com/${url}`;
const response = await fetch(proxyUrl);
return await response.text();
} catch (error) {
console.warn('CORS proxy failed, trying direct fetch');
return await fetch(url).then(r => r.text());
}
}
static async inlineExternalSvgResources(svgString) {
const urlRegex = /href=["']([^"']+)["']/g;
const urls = [];
let match;
while ((match = urlRegex.exec(svgString)) !== null) {
const url = match[1];
if (url.startsWith('http')) {
urls.push(url);
}
}
for (const url of urls) {
try {
const content = await this.fetchWithProxy(url);
svgString = svgString.replace(url, `data:text/plain;base64,${btoa(content)}`);
} catch (error) {
console.warn(`Failed to fetch external resource: ${url}`);
}
}
return svgString;
}
}
Conclusion
JavaScript-based SVG to PNG conversion offers powerful capabilities for web applications, from simple client-side tools to complex server-side processing systems. The Canvas API provides the foundation, while advanced techniques like Web Workers, memory management, and external resource handling enable production-ready solutions. Whether converting graphics from our AI icon generator or processing animated SVGs from our animation tool, these techniques will handle any conversion scenario.
For immediate SVG to PNG conversion without coding, try our free SVG to PNG converter tool. For more advanced image processing, explore our complete SVG toolkit.
Related Resources
- Convert SVG String to Image - Multiple conversion methods
- SVG to PNG Quality Guide - Optimization techniques
- Export SVG Programmatically - Server-side solutions
- SVG to PNG Converter - Online conversion tool