ExcelJSライブラリを使用すると、画像を含むテーブルデータをExcelファイルとして出力できます。以下では、画像がURLまたはBase64形式で提供される場合の実装例を紹介します。
必要なパッケージのインストール
npm install exceljs file-saver
URL形式の画像を1セル1枚で出力
この実装では、画像を並列でダウンロードし、必要に応じて圧縮してからExcelに埋め込みます。
<template>
<div>
<button @click="exportToExcel">Excel出力</button>
<table>
<thead>
<tr>
<th>ID</th>
<th>名前</th>
<th>画像</th>
</tr>
</thead>
<tbody>
<tr v-for="record in dataset" :key="record.id">
<td>{{ record.id }}</td>
<td>{{ record.title }}</td>
<td><img :src="record.imageUrl" width="80" height="80" /></td>
</tr>
</tbody>
</table>
</div>
</template>
<script setup>
import { ref } from 'vue';
import ExcelJS from 'exceljs';
import { saveAs } from 'file-saver';
const dataset = ref([
{
id: 1,
title: 'サンプルA',
imageUrl: 'https://example.com/image1.jpg'
},
{
id: 2,
title: 'サンプルB',
imageUrl: 'https://example.com/image2.png'
}
]);
const exportToExcel = async () => {
const workbook = new ExcelJS.Workbook();
const sheet = workbook.addWorksheet('画像付きデータ');
sheet.columns = [
{ header: 'ID', key: 'id', width: 10 },
{ header: '名前', key: 'title', width: 20 },
{ header: '画像', key: 'image', width: 30 }
];
// 画像の並列取得
const imagePromises = dataset.value.map(async (item, idx) => {
try {
const res = await fetch(item.imageUrl, { cache: 'force-cache' });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const contentType = res.headers.get('content-type') || 'image/jpeg';
const ext = contentType.split('/')[1] || 'jpeg';
const blob = await res.blob();
const optimized = await resizeImage(blob, ext);
const buffer = await optimized.arrayBuffer();
return { rowIndex: idx, data: buffer, format: ext };
} catch (err) {
console.warn(`画像取得失敗 (${item.id})`, err);
return { rowIndex: idx, data: null, format: null };
}
});
const results = await Promise.all(imagePromises);
const sorted = results.sort((a, b) => a.rowIndex - b.rowIndex);
// テキストデータの書き込み
dataset.value.forEach((item, i) => {
const row = i + 2;
sheet.getCell(`A${row}`).value = item.id;
sheet.getCell(`B${row}`).value = item.title;
});
// 画像の挿入
sorted.forEach((img, i) => {
const row = i + 2;
if (!img.data) {
sheet.getCell(`C${row}`).value = '画像なし';
return;
}
const imgId = workbook.addImage({
buffer: img.data,
extension: img.format
});
sheet.addImage(imgId, {
tl: { col: 2, row: row - 1 },
ext: { width: 100, height: 100 }
});
sheet.getRow(row).height = 80;
});
const buf = await workbook.xlsx.writeBuffer();
const blob = new Blob([buf], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
saveAs(blob, '画像付きデータ.xlsx');
};
// 画像リサイズ関数
const resizeImage = (blob, fmt, maxSize = 200) => {
return new Promise((resolve) => {
const img = new Image();
img.src = URL.createObjectURL(blob);
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
let { width, height } = img;
if (width > height && width > maxSize) {
height = (height * maxSize) / width;
width = maxSize;
} else if (height > maxSize) {
width = (width * maxSize) / height;
height = maxSize;
}
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
canvas.toBlob(
(newBlob) => {
URL.revokeObjectURL(img.src);
resolve(newBlob || blob);
},
`image/${fmt}`,
0.8
);
};
img.onerror = () => resolve(blob);
});
};
</script>
1行に複数画像を配置する場合
各行に複数の画像がある場合、横方向に並べて表示できます。
const dataset = ref([
{
id: 1,
title: '複数画像',
images: [
{ url: 'https://example.com/img1.jpg' },
{ url: 'https://example.com/img2.jpg' }
]
}
]);
// 出力処理内では、各画像に対して:
// - 所属行(rowIndex)
// - 行内での順序(imgIndex)
// を記録し、Excel上で横並びに配置
const colOffset = (imgIndex * (imgWidth + spacing)) / 64;
worksheet.addImage(imgId, {
tl: { col: 2 + colOffset, row: actualRow - 1 },
ext: { width: imgWidth, height: 200 }
});
Base64形式の画像を扱う場合
Base64文字列を直接ArrayBufferに変換して使用します。ただし、データ量が増加するため小規模な用途に限定すべきです。
const processBase64Image = (base64Str) => {
const parts = base64Str.split(',');
const data = parts.length > 1 ? parts[1] : base64Str;
const binary = atob(data);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes.buffer;
};
const mimeType = parts[0]?.match(/:(.*?);/)?.[1] || 'image/png';
const ext = mimeType.split('/')[1];
方式の比較と選択指針
- URL形式:ネットワーク環境が良好で画像数が多い場合に最適。ブラウザキャッシュや並列ダウンロードにより効率的。
- Base64形式:オフライン対応や小規模データ向け。信頼性は高いがデータサイズが約33%増加。
- サーバーからのArrayBuffer直送:理論的には高速だが、実際にはBase64エンコードやストリーム処理が必要で、大容量時にメモリ・ネットワーク負荷が高まる。画像が少なく、かつ一時URLなどキャッシュ不可なケースに限定して検討。