Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> JavaScript

Cách lưu dữ liệu Canvas ra file ảnh trong HTML5


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ém SecurityError.
  • 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ạngChất lượngKích thướcHỗ trợPhù hợp
PNGLosslessLớnTất cảĐồ họa, text, transparent
JPEGLossyNhỏTất cảẢnh chụp, gradient phức tạp
WebPLossless/LossyNhỏ hơn JPEG ~25%Hiện đại (IE không)Web hiện đại, hiệu năng cao