Phương pháp chuẩn: toDataURL()
Phương thức canvas.toDataURL(type, quality) trả về Data URL (Base64) của hình ảnh canvas.
Cú pháp
// PNG (mặc định, lossless)
canvas.toDataURL()
canvas.toDataURL('image/png')
// JPEG (lossy, quality 0.0 - 1.0)
canvas.toDataURL('image/jpeg', 0.92)
// WebP (hỗ trợ tốt trên trình duyệt hiện đại)
canvas.toDataURL('image/webp', 0.9)
Ví dụ hoàn chỉnh: Vẽ và lưu ảnh
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<title>Canvas to Image</title>
<style>
.canvas-wrapper { display: flex; gap: 2rem; flex-wrap: wrap; justify-content: center; margin: 2rem; }
canvas { border: 1px solid #ddd; border-radius: 8px; background: #fafafa; }
.controls { display: flex; flex-direction: column; gap: 1rem; min-width: 200px; }
button { padding: 0.75rem; background: #3498db; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 1rem; }
button:hover { background: #2980b9; }
button:disabled { background: #bdc3c7; cursor: not-allowed; }
#downloadLink { display: none; }
img#preview { max-width: 100%; border: 1px solid #ddd; border-radius: 4px; margin-top: 1rem; }
</style>
</head>
<body>
<div class="canvas-wrapper">
<canvas id="drawCanvas" width="400" height="300"></canvas>
<div class="controls">
<button id="savePngBtn" onclick="saveAsPNG()">Lưu PNG</button>
<button id="saveJpgBtn" onclick="saveAsJPG()">Lưu JPEG (92%)</button>
<button id="saveWebpBtn" onclick="saveAsWebP()">Lưu WebP (90%)</button>
<button id="previewBtn" onclick="previewImage()">Xem trước</button>
<a id="downloadLink" download="canvas-image.png"></a>
<img id="preview" alt="Preview">
</div>
</div>
<script>
const canvas = document.getElementById('drawCanvas');
const ctx = canvas.getContext('2d');
const downloadLink = document.getElementById('downloadLink');
const previewImg = document.getElementById('preview');
// Vẽ nội dung mẫu
function drawSample() {
// Gradient background
const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
gradient.addColorStop(0, '#667eea');
gradient.addColorStop(1, '#764ba2');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Hình tròn
ctx.beginPath();
ctx.arc(200, 150, 80, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(255,255,255,0.9)';
ctx.fill();
ctx.strokeStyle = '#fff';
ctx.lineWidth = 4;
ctx.stroke();
// Text
ctx.font = 'bold 32px Arial';
ctx.fillStyle = '#333';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('HTML5 Canvas', 200, 150);
// Decorative
for (let i = 0; i < 20; i++) {
ctx.beginPath();
ctx.arc(Math.random() * 400, Math.random() * 300, Math.random() * 10 + 2, 0, Math.PI * 2);
ctx.fillStyle = `rgba(255,255,255,${Math.random() * 0.5 + 0.2})`;
ctx.fill();
}
}
// Lưu file helper
function downloadCanvas(filename, mimeType, quality) {
try {
const dataURL = canvas.toDataURL(mimeType, quality);
downloadLink.href = dataURL;
downloadLink.download = filename;
downloadLink.click();
} catch (e) {
console.error('Lỗi lưu ảnh:', e);
alert('Không thể lưu ảnh. Kiểm tra console.');
}
}
function saveAsPNG() {
downloadCanvas('canvas-' + Date.now() + '.png', 'image/png');
}
function saveAsJPG() {
downloadCanvas('canvas-' + Date.now() + '.jpg', 'image/jpeg', 0.92);
}
function saveAsWebP() {
downloadCanvas('canvas-' + Date.now() + '.webp', 'image/webp', 0.9);
}
function previewImage() {
const dataURL = canvas.toDataURL('image/png');
previewImg.src = dataURL;
previewImg.style.display = 'block';
}
// Khởi tạo
drawSample();
</script>
</body>
</html>
Lưu ý quan trọng
- Tainted Canvas: Nếu vẽ hình ảnh từ domain khác (cross-origin) không có CORS header
Access-Control-Allow-Origin, canvas sẽ bị "tainted" vàtoDataURL()némSecurityError. - Giải pháp: Thêm
crossorigin="anonymous"vào thẻ<img>và server phải trả header CORS. - Kích thước file: Data URL Base64 lớn ~33% so với binary. File > vài MB có thể gây treo trình duyệt.
- Blob (hiện đại): Dùng
canvas.toBlob()cho file lớn, tránh block main thread.
Phương pháp hiện đại: toBlob() (Async, hiệu quả hơn)
canvas.toBlob(function(blob) {
// Tạo Object URL
const url = URL.createObjectURL(blob);
// Tải xuống
const a = document.createElement('a');
a.href = url;
a.download = 'canvas-image.png';
a.click();
// Giải phóng memory
URL.revokeObjectURL(url);
}, 'image/png', 0.92); // quality chỉ áp dụng cho JPEG/WebP
Xử lý Cross-Origin Images (CORS)
<!-- HTML: thêm crossorigin -->
<img id="sourceImg" src="https://cdn.example.com/image.jpg" crossorigin="anonymous">
<script>
const img = document.getElementById('sourceImg');
img.onload = function() {
ctx.drawImage(img, 0, 0);
// Bây giờ toDataURL() sẽ hoạt động
};
// Server phải trả header: Access-Control-Allow-Origin: * (hoặc domain của bạn)
</script>
So sánh định dạng xuất
| Định dạng | Chất lượng | Kích thước | Hỗ trợ | Phù hợp |
|---|---|---|---|---|
| PNG | Lossless | Lớn | Tất cả | Đồ họa, text, transparent |
| JPEG | Lossy | Nhỏ | Tất cả | Ảnh chụp, gradient phức tạp |
| WebP | Lossless/Lossy | Nhỏ hơn JPEG ~25% | Hiện đại (IE không) | Web hiện đại, hiệu năng cao |