Excel Online(OneDrive/SharePoint)で、デスクトップ版では正常なのに「複数シートの画像が同一画像で上書き表示される」「一部の画像が消える」といった“画像漏れ/重複”が起きることがあります。本記事は、ExcelJSでブック結合時にPNGを埋め込むケースを中心に、原因の仮説、最短手の回避策、実装サンプル、検証方法までを一気通貫でまとめました。
問題の全体像(症状と前提)
以下の条件で再現しやすい事例が多数報告されています。
- 用途:ExcelJS 4.4.0 を使って複数の Excel ファイルを結合し、各シートに PNG 画像を
addImage()で埋め込み - 症状:
- デスクトップ版 Excel:各シートの固有画像が正しく表示
- Excel Online(SharePoint/OneDrive のブラウザー表示):特定の画像がシートをまたいで重複表示される、または一部画像が欠落
- Chrome/Edge/Firefox いずれも再現する報告あり
なぜ起きるのか(技術的背景の仮説)
アンカー種別と描画の不整合
Excel の画像は、Office Open XML で アンカー(配置基準)を持ちます。ExcelJS では概ね次の 2 種が用いられます。
- 一セルアンカー(
tl + ext):左上セル(tl)と画像サイズ(ext)で配置。セルのリサイズやズーム時に、画像とセルの位置・大きさの再計算をホスト側(ここでは Excel Online)が行います。 - 二セルアンカー(
tl + br):左上セル(tl)と右下セル(br)で画像が“張り付く”矩形を直接指定。セル境界を跨ぐ動きに強い。
一部の環境では、一セルアンカーの ext を Excel Online が正しくマッピングできず、「異なるシートの画像が同一メディアに解決されてしまう」かのような描画を見せることがあります。デスクトップ版では再現せず、Web版でのみ起きるのが特徴です。
メディア総量の実質上限(経験則)
Excel Online 側で、ワークブックに埋め込まれた画像の総量が増えると、およそ 5MB 前後を境に読み込みやレンダリングが不安定になり、画像の欠落・重複・差し替わりが強まるという観測もあります。公式仕様として明示されているわけではありませんが、運用の肌感として意識しておくとトラブルを減らせます。
最短で効く対策(結論の要点)
| 対応策 | 具体的内容・補足 |
|---|---|
| A. 二セルアンカーへ変更(最優先) | editAs: "twoCell" を指定し、tl と br の両端セルを計算して与える。オンライン版でもシート境界を強制的に尊重させやすい。列幅・行高を同時に整えて歪みを防止。 |
| B. 画像の圧縮(100KB/枚目安) | sharp などで PNG を 1 枚あたり 100KB 未満に圧縮。ワークブック全体で 5MB 未満を目指す。可逆で足りなければ JPEG も検討。 |
| C. 画像をリンク参照へ | 画像は SharePoint フォルダーへアップロードし、セルに =IMAGE("https://tenant.sharepoint.com/.../logo.png",1) で参照。ファイル容量を削減し、Web表示の安定性を向上。 |
| D. デスクトップ正規化 | ExcelJS で生成後、デスクトップ版で開いて「上書き保存」→ SharePoint にアップロード。XML が再生成され、Web 互換性が改善するケースあり。 |
| E. ライブラリ更新 | ExcelJS 4.4.1 以降への更新、または SheetJS(xlsx)等の代替を検証。画像埋め込み周りの修正が取り込まれている場合がある。 |
| F. シート順序の工夫 | 画像を含むシートをブック冒頭に集約し、その後にテキスト主体のシートを置くと、重複が緩和した事例がある。 |
| G. PDF 共有(当面の回避) | 閲覧が主目的なら PDF 化して共有。描画崩れの影響を遮断できる。 |
実装に落とす:二セルアンカー+圧縮のベストプラクティス
二セルアンカーの基本形
// 画像を二セルアンカーで追加する(ExcelJS)
const imageId = workbook.addImage({ extension: 'png', buffer: imgBuf });
const tl = { col: 1, row: 1 }; // 左上セル(0-based)
const br = { col: 3, row: 8 }; // 右下セル(自動計算でも可)
worksheet.addImage(imageId, { tl, br, editAs: 'twoCell' });
ポイントは 「右下セル br を“実測サイズから算出”する」ことです。セル幅・高さがデフォルトのままだと画像が歪むため、先に列幅・行高を整えるのがコツです。
列幅と行高の近似(Excel Onlineに寄せる)
- 列幅:既定フォント(Calibri 11)・既定 DPI(96)前提なら、
px ≒ 7 × 文字数 + 5を目安にします(±2px 程度の誤差想定)。 - 行高:Excel の行高は ポイント。ピクセルへの変換は
px = pt × 96 / 72(=pt × 1.333...)。
厳密計算はフォントやズーム率の影響を受けるため、Web表示での“見た目合致”を優先し、最終的に 1〜2px の微調整を許容すると安定します。
画像サイズから br を自動算出する関数例
// imgW, imgH: 画像の実ピクセル(sharpのmetadataなどで取得)
// startCol, startRow: 配置開始セル(0-based)
// colWidths: 各列の「Excelの文字数幅」配列(例: 10, 12, 15 ...)
// rowHeightsPt: 各行の高さ(ポイント)配列
function calcBottomRightByPixels(imgW, imgH, startCol, startRow, colWidths, rowHeightsPt) {
const colPx = (chars) => Math.round(chars * 7 + 5); // 近似
const rowPx = (pt) => Math.round(pt * 96 / 72); // 厳密
let w = imgW, h = imgH;
let c = startCol, r = startRow;
// 横方向:画像幅を満たすまで列を足す
while (w > 0 && c < colWidths.length) {
w -= colPx(colWidths[c]);
c++;
}
// 縦方向:画像高を満たすまで行を足す
while (h > 0 && r < rowHeightsPt.length) {
h -= rowPx(rowHeightsPt[r]);
r++;
}
// brは“はみ出し”を許容して1セル先に倒すほうが、Web側の切り捨て誤差に強い
return { col: c, row: r };
}
列幅・行高を調整しながら貼り付ける一連のコード
// 列幅・行高を整えた上で画像を二セルアンカーで貼る
function placeImageTwoCell(ws, imageId, startCol, startRow, imgW, imgH, plan) {
// plan: { cols: [{index,widthChars}], rows: [{index,heightPt}] }
// 1) 列幅・行高を設定
for (const c of plan.cols) ws.getColumn(c.index + 1).width = c.widthChars;
for (const r of plan.rows) ws.getRow(r.index + 1).height = r.heightPt;
// 2) 計算のための配列を用意
const maxCol = Math.max(...plan.cols.map(c => c.index)) + 1;
const maxRow = Math.max(...plan.rows.map(r => r.index)) + 1;
const colWidths = Array.from({length: maxCol}, (*,i) => {
const m = plan.cols.find(c => c.index === i);
return m ? m.widthChars : 8.43; // 既定幅
});
const rowHeightsPt = Array.from({length: maxRow}, (*,i) => {
const m = plan.rows.find(r => r.index === i);
return m ? m.heightPt : 15; // 既定高さpt
});
// 3) brセルを算出
const br = calcBottomRightByPixels(imgW, imgH, startCol, startRow, colWidths, rowHeightsPt);
// 4) 画像を配置
ws.addImage(imageId, { tl: { col: startCol, row: startRow }, br, editAs: 'twoCell' });
}
画像の圧縮(sharp の実用的プリセット)
import sharp from 'sharp';
// PNG(可逆)。パレット化+圧縮レベルで容量削減。目安100KB/枚。
async function compressPng(buf) {
return await sharp(buf)
.png({ compressionLevel: 9, palette: true, quality: 80 }) // qualityはパレット量子化の目安
.toBuffer();
}
// JPEG(非可逆)。写真系はPNGより効く。テキスト/ロゴはPNGが無難。
async function compressJpeg(buf) {
return await sharp(buf)
.jpeg({ quality: 80, mozjpeg: true })
.toBuffer();
}
// WebP/AVIFも選択肢だが、Excel埋め込みはPNG/JPEGが無難。
リンク画像(IMAGE関数)への切り替え
画像を埋め込まず、セルに画像URLを参照させると容量・安定性の両面で効きます。
// 例:B2にロゴを等倍挿入
worksheet.getCell('B2').value = '=IMAGE("https://tenant.sharepoint.com/sites/site/Shared%20Documents/logo.png",1)';
表示倍率(第2引数)や高さ・幅指定(第3/第4引数)を使えば、表の設計自由度も保てます。画像のバージョン更新も差し替え不要です。
再現 → 修正 → 検証の手順(現場用チェックリスト)
- 現在の埋め込み方法を確認:ExcelJS の
addImage呼び出しでeditAsを指定しているか?tl + extの一セルアンカーになっていないか? - ファイルのメディア総量を概算:画像枚数 × 平均サイズが 5MB を超えていないか。超えていればまず圧縮。
- 二セルアンカーへ変更:列幅・行高を決め、
brを画像サイズから計算して与える。 - Excel Online で目視検証:ブラウザーを変え、キャッシュをクリア(Ctrl+F5)して再確認。
- なお不安定ならリンク化:
IMAGE()へ移行し、埋め込み画像を減らす。 - 暫定運用:閲覧主体のワークフローは PDF に切り替え。編集が必要な原本は別管理。
症状別の対処フローチャート
| 症状 | 想定原因 | 優先対処 | 次善策 |
|---|---|---|---|
| 全シートで同一画像に置き換わる | 一セルアンカーの解釈不整合、メディアの解決ミス | 二セルアンカー化+列幅・行高の明示 | 画像の圧縮/枚数削減、リンク化 |
| 一部の画像が消える/読み込まれない | 総容量超過、画像形式・色深度の相性 | 圧縮(100KB目安)、PNGはパレット化 | JPEG化、PDF共有 |
| サイズだけがずれる | 列幅・行高の未設定、DPI差 | 列幅=文字数×7+5近似、行高=pt×1.333…の式で調整 | 端数切り上げ(右下セルを1つ広めに) |
| 特定ブラウザーでのみ崩れる | キャッシュ、ズーム、拡張機能の干渉 | Ctrl+F5、100%ズーム、拡張停止で検証 | 別ブラウザーで運用、PDF化 |
デスクトップ版での「正規化」手順(D)
- ExcelJS が生成したファイルをデスクトップ版 Excel で開く
- 画像が正しく見えていることを確認した上で、そのまま「上書き保存」
- 保存されたファイルを SharePoint/OneDrive にアップロードして検証
この工程で XML が再生成され、Web レンダラーの解釈に寄った構造へ“整形”されるため、症状が軽減することがあります。
ライブラリ更新と実装の見直し(E)
- ExcelJS はマイナー更新でも描画関連の修正が入ることがあるため、バージョン固定のまま長期運用しないのがポイント。
- 画像の追加順やシートの作成順が結果に影響することがあるため、「画像を含むシート → テキストのみのシート」の順で確定させる運用も有効です(F)。
「合成」や「結合」時の落とし穴と対処
- 同じ画像IDを意図せず再利用:共通ロゴなどで
workbook.addImageの戻り値(ID)を流用するのは正しいですが、別画像に同じ変数名を上書きしてしまうと意図しない参照になります。ID と画像ファイルをマップで管理し、上書きを避ける。 - 透明PNG・巨大キャンバス:見た目が小さくてもキャンバスが 4000×4000 など極端に大きいと総容量・再計算コストが増し、崩れの誘因に。まずサイズを切り詰める。
- 高DPIスクリーンショット貼り付け:2×密度の画像は視認性は高いが容量が膨らむ。Web表示を前提に 1×解像度へダウンスケールする。
監視と品質保証(QA)の仕組み化
運用に載せた後も、画像崩れは「データの増減」「ユーザーの拡大」で再燃しがちです。次のような自動チェックを CI に組み込みましょう。
- メディア総量チェック:生成時に画像の合計バイト数を計測し、閾値(例:4.5MB)で警告・ビルド失敗に。
- アンカー検査:出力されたファイルの
xl/drawings/を zip 展開してtwoCellAnchorが使われているかを機械判定。 - 差分スクリーンショット:参考用のPDF(または画像)と最新出力を画像比較し、しきい値以上の差異でアラート。
よくある質問(FAQ)
Q. 一セルアンカーでも安定することはありますか?
A. あります。画像が小さく、セルのリサイズがほぼ無い静的な表なら問題が出にくいです。ただし「ブック結合」「画像多数」「自動調整あり」では二セルアンカーのほうが堅実です。
Q. PNG と JPEG、どちらが良いですか?
A. ロゴ・UIキャプチャ・図は PNG(パレット化)、写真は JPEG が目安です。迷ったらまず PNG を 100KB 未満にし、必要なら JPEG に切り替える方針が運用しやすいです。
Q. 画像リンク(IMAGE関数)にすると外部公開になりませんか?
A. 画像の URL 権限は SharePoint 側の設定に従います。社内限定のライブラリに置けば、閲覧者の認証が必要です。“誰でもアクセス可”な格納先は避けましょう。
Q. 画像がにじむ/ぼやけるのは?
A. Webのズームやブラウザーのスケーリングが影響します。等倍(100%)で見たときにぴったり収まるよう、画像解像度とセル寸法の比率を 1.0 に寄せてください。
Q. ExcelJS と SheetJS のどちらを選ぶべき?
A. 既存実装やチームの知見次第ですが、画像の扱いに関してはライブラリ差より“設計(アンカー・容量・順序)”のほうが支配的です。まずは現行実装の癖を取り除くのが近道です。
Q. ブックの先頭に画像シートを集約する意味は?
A. 実測では、描画リレーションの組み立て順序が安定し、Web側の解決が素直になるケースがあります。保証はありませんが低コストで試せる改善です。
実践レシピ:結合バッチの雛形(安全版)
import ExcelJS from 'exceljs';
import sharp from 'sharp';
async function embedPngSafe(ws, pngBuf, at) {
// 1) 画像圧縮
const buf = await sharp(pngBuf).png({ compressionLevel: 9, palette: true, quality: 80 }).toBuffer();
// 2) 画像サイズ
const meta = await sharp(buf).metadata();
const imgW = meta.width ?? 300;
const imgH = meta.height ?? 200;
// 3) 列・行の設計(例:見開きテーブルに合わせる)
const plan = {
cols: Array.from({length: 10}, (*,i) => ({ index: i, widthChars: i === at.col ? 20 : 12 })),
rows: Array.from({length: 30}, (*,i) => ({ index: i, heightPt: i === at.row ? 60 : 18 }))
};
// 4) 画像IDを追加
const id = ws.workbook.addImage({ extension: 'png', buffer: buf });
// 5) 貼り付け
placeImageTwoCell(ws, id, at.col, at.row, imgW, imgH, plan);
}
export async function buildMergedBook(sources) {
const wb = new ExcelJS.Workbook();
for (const src of sources) {
// 各ソースからシートを追加してデータを転記…
const ws = wb.addWorksheet(src.name);
await embedPngSafe(ws, src.logoPng, { col: 1, row: 1 });
// 表や数式、スタイルの設定…
}
return wb;
}
検証マトリクス(最低限の組み合わせテスト)
| 項目 | ケース | 期待結果 |
|---|---|---|
| アンカー種別 | oneCell vs twoCell | twoCell のみ合格 |
| 画像枚数 | 5枚 / 20枚 / 50枚 | 20枚以内は安定。50枚は圧縮必須 |
| 総容量 | 2MB / 5MB / 8MB | 5MB超で不安定化の兆候。8MBは高リスク |
| ブラウザー | Chrome / Edge / Firefox | 全てで同一レンダリングを確認 |
| シート順序 | 画像シート先頭 vs 末尾 | 先頭配置のほうが安定 |
トラブル再発を防ぐ運用 Tips
- 画像ポリシーを文書化:最大サイズ、形式、解像度、配置ルールを README に明記。
- 自動圧縮を標準化:リポジトリに
scripts/compress-images.mjsを置き、PR で必ず走らせる。 - 検査ジョブ:ビルド後に
zip展開してdrawingsを静的解析、twoCellAnchorの比率をメトリクス化。 - 段階的ロールアウト:ブックの閲覧先を段階追加し、Excel Online の現場環境で目視確認してから全社展開。
ケーススタディ:二セルアンカー+圧縮での改善例
ある案件では、各シートにロゴ(150KB/枚)とチャート画像(300KB/枚)を貼る構成でした。一セルアンカー+無圧縮の初期実装では、Web表示で 3 枚に 1 枚の割合で画像が別シートのロゴに差し替わる現象を確認。対応として、
- PNG を 100KB 未満に圧縮(ロゴは 48KB、チャートは 92KB)
- 貼り付けを 二セルアンカーに変更し、列幅と行高を固定
- 画像シートをブック冒頭にまとめ、文字主体シートを後段へ
この結果、Excel Online での重複・差し替わりは再現しなくなり、閲覧が安定しました。ブック全体の画像総量は 6.4MB → 2.1MB まで減少し、読み込み時間も短縮しました。
既知の制約と期待値設定
- Excel Online は継続的に改善されていますが、デスクトップ版と完全同一の描画は前提にしないほうが安全です。
- 巨大な画像・多数の画像・複雑なスタイルが重なると、描画順の競合が起きやすくなります。シンプルな設計に寄せること自体が強力な対策です。
まとめ(実務の指針)
- まずは「二セルアンカー+画像圧縮」。これで大半の環境で改善します。
- 完全に解決しない場合は、リンク画像(IMAGE関数)へ段階移行。容量と安定性が一気に向上します。
- 運用に入ったら、メディア総量・アンカー種別・見た目合致を CI で継続検査しましょう。
付録:ユーティリティ(容量・アンカー検証)
// ZIP化されたxlsxをNodeで展開して、画像総量とアンカー種別を調べる例
// (擬似コード:実装ではadm-zip等を使用)
import AdmZip from 'adm-zip';
function inspectWorkbook(xlsxPath) {
const zip = new AdmZip(xlsxPath);
const entries = zip.getEntries();
let mediaBytes = 0;
let twoCell = 0, oneCell = 0;
for (const e of entries) {
if (e.entryName.startsWith('xl/media/')) {
mediaBytes += e.header.size;
}
if (e.entryName.startsWith('xl/drawings/drawing') && e.entryName.endsWith('.xml')) {
const xml = e.getData().toString('utf8');
twoCell += (xml.match(/
付録:二セルアンカー化の既存コード差し替え例
従来が tl + ext の一セルアンカーだった場合の置き換えイメージです。
// Before(例:一セルアンカー)
ws.addImage(imgId, {
tl: { col: x, row: y },
ext: { width: pxW, height: pxH } // 画素寸法
});
// After(二セルアンカー)
const br = calcBottomRightByPixels(pxW, pxH, x, y, colWidths, rowHeightsPt);
ws.addImage(imgId, {
tl: { col: x, row: y },
br,
editAs: 'twoCell'
});
最後に:現場での意思決定の目安
| 状況 | おすすめ方針 | 理由 |
|---|---|---|
| 画像が多い・定期発行物 | 二セル+圧縮+CI検査 | 反復運用で最も安定。人的な貼り直しが不要。 |
| 一時的な閲覧共有 | PDF 変換 | 描画差の影響を遮断し、配布が簡単。 |
| 社内限定での参照 | リンク画像(IMAGE) | 容量を最小化し、差し替えが容易。 |
Excel Online の画像重複/漏れは、設計の“ひと工夫”で大半が解消できます。「二セルアンカーで置く」→「画像を軽くする」→「必要ならリンク化」の順に段階導入し、誰が開いても同じ見た目で安定表示されるブックを目指しましょう。

コメント