ReoGrid Web は、Excel グレードの編集体験 — 数式エンジン、xlsx I/O、Canvas レンダラー — をすべて単一の依存ゼロパッケージにまとめた JavaScript/TypeScript 製スプレッドシートライブラリです。React/Vue 用のラッパーと無料の Lite ティアを npm で提供しています。
v1.4 でグリッドはドキュメントツールになりました。v1.5 は日々の操作を埋めました。v1.6 のテーマは「グリッドが黙って捨てていたもの」です。
具体的には 3 つ。xlsx から読み込んだ画像は、表示はされるものの保存すると消えていました。セルコメントは読めるのに書き戻せず、レビュー済みのブックを保存するとすべてのメモが消えていました。印刷範囲は自社の JSON では往復するのに xlsx では往復せず、次に開くとシート全体が印刷対象に戻っていました。この 3 つがすべて保存後も残るようになり、あわせて画像は読み取り専用から完全に編集可能になりました。
さらに、Ctrl/Cmd+クリックによる複数範囲の選択、縦書きと文字回転、保護シートをキーボードだけで埋められるフォームモード、Excel の**「N ページに収める」**、そして書き出した PDF がすべて極細になっていた不具合の修正が入っています。
いずれも追加のみで、公開 API に破壊的変更はありません。
画像 — 挿入・移動・リサイズ、そして残る
これまで画像はビューア向けの機能でした。xlsx を読み込んでロゴを表示することはできましたし、バイト列を読み出すこともできました。できなかったのは、追加すること、動かすこと、そして失わずに保存することです。
import { createReogrid } from '@reogrid/pro';
const grid = createReogrid({ workspace: '#app' });
const ws = grid.worksheet;
// バイト列・ArrayBuffer・data: URL のいずれか。新しい画像の id を返します
const id = ws.addImage(bytes, { row: 1, column: 1, width: 240, height: 120 });
// ファイル選択から直接 — 既定で画像本来のサイズを使います
await ws.addImageFromFile(file, { row: 1, column: 1 });
ws.moveImage(id, { row: 5, column: 2 });
ws.resizeImage(id, { width: 320, height: 160 });
ws.removeImage(id);
マウスでは、クリックで選択すると 8 つのグリップが現れ、本体のドラッグで移動、グリップのドラッグでリサイズ(Shift で縦横比を維持)、Delete で削除できます。ドラッグ 1 回が Ctrl+Z 1 手です — 移動したピクセル数ぶんではありません。
画像は Excel と同じく 3 種類の OOXML アンカーでセルに紐づきます。oneCell(セルと一緒に動き、サイズは固定)、twoCell(セルと一緒に動き、サイズも変わる)、absolute(シートに固定)。行・列の挿入と削除にも追従するので、ロゴの下の行を削除してもロゴは消えず、twoCell の画像はまたがる行が縮めば一緒に縮みます。Excel の挙動そのままです。
本当の変化は永続化のほうです。エクスポートは実体のある xl/media と xl/drawings パートを書き出し、同一の画像は 1 つにまとめて格納するため、押印済みの請求書は押印を保ったまま Excel で開けます。ReoGrid JSON も往復し(sheet.images。JSON を通信フォーマットとして使っていて写真がそれを圧倒する場合は toJson({ includeImages: false }))、PDF エクスポートにも描画されます。
モデルに触れずに操作だけを制御するスイッチが 2 つあります。ws.setImageEditEnabled(false) は画像を表示したまま API も使えるまま、マウス操作だけを止めます(シート保護でも同じ状態になります)。ws.setImagesEnabled(false) は機能ごと無効化するもので、Lite が内部で行っているのはこれです。Lite は画像を表示も編集もしませんが、取り込みと書き出しでは保持するため、Lite のビューアーで開いた文書が画像を失うことはありません。
Pro の機能です。画像のドキュメントとデモをご覧ください。
複数範囲の選択 — 両エディションで
Ctrl(macOS では Cmd。macOS の Ctrl+クリックは右クリックのため)を押しながらクリック・ドラッグすると、選択を置き換えずに範囲を追加します。セルでも行・列ヘッダーでも同じです。地域別の売上シートから離れた 4 支店を拾って、まとめて太字にする — そういう操作ができます。
「選択範囲」に対する操作はすべての範囲に効くようになりました。Delete は 1 手の undo ですべての範囲を消し、書式はすべての範囲に適用され、Ctrl+クリックした列の境界をダブルクリックするとその各列が調整されます(間の列は対象外です)。
const ws = grid.worksheet;
ws.selection.moveTo('A1:B2');
ws.selection.add('D4:E6'); // Ctrl+クリックの API 版
ws.selection.count; // 2
ws.selection.ranges.forEach(r => r.setBold());
単一範囲の選択でも ws.selection.ranges は 1 要素を返すため、「ユーザーが選択したもの」に対して操作するならこれが安全です。bounds / range / activeCell / moveTo は従来どおり単一範囲を指し(最後に選択されたアクティブな範囲)、onSelectionChange にはすべての範囲を列挙する第 2 引数が増えたため、既存の 1 引数のリスナーは影響を受けません。
コピーは Excel の規則に従います。範囲が整列している(同じ列で縦に並ぶ、または同じ行で横に並ぶ)場合のみ 1 ブロックとして運ばれ、それ以外は Excel と同じ説明とともに拒否されます。複数範囲を選択している間はフィルハンドルとドラッグ移動が無効になります。
両エディションで利用できます。選択とイベントのドキュメントとデモをご覧ください。
縦書きと文字回転 — 両エディションで
日本の帳票には必要なのに、Web のグリッドではたいてい実現できない 2 つです。
ws.range('B1:F1').setTextRotation(45); // Excel と同じ -90〜90。範囲外はクランプ
ws.range('A1:A6').setVerticalText(); // 縦書き
回転は、狭い数値列が並ぶ横長の表を読みやすくするレイアウトです。見出しは長い語、データは 4 桁 — 見出しを 45 度傾ければ、列は数値が必要とする幅のままで済みます。縦書きは 1 文字ずつ縦に積み、段は右から左へ流れ、長音記号や括弧は縦組みの向きに回し、、 。 は縦組みの組版どおり文字枠の右上に寄せます。1 文字幅の列に「年度」「第一四半期」を入れるときに必要になるものです。
これを「使える機能」にしているのは、自動調整が回転後の外形を測る点です。90 度倒したラベルには、行の高さぶんだけの狭い列が割り当たります。回転前の横書きの幅で調整してしまえば、傾けた意味がなくなります。
画面・xlsx・ブラウザ印刷・PDF エクスポートのすべてが同じ配置になり、スタイルは Excel の <alignment textRotation>(縦書きは 255)としてそのまま往復します。Excel の配置ダイアログと同じく回転と縦書きは 1 つのコントロールで、角度を設定すると縦書きが解除され、その逆も同様です。
両エディションで利用できます。文字回転・縦書きのドキュメントとデモをご覧ください。
フォームモード — キーボードだけで埋める保護シート
業務アプリでは、スプレッドシートを「表計算」ではなくフォームとして使うことがよくあります。レイアウトは固定、ほとんどのセルは保護され、ユーザーは決まった枠に値を埋めるだけ。従来この形を作るにはホスト側で大量のイベント処理が必要でしたが、いまは 4 つのスイッチで済みます。
const ws = grid.worksheet;
ws.protected = true;
ws.range('C3:C8').setLock('unlocked'); // 入力セル
ws.layoutLocked = true; // ヘッダーのドラッグ・自動調整・並べ替えを止める
ws.clickToEdit = true; // 1 クリックで編集開始、キャレットは末尾
ws.formNavigation = true; // Enter / Tab が入力欄を渡り歩く
ws.setCellPlaceholder(2, 2, '例: 山田 太郎');
clickToEdit は編集可能セルの上でカーソルを I ビームにもするので、見た目も入力欄らしくなります。formNavigation の既定の順序は、保護セル・非表示の行と列・結合セルの内側を飛ばす読み順スキャンで、行の末尾で折り返します — これがあるおかげで、行の最後の欄から次の行の最初の欄へ自然に進めます。アプリ側がレイアウトを把握している場合は ws.setFormNavigator(fn) で意図した順序に差し替えられ、null を返せばフォームの終わりで止まります。
プレースホルダーは空のセルにだけ描かれ、セルの値には決して含まれません — PDF・印刷・xlsx・JSON には出ず、数値として右に揃えられることもありません。
最後の一歩がドロップダウンです。入力規則のリストをキーボードだけで操作できるようになりました。編集中の ↓、または選択中の Alt+↓ で、マウスで開くのと同じリストが開きます。↑↓ で移動、Enter で確定、Escape で閉じる。リストは現在値の位置で開くので ↓ → Enter の 2 打で選べ、選択はエディタを経由して確定されるため入力規則の検証もフォーム移動もそのまま効きます。フォームが最後まで、マウスに触れずに埋まります。
これらのスイッチはいずれも保存されないため、loadJson() や reset() のあとに付け直してください。
両エディションで利用できます。フォームモードのドキュメントとデモをご覧ください。
印刷 — N ページに収める、そしてようやく xlsx に届いた 2 つ
fitToPages は Excel の「N ページに収める」です。縮小率を手で決める代わりに、横/縦を何ページに収めるかを指定します。
ws.setPrintSettings({ fitToPages: { width: 1 } }); // すべての列を 1 ページ幅に
ws.getEffectivePrintScale(); // → 導出された倍率(例: 0.72)
Excel と同じく縮小のみで、すでに収まっている内容は 100% で印刷されます。改ページプレビュー・PDF エクスポート・ブラウザ印刷はすべてこの値で一致します。設定していた scale はフィット中も保持され、解除すると戻ります。
あわせて、長く空いていた 2 つの穴が塞がりました。
- 印刷範囲が xlsx に書き出されます。
setPrintableRange(...)は ReoGrid JSON でしか往復しなかったため、xlsx に保存すると次に開いたときシート全体が印刷対象に戻っていました。現在は Excel と同じ_xlnm.Print_Areaとして書き出されます。 - セルコメントが保存後も残ります。 エクスポートがコメントパートと、Excel が併せて要求する従来形式の VML ボックスを書き出すため、メモは本文・作成者・常時表示かどうかを保ったまま往復します。呼ぶものはなく、
saveAsXlsxで自動的に行われます。
改ページと印刷範囲のドラッグも undo できるようになりました。ドラッグ 1 回に Ctrl+Z 1 回で、改ページ移動の undo は、そのドラッグが下げた印刷倍率も元に戻します。
Pro の機能です。ページレイアウトのドキュメントをご覧ください。
PDF がすべて極細になっていた理由
これは書いておく価値があります。症状が原因をまったく指していなかったからです。
書き出した PDF が極細でした。「少し薄い」ではなく、紙面全体が Thin ウェイトで描画され、太字の見出しも太く見えません。CSS の問題やスケーリングの問題に見えますが、どちらでもありません。
組み込みのフォントタグが Google の可変フォントを指していました。可変フォントは既定座標(fvar の初期値)の字形を glyf に持ち、他のウェイトへ動かす差分を gvar に持ちます。PDF は glyf の輪郭を埋め込みますが、書き出し側は gvar を適用しません。Noto の既定座標は wght=100 — つまり Thin です。そのため出力はまるごと Thin で焼き付いていました(出力中の /BaseFont /…+NotoSansJP-Thin がその証拠です)。しかも太字は渡された輪郭を 4% ストロークする擬似ボールドなので、土台が極細では太くしようがありませんでした。
PDF はグリフの輪郭を埋め込む以上「ウェイト=別のフォントファイル」なので、レジストリはウェイトごとに face を持つ形になりました。
import { registerPdfFont, preloadPdfFont } from '@reogrid/pro';
import { notoSansJP } from '@reogrid/font-jp'; // { thin, normal, bold }
registerPdfFont('ja', notoSansJP);
await preloadPdfFont('ja'); // 既定で normal + bold を取得
組み込みの URL は静的な face を指すようになり、日本語のペイロードは可変フォント 1 本 9.15MB から 1 face あたり約 2.5MB になりました。preloadPdfFont の返り値は従来どおり normal のバイト列なので v1.6 より前に書いた呼び出しはそのままで構いませんし、registerPdfFont(tag, ソース1つ) も従来どおりそれを normal として登録します。擬似ボールドは本来あるべき位置 — bold face が無いときだけ働く代替 — に降格しました。
自前で読み込んだフォントには options.fonts で他のウェイトを足せます。
grid.saveAsPdf({
font: await loadFont('/Brand-Regular.ttf'),
fonts: { bold: await loadFont('/Brand-Bold.ttf') },
});
@reogrid/font-{jp,sc,tc,kr} は v2 から Thin/Regular/Bold の静的 face を同梱し、registerPdfFont がそのまま受け取れる形のオブジェクトを export します。ローダー 1 本ではなく notoSansJP を渡してください。ローダー 1 本でも動きますが regular だけが登録されるため、太字は擬似ボールドにフォールバックします。
Pro の機能です。PDF エクスポートのドキュメントをご覧ください。
ブラウザ印刷が画面の描画と一致するようになりました
HTML 経由の印刷で、画面には出ている文字が紙で失われていました — 「有効期限」が「有…」になる、というものです。原因は 3 つのずれで、いずれも修正済みです。
- 隣の空セルへはみ出すようになりました。 canvas は Excel と同じく、列幅より広い値を隣の空セルへこぼしますが、
<td>は必ずクリップします。溢れ幅は画面と同じ関数で求めるようにしました。 - 省略記号を付けなくなりました。 2px はみ出しただけで最後の 1 文字が「…」に化けていました。canvas も Excel も紙には省略記号を出さずクリップするだけです。
- 行が指定どおりの高さになりました。
<tr height>は HTML では最小値なので、折り返しのある明細行が伸びて後続の行がずれ、ページ下端の行 — 振込先、備考 — が紙から落ちていました。
セルにアンカーした画像もこの経路で描画されるようになったため、画面と PDF に出るロゴや印影が印刷にも出ます。行より高い内容は上下の行にこぼさず行でクリップし、PDF 出力は表示形式のブラケット色([赤] / [Red])を反映するようになったので、会計帳票の赤字が黒く印刷されることもなくなりました。
その他
- Enter で値が二重にならなくなりました。 1 つのセルに「120」⏎「180」⏎ と打つと
120180になっていました。エディタの Enter が確定するだけで移動せず、同じ keydown がキーボードコントローラまで上がって、確定済みの文字ごとエディタを開き直していたためです。Enter=確定して下、Shift+Enter=上、Tab=右、Shift+Tab=左に修正しました。 - 小数を含む書式でも千単位スケーリングが効きます。
#,##0,は動いていましたが、0.0,や#,##0.0,,のように小数部の後ろにカンマを置く形 — Excel が標準的に書き出すほうの並び — はスケーリングされず、しかもカンマが除去されないため0.0,,"M"は 2,400,000 を2400000.0,,Mと表示していました。 - 和暦の元年。
[$-ja-JP-x-gannen]ggge"年"m"月"d"日"が 令和1年 ではなく 令和元年7月1日 を表示します。官公庁・金融の帳票で必要になるものです。 - 並べ替えがコメント・セル型・ロック状態を運びます。
sortRowsは値・スタイル・罫線は並べ替えていましたが、メモは元の行に残っていたため、並べ替えたあとコメントが別の行の内容に付いているように見えていました。 - 非表示列の文字が隣の列に重ならなくなりました — 作業用の列を隠して使う、取り込んだ帳票テンプレートで起きていたものです。
- 1 本ずつのサイズ変更にも改ページが追随します。
worksheet.column(c).width = wが境界を作り直しておらず、getPrintPageRanges()が変更前のサイズで計算した改ページを返していました。ブラウザでは次の描画に隠れがちですが、ヘッドレスでは常に誤ります。 autoFitMaxScanRowsをcreateReogrid()から設定できます。 v1.5 でWorksheetOptionsに追加したものの、公開オプション型に宣言が無く、パッケージ利用者からは設定できませんでした。- 行・列ヘッダーのフォント指定。
createReogrid({ headerFont: { fontFamily: 'Meiryo', fontSize: 14 } })。行番号・列名は12px sans-serif固定だったため、本文フォントを変えてもヘッダーだけ揃えられませんでした。ヘッダー帯はフォントに合わせて広がります。 - ヘッドレスの xlsx 取り込み。
XlsxImporterとconvertImageInfoを公開エントリから出しました。顧客の .xlsx をブラウザ外でテンプレートに変換するビルドツール向けです。
アップグレード
v1.6 はドロップインのアップグレードで、コードの変更は不要です。
yarn up @reogrid/pro@latest
Lite はライセンスキー不要で npm から利用できます。
npm install @reogrid/lite
v1.6 の完全な変更履歴はリリースノート、API 全体は画像、選択とイベント、文字回転・縦書き、フォームモード、ページレイアウト、PDF エクスポートの各ドキュメント、体験は画像、複数範囲の選択、文字回転、フォームモードの各デモをご覧ください。Lite / Pro の比較は料金表にあります。