Скачивание изображения через ссылку

В Chart.js любой график рендерится внутри <canvas>, что позволяет получать итоговое изображение без серверной обработки. Основной механизм экспорта основан на API HTMLCanvasElement и методах, встроенных в саму библиотеку.

Ключевые подходы к получению изображения:

  • toBase64Image() — быстрое получение dataURL
  • toBlob() — формирование бинарного файла
  • ручной экспорт через canvas.toDataURL()

Получение изображения через toBase64Image

Chart.js предоставляет встроенный метод, позволяющий получить изображение графика в формате Base64.

const canvas = document.getElementById('chart');

const chart = new Chart(canvas, {
  type: 'bar',
  data: {
    labels: ['A', 'B', 'C'],
    datasets: [{
      label: 'Данные',
      data: [10, 20, 30]
    }]
  }
});

const imageBase64 = chart.toBase64Image();

Результат представляет собой строку:

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...

Использование результата

Наиболее распространённый сценарий — подстановка в ссылку загрузки:

const link = document.createElement('a');
link.href = chart.toBase64Image();
link.download = 'chart.png';
link.click();

Формирование файла через toBlob

Метод toBlob позволяет получить изображение в виде бинарного объекта, что удобнее для работы с файлами и большими графиками.

const canvas = document.getElementById('chart');

const chart = new Chart(canvas, {
  type: 'line',
  data: {
    labels: ['Янв', 'Фев', 'Мар'],
    datasets: [{
      label: 'Продажи',
      data: [15, 25, 18]
    }]
  }
});

chart.canvas.toBlob(function(blob) {
  const url = URL.createObjectURL(blob);

  const link = document.createElement('a');
  link.href = url;
  link.download = 'chart.png';
  link.click();

  URL.revokeObjectURL(url);
}, 'image/png');

Особенность подхода заключается в том, что данные не кодируются в base64, что снижает нагрузку на память.


Экспорт с использованием canvas.toDataURL

Chart.js фактически опирается на стандартный метод canvas:

const canvas = chart.canvas;

const image = canvas.toDataURL('image/png');

Далее изображение обрабатывается так же, как и в предыдущих случаях:

const link = document.createElement('a');
link.href = image;
link.download = 'chart.png';
link.click();

Управление качеством изображения

При стандартном экспорте изображение может получаться размытым на дисплеях с высокой плотностью пикселей. Причина заключается в devicePixelRatio.

Увеличение качества достигается через временное масштабирование:

const originalRatio = window.devicePixelRatio;

const canvas = chart.canvas;
const context = canvas.getContext('2d');

canvas.width = canvas.width * 2;
canvas.height = canvas.height * 2;

context.scale(2, 2);

chart.update();

Более корректный способ — пересоздание графика с увеличенным devicePixelRatio:

new Chart(canvas, {
  type: 'bar',
  data: data,
  options: {
    devicePixelRatio: 2
  }
});

Добавление фона для корректного PNG

PNG поддерживает прозрачность, и при экспорте фон может оказаться прозрачным. Для обеспечения стабильного внешнего вида задаётся принудительный фон.

const plugin = {
  id: 'custom_canvas_background',
  beforeDraw: (chart) => {
    const ctx = chart.canvas.getContext('2d');
    ctx.save();
    ctx.fillStyle = 'white';
    ctx.fillRect(0, 0, chart.width, chart.height);
    ctx.restore();
  }
};

Использование:

new Chart(canvas, {
  type: 'line',
  data: data,
  plugins: [plugin]
});

Скачивание изображения через отдельную функцию

При повторяющемся использовании логика выносится в универсальную функцию:

function downloadChartImage(chart, filename = 'chart.png') {
  const link = document.createElement('a');
  link.href = chart.toBase64Image();
  link.download = filename;
  link.click();
}

Вариант с Blob:

function downloadChartImageBlob(chart, filename = 'chart.png') {
  chart.canvas.toBlob((blob) => {
    const url = URL.createObjectURL(blob);

    const link = document.createElement('a');
    link.href = url;
    link.download = filename;
    link.click();

    URL.revokeObjectURL(url);
  }, 'image/png');
}

Особенности работы с CORS и внешними изображениями

При использовании изображений внутри графика (например, pointStyle: image) может возникнуть ограничение безопасности браузера.

Если изображение загружено с другого домена без корректных CORS-заголовков, canvas становится «tainted», и экспорт блокируется.

Решение заключается в:

  • использовании изображений с включённым CORS
  • установке img.crossOrigin = 'anonymous' перед загрузкой
  • хранении ассетов локально

Экспорт с учётом анимации

Chart.js по умолчанию завершает анимацию перед созданием изображения. Для фиксации состояния без ожидания используется отключение анимации:

new Chart(canvas, {
  type: 'bar',
  data: data,
  options: {
    animation: false
  }
});

Получение изображения после обновления данных

При динамическом изменении данных важно дождаться завершения перерисовки:

chart.data.datasets[0].data = [5, 10, 15];

chart.update();

setTimeout(() => {
  const img = chart.toBase64Image();
}, 0);

Более стабильный вариант:

chart.update('none');
const img = chart.toBase64Image();

Экспорт нескольких графиков на одной странице

При наличии нескольких экземпляров Chart.js логика применяется индивидуально к каждому объекту:

charts.forEach((chart, index) => {
  const link = document.createElement('a');
  link.href = chart.toBase64Image();
  link.download = `chart_${index}.png`;
  link.click();
});

Ограничения экспорта canvas

Основные технические ограничения:

  • невозможность экспорта «векторного» качества
  • зависимость от разрешения canvas
  • блокировка при tainted canvas
  • отсутствие поддержки прозрачности в некоторых сценариях браузера
  • ограничение памяти при base64-строках большого размера