やりたいこと:画面上のテーブルをExcelにそのまま貼り付ける
業務アプリでは「画面に表示している集計テーブルを、そのままExcelに貼り付けて資料に使いたい」という要望がよくあります。CSVダウンロードを用意する方法もありますが、ユーザーからするとテーブルを選択してコピーし、Excelに貼り付けるだけのほうが手軽です。
ところがこれを素朴に「テーブルをドラッグ選択してコピー」でやると、Excel側で列がずれたり、罫線や色が消えたり、逆に意図しない書式まで貼り付いたりすることがあります。原因はコピー元のHTML構造とクリップボードへの書き込み方法にあり、navigator.clipboard.write() で text/html 形式を明示的に書き込めば、狙った見た目のままExcelに貼り付けられます。
この記事では、まずドラッグ選択コピーで崩れる例を失敗デモで見せてから、コピーボタンで整形済みのHTMLをクリップボードに書き込む修正デモを紹介します。
まず失敗を見る:ドラッグ選択コピーで列がずれる
下のテーブルは、集計行やマイナス値の強調にCSSを使った、業務アプリでよくある売上一覧です。このテーブルをドラッグで選択してコピーし、Excelに貼り付けてみてください(この場ではExcelを開けないので、まず選択範囲とコピー結果の挙動を確認します)。
テーブル全体をドラッグで選択してコピー(Ctrl+C)し、Excel・Googleスプレッドシートに貼り付けてみてください。ブラウザや貼り付け先によっては装飾がある程度残ることもありますが、選択範囲に周囲の要素まで巻き込んで余分な列や空行が入り込む、貼り付け先によって見た目が変わるといった不安定さが残ります。
| 商品名 | 数量 | 単価 | 金額 |
|---|---|---|---|
| ノートPC | 3 | 120,000 | 360,000 |
| マウス | 10 | 2,000 | 20,000 |
| 返品分 | -1 | 2,000 | -2,000 |
| 合計 | 12 | - | 378,000 |
ここを選択してコピーし、Excelに貼り付けて崩れを確認してください。
ブラウザは選択範囲の計算済みスタイルをある程度インライン化してコピーするため、装飾自体は貼り付け先によって残ることもあります。ただし実際に起きやすいのは、テーブル外の余白テキストまで1つのセルとして混入する、ページの他の要素(見出しやボタン)まで選択範囲に入り込み列がずれる、ブラウザやOSのバージョンによって貼り付け結果が変わる、といった「毎回同じ結果になるとは限らない」不安定さです。狙った見た目を毎回再現したいなら、選択範囲をユーザーに委ねるべきではありません。
原因:ドラッグ選択はブラウザ任せのHTML変換に頼っている
ドラッグ選択コピーでは、ブラウザが「選択範囲に含まれるDOM」をそのままクリップボードの text/html として書き出します。この変換はブラウザ任せで、次のような点をコントロールできません。
選択範囲の境界があいまい(テーブルの外側の余白や隣接要素を巻き込みやすい)。CSSクラスで当てたスタイルは、貼り付け先のExcelがそのCSSを解釈できるとは限らない。同じ操作でもブラウザによって書き出されるHTMLの形が微妙に異なる。
Excelは貼り付け時に受け取った text/html をExcel独自のルールで解釈して表に変換しますが、外側の要素まで含まれていたり、罫線や背景色がclass指定(Excelには意味を持たない)だけで表現されていたりすると、意図通りに変換されません。列がずれて見えるのは、テーブルの構造自体が崩れているのではなく、コピーされたHTMLに余計な要素が混ざっている、またはExcelが解釈できるインライン形式で書式が渡っていないためです。
解決策は、ユーザーのドラッグ選択に頼らず、コピーボタンを押した瞬間に「Excelが解釈できる整形済みのHTML」を自前で組み立て、クリップボードに書き込むことです。
対策:navigator.clipboard.write() でtext/htmlを書き込む
navigator.clipboard.write() は、ClipboardItem を使って複数のMIMEタイプを同時にクリップボードへ書き込めるAPIです。text/html にExcelが解釈しやすいインラインスタイル付きのテーブルHTML、text/plain にタブ区切りのプレーンテキストを同時に用意しておくと、貼り付け先がリッチテキストを受け付けるアプリ(Excel・Word・メールなど)ならHTML側が、受け付けないアプリ(テキストエディタなど)ならプレーンテキスト側が自動で使われます。
// テーブル要素からExcel向けHTMLとTSVの両方を生成してコピー
async function copyTableForExcel(tableEl) {
const rows = Array.from(tableEl.querySelectorAll('tr'));
// text/html: インラインスタイルで罫線・太字・色を明示する
// (class指定はExcelに伝わらないため、貼り付け直前にstyle属性へ変換する)
let html = '<table style="border-collapse:collapse">';
rows.forEach((tr) => {
html += '<tr>';
Array.from(tr.children).forEach((cell) => {
const tag = cell.tagName.toLowerCase(); // th or td
const isTotalRow = cell.closest('tr').classList.contains('is-total');
const bold = tag === 'th' || isTotalRow;
const color = cell.closest('tr').classList.contains('is-negative') ? 'color:#c00;' : '';
// 見出し行・合計行は背景色もインラインで明示する(class指定だけでは背景色もExcelに伝わらない)
const background = tag === 'th' ? 'background-color:#f1f5f9;' : (isTotalRow ? 'background-color:#f8fafc;' : '');
const style = `border:1px solid #999;padding:4px 8px;${bold ? 'font-weight:bold;' : ''}${color}${background}`;
html += `<${tag} style="${style}">${cell.textContent.trim()}</${tag}>`;
});
html += '</tr>';
});
html += '</table>';
// text/plain: タブ区切り(Excelにはこちらも解釈されるフォールバック)
const tsv = rows
.map((tr) => Array.from(tr.children).map((c) => c.textContent.trim()).join('\t'))
.join('\n');
const item = new ClipboardItem({
'text/html': new Blob([html], { type: 'text/html' }),
'text/plain': new Blob([tsv], { type: 'text/plain' })
});
await navigator.clipboard.write([item]);
}
ポイントは、選択範囲をユーザーに任せずテーブル要素そのものからHTMLを組み立て直していることです。これにより、外側の余計な要素が混入する心配がなくなります。加えて、背景色や太字・文字色を class ではなく style のインライン指定で書き出しているのが要です。Excelは貼り付け時にクラス定義のCSSファイルを参照しないため、インラインスタイルにしておかないと装飾が反映されません。
「Excel用にコピー」ボタンを押すと、上のテーブルと同じ内容から整形済みのHTMLとTSVを組み立ててクリップボードに書き込みます。Excelに貼り付けると、罫線・見出し行と合計行の背景色・太字・赤字がそのまま反映されます(クリップボード書き込みにはページがhttps、またはlocalhostである必要があります)。
| 商品名 | 数量 | 単価 | 金額 |
|---|---|---|---|
| ノートPC | 3 | 120,000 | 360,000 |
| マウス | 10 | 2,000 | 20,000 |
| 返品分 | -1 | 2,000 | -2,000 |
| 合計 | 12 | - | 378,000 |
まだコピーしていません。
クリップボードコピー自体は、ボタンひとつ・数行のコードで実現できる小さな機能です。テキストのみでよいなら navigator.clipboard.writeText(text) を使う一行実装で十分ですが、Excel向けに書式まで保つ場合は、上記のように ClipboardItem で text/html と text/plain を両方渡すのが安全です。writeText だけでは書式情報を運べない点を覚えておいてください。
エッジケース一覧:この場合はどうなるか
HTMLテーブルのExcelコピペで実際につまずきやすいパターンを、ドラッグ選択コピーと copyTableForExcel 方式で対比します。
| 入力パターン | ドラッグ選択コピーの結果 | copyTableForExcel方式の結果 |
|---|---|---|
| CSSクラスによる背景色・太字 | Excelにクラス定義が伝わらず装飾が消える | インラインstyleに変換しているため反映される |
| テーブル外の余白・隣接ボタン | 選択範囲に巻き込まれ、余計な列や行として貼り付く | テーブル要素だけを走査するため混入しない |
| 数値セル(カンマ区切り表示) | 文字列として貼り付き、Excel側で数値扱いされないことがある | 同じく文字列として渡る。数値として計算させたい場合はカンマを除去してから書き込む必要がある |
| 結合セル(colspan/rowspan) | ブラウザの変換次第で崩れやすい | HTML生成時にcolspan/rowspan属性をそのまま出力すればExcelも結合セルとして解釈する |
| 非対応ブラウザ・非HTTPS環境 | 影響なし(OS標準のコピーのため) | clipboard.writeが使えず失敗する。フォールバックとしてwriteTextでTSVのみコピーする処理を用意する |
| セル内改行を含む値 | ブラウザ変換に依存し不安定 | HTML側は<br>等で表現、TSV側はダブルクォートで囲むなど別途エスケープ処理が必要(本記事の逆方向にあたる「Excelから貼り付け」時のクォート処理と対になる) |
特に注意したいのは非HTTPS環境・非対応ブラウザでのフォールバックです。navigator.clipboard.write() は比較的新しいAPIで、かつセキュアコンテキスト(HTTPSまたはlocalhost)でしか動作しません。イントラネットの古い環境などでは動かない可能性があるため、失敗時は document.execCommand('copy') や writeText による簡易フォールバックを用意しておくと安全です。
まとめ
HTMLテーブルをExcelに貼り付けたときに列がずれたり書式が消えたりするのは、テーブルの作り方の問題ではなく、ドラッグ選択コピー任せのHTML変換に依存しているためです。navigator.clipboard.write() で ClipboardItem を使い、テーブル要素から自前でインラインスタイル付きのHTMLと、フォールバック用のTSVを組み立ててクリップボードに書き込めば、狙った見た目のままExcelに渡せます。
装飾はclassではなくstyleのインライン指定で渡す。text/htmlとtext/plainの両方を用意し、貼り付け先に応じて自動で選ばれるようにする。これがExcel向けコピーの基本形。
この記事はExcelから受け取る方向(Excelからのコピペをテキストエリアで受け取ってテーブルに取り込む方法)と対になる、Webから渡す方向の記事です。両方を押さえておくと、業務アプリとExcelの間のコピペをどちらの向きでも崩さず扱えるようになります。
関連するUI事例
テーブル表示やクリップボードコピーの実装を、実際に動くコードで確認できる事例です。
あわせて読みたい
Excel連携では、コピペの向きが変わるだけで注意点も変わります。あわせて読むとExcelとのデータやり取りをまとめて押さえられます。