ReoGrid Web は、Excel グレードの編集体験 — 数式エンジン、xlsx I/O、Canvas レンダラー — をすべて単一の依存ゼロパッケージにまとめた JavaScript/TypeScript 製スプレッドシートライブラリです。React/Vue 用のラッパーと無料の Lite ティアを npm で提供しています。
v1.4 でグリッドはドキュメントツールになりました。v1.5 は日々の操作を埋め、v1.6 は保存のたびに黙って捨てていたものを残すようにしました。v1.7 のテーマは「誰かが作ったブック」です。
今回の 2 つの目玉は、どちらもこのサイトのオンライン xlsx ビューアーに寄せられた申告から生まれました。手元にある実際のファイルを開いてもらい、表示がおかしければ知らせてもらう場所です。1 つは、表が大きすぎて読めないのに縮小する手段がないという申告。もう 1 つは、リンクが開かないという申告です — Excel のハイパーリンクは青い下線付きの文字として表示されるだけでクリックしても何も起きず、=HYPERLINK() は #NAME? になっていました。後半で紹介する xlsx 読み込みの修正にも、同じ確認作業から生まれたものがいくつかあります。
残りは、同じブックのその後をたどる変更です。ブックの先頭シートだけを黙って読み込んでいたメソッドの名前を改め、素直に書けばすべてのシートが開くようにしました。ブック全体を 1 つの PDF にでき、帳票の空の罫線枠も印刷範囲に含まれます。書き出した xlsx は Excel でも画面どおりに開き、iPad・iPhone ではセルへの入力とファイルのダウンロードが正しく動くようになりました。
公開 API に破壊的変更はありません。 ただし挙動が変わる点が 3 つあり、末尾の「アップグレード」にまとめています。
表示倍率 — シートごとに 10%〜400%
Excel の「ズーム」ボックスと同じものが、両エディションで使えます。
import { createReogrid } from '@reogrid/pro';
const grid = createReogrid({ workspace: '#app' });
const ws = grid.worksheet;
ws.setZoom(1.5); // 150% — 10%〜400% にクランプ
ws.getZoom(); // 1.5
// ツールバーの「150%」表示を追従させる
grid.onZoomChange(zoom => {
label.textContent = `${Math.round(zoom * 100)}%`;
});
// 倍率はシートごと。シートの切り替えは「変化」ではないので、そこでも読み直す
grid.workbook.onActiveSheetChange(() => {
label.textContent = `${Math.round(grid.worksheet.getZoom() * 100)}%`;
});
マウスでは Ctrl+ホイール(macOS は Cmd+ホイール)で、ポインタの位置を中心に拡大縮小します。見ている場所がカーソルの下から逃げません。マウスホイール 1 ノッチでおよそ 15% です。ブラウザはピンチ操作を Ctrl 付きのホイールイベントとして送ってくるため、トラックパッドのピンチも同じ経路で効きます。倍率は Excel と同じくシートごとに持ち、xlsx には zoomScale として保存されます。85% で保存されたブックは 85% で開き、保存しても 85% のままです。
設計上の要点は、変わるのは画面の見え方だけということです。行の高さ・列幅・フォントサイズ・スクロール量・ヒットテストはすべて 100% のシート上のピクセルのままで、倍率を掛けるのはシートと画面が接する部分だけです。そのため倍率を変えても、印刷・PDF や xlsx への書き出し・自動調整の結果は変わりません。グリッド線は Excel と同じくどの倍率でも 1px のまま。つかむ場所の大きさも画面上で変わりません — 行・列の境界、ドラッグ移動に使う選択枠、フィルハンドル、画像のグリップ — 50% に縮小しても掴みにくくなることはありません。
倍率は閲覧側の設定であって、シートの内容ではありません。ReoGrid JSON には保存されず、reset() や loadJson() を呼んでも倍率はそのままです。キーボードショートカットは割り当てていません。Ctrl+プラス/マイナス/0 はブラウザのものだからです。
アップグレード時に知っておきたいこと 2 つ。
-
グリッド上の Ctrl/Cmd+ホイールはシートの拡大縮小になりました。 1.6 まではブラウザのページズームに素通ししていました。従来どおりにしたい場合はオフにできます。
setZoom()はどちらの設定でも使えます。const grid = createReogrid({ workspace: '#app', wheelZoom: false }); -
重ね合わせ用の矩形は 100% のピクセルのままです。
getCellRect()・getRangeRect()・getImageRect()で位置を取って独自の要素をグリッドに重ねている場合は、倍率を掛けてください。const z = ws.getZoom(); const r = ws.getCellRect(4, 2); // 倍率に関係なく 100% のシート px Object.assign(badge.style, { left: `${r.x * z}px`, top: `${r.y * z}px`, width: `${r.width * z}px`, height: `${r.height * z}px`, });
両エディションで利用できます。表示倍率のドキュメントとデモをご覧ください。xlsx ビューアーでファイルを開き、表の上で Ctrl+ホイールを回して試すこともできます。
クリックで開くハイパーリンク
xlsx ファイルのリンクを読み込み、表示し、保存時にも残すようになりました。リンクのあるセルをクリックすると、リンク先が新しいタブで開きます。Excel と同じく、押したままにすると開かずにセルを選択します。マウスを乗せると指カーソルになり、ツールチップにリンク先のアドレスが出ます(ヒントが設定されたリンクでは、ヒントの下にアドレスを表示)。クリックする前に行き先を確かめられるようにするためです。Excel の =HYPERLINK(リンク先, [別名]) も動き、セルには別名が表示され、クリックするとリンク先が開きます。
コード上のリンクはセルの属性であって、セル型ではありません。値も書式もそのまま残ります。xlsx から読み込んだリンクが Excel での見た目を保てるのも、このためです。
const ws = grid.worksheet;
// セルにリンクを付ける。値と書式はそのまま — 見た目は自分で整える
ws.setCellInput(1, 0, 'リリースノート');
ws.setCellStyle(1, 0, { color: '#0563C1', underline: true });
ws.setHyperlink(1, 0, 'https://web.reogrid.net/jp/release-notes/', { tooltip: '変更履歴の全文' });
// Excel の関数でも
ws.setCellInput(2, 0, '=HYPERLINK("mailto:[email protected]", "営業窓口へ")');
// クリックはすべてここを先に通る
grid.onHyperlinkClick(e => {
if (!confirm(`${e.url} を開きますか?`)) e.cancel = true;
});
開く対象は意図的に絞っています。 開くのは絶対 URL の http・https・mailto・tel だけで、新しいタブは noopener,noreferrer 付きで開きます。開いた先のページは元のページへの参照を持たず、Referer で元のページの URL を知ることもありません。それ以外 — javascript:、data:、file: のパス、ブックからの相対パス — はセルに短いメッセージを出すだけです。onHyperlinkClick にはこのチェックの前に、保存されたままのアドレスが渡されます。cancel を使えば、ReoGrid 自身が開かない宛先もアプリ側で処理できます。
従来からある hyperlink セル型も、同じチェックを通るようになりました(こちらは相対パスも引き続き使え、ページの URL を基準に解決されます)。1.7 より前はアドレスをそのまま window.open に渡し、HTML 書き出しでも <a href> にそのまま書いていたため、javascript: のアドレスがページ内で実行されてしまう状態でした。
リンクは行・列の挿入と削除、並べ替え、ドラッグ移動に追従し、xlsx だけでなく ReoGrid JSON(sheet.hyperlinks)でも往復します。
まだ対応していないこと: ブック内の場所(Sheet2!A1)へのリンクは保持され、保存時にも書き戻されますが、クリックしてもまだジャンプしません。コピー&ペーストではリンクは運ばれません。
グリッドを sandbox 付きの <iframe> に埋め込んでいる場合、sandbox に allow-popups がないとブラウザが新しいタブをブロックします。allow-popups-to-escape-sandbox も加えないと、開いたページに埋め込み側の sandbox が引き継がれます。
リンクの読み込み・表示・クリックは両エディションで動きます。setHyperlink でリンクを作るのは Pro の機能で、=HYPERLINK() も Pro です(Lite には組み込み関数がないため #NAME? になります)。ハイパーリンクのドキュメントとデモをご覧ください。
ファイルを開けばブック全体が開く
これは私たち自身の API にあった落とし穴です。
ワークシートには、インスタンスとまったく同じ名前の読み込みメソッドがありました。grid.loadFromFile(file) の隣に grid.worksheet.loadFromFile(file) があり、どちらも同じように読めます。ところがワークシート側が取り込むのはシート 1 枚だけ(先頭のシート)で、シートタブのバーも出ず、ほかのシートには辿り着けません。先頭のタブが表紙や目次になっているブックは何も入っていないように見えたうえ、理由はどこにも出ませんでした。
シート 1 枚を読み込むメソッドは、名前でそれが分かるようにしました。
| 旧名(非推奨) | 新名 |
|---|---|
worksheet.loadFromFile | worksheet.loadSheetFromFile |
worksheet.loadFromUrl | worksheet.loadSheetFromUrl |
worksheet.loadXlsx / loadFromBuffer | worksheet.loadSheetFromBuffer |
// ブック全体 — すべてのシートを、シートタブ付きで
await grid.loadFromFile(file);
// あえてシート 1 枚だけ
await grid.worksheet.loadSheetFromFile(file, { sheetName: '集計' });
ファイルを開くときはインスタンスのメソッド — grid.loadFromFile()、loadFromUrl()、loadXlsx() — を使ってください。worksheet.loadSheet* は、本当にシート 1 枚だけが欲しいときのためのものです。旧名もこれまでどおりに動きます。@deprecated が付いているのでエディタでは取り消し線で表示され、初回の呼び出し時にコンソールへ一度だけ警告を出し、代わりに使うべき 2 つのメソッドを示します。旧名は 2.0 で削除します。
両エディションが対象です。xlsx 入出力のドキュメントをご覧ください。
読み込んだブックが、より Excel に近い見た目に
同じビューアーの確認作業から生まれた、読み込みの修正が 3 つあります。
- 書式を設定していないセルは、ブックの既定フォントで表示します。 Excel がスタイルなしで保存したセルはブックの「標準」スタイルを使いますが、取り込み側は ReoGrid 自身の Arial 10 のままにしていました。標準フォントが Candara 12 のブックでは、同じ表の中で書式を触っていないセルだけ、隣の書式付きセルとフォントもサイズも違って表示されていました。これからはブックのフォントで開くので、一般的な Excel ファイルなら Calibri 11、游ゴシック 11、MS Pゴシック 11 になります。なお、ファイルに含まれていない空のセルに新しく入力した場合は、これまでどおり ReoGrid の既定フォントで始まります。
- Excel で「スタイルなし」にしたテーブルを、そのまま無地で表示します。 スタイル名がないと既定の
TableStyleMedium2として読んでいたため、Excel では無地のテーブルが青い見出しと縞模様付きで表示されていました。これからは何も塗らず、保存してもスタイルなしのまま書き戻します。addTable(range, { style: '' })で同じテーブルを作れます(Pro)。 - ReoGrid が解釈できない数式は、数式の文字列ではなく Excel の計算結果を表示します。 別のブックを参照する数式(
=[1]Sheet1!A1*2)や、テーブルの構造化参照を使う数式(=SUM(Table1[Qty]))は、これまでセルに数式の文字列がそのまま出ていました。これからは Excel がファイルに保存した結果を表示し、ほかの数式からもその値を使えます。数式そのものは保持され、書き出し時にも書き戻されます。再計算はされず、セルを編集すると数式は置き換わります。また、引用符で囲んだ別ブックへの参照(='[1]My Sheet'!A1)を、存在しない自ブックのシートと取り違えて#REF!にすることもなくなりました。
ブック全体を 1 つの PDF に
exportPdf と saveAsPdf に sheets オプションが加わりました。Excel の「ブック全体を印刷」にあたるものです。
import { createReogrid, preloadPdfFont } from '@reogrid/pro';
await preloadPdfFont('ja');
// 表示されているすべてのシートを、それぞれのページ設定で
grid.saveAsPdf({ locale: 'ja', usePageBreaks: true, sheets: 'all' });
// 指定したシートを、指定した順に — 名前でもインデックスでも
const bytes = grid.exportPdf({ locale: 'ja', sheets: ['見積書', 2] });
'all' は表示されているすべてのシートをタブの順に出力します。配列で指定すると、そのシートだけを指定した順に出力し、非表示のシートも名前を挙げれば含まれます。既定値は従来どおり 'active' なので、既存の呼び出しはそのまま動きます。
usePageBreaks を指定すると、各シートがそれぞれの設定で改ページされます — 用紙サイズ、向き、余白、「N ページに収める」、改ページ位置。A4 縦と A3 横のページを 1 つの文書に混在させることもできます。ページ番号はシートをまたいで通しで振られます。 ヘッダー・フッターの &P は文書の最初のページから数え、&N は文書全体のページ数です。Excel がブック全体を印刷するときと同じ数え方で、&A は引き続き、そのページが属するシートの名前になります。
grid.worksheet.setPrintSettings({
headerFooter: { footer: { left: '&A', right: '&P / &N ページ' } },
});
印刷する内容がないシートは、Excel と同じく飛ばされます。ビルドスクリプトやサーバー向けには、ヘッドレス版の exportWorksheetsPdf(worksheets, options) があります。
Pro の機能です。PDF エクスポートのドキュメントをご覧ください。コードを書かずに試すなら、xlsx → PDF 変換ツールで Sheets → All sheets を選んでください。
帳票が欠けずに印刷される
印刷範囲を設定していないとき、これまで印刷される範囲は A1 から値が入っている最後のセルまででした。しかし典型的な帳票はもっと先まで続きます。まだ行が空の罫線付きの表、文字が先頭セルにだけ入っている結合された備考欄、押印欄、ロゴ。そのため改ページプレビュー・PDF エクスポート・ブラウザ印刷のいずれでも帳票が最後の値のところで切れ、「N ページに収める」はその部分だけが収まるように縮小していました。
これからは、紙に何かが残るセルすべてが印刷範囲に入ります。値、罫線、塗りつぶし、結合セルの全体、画像です。何も印刷されない書式 — フォント、表示形式、白の塗りつぶし — は範囲を広げないので、空白ページが増えることはありません。結果は worksheet.getPrintContentExtent() で確認でき、印刷と PDF のすべての経路がこの同じ範囲を使います。
Pro の機能です。ページレイアウトのドキュメントをご覧ください。
書き出した xlsx が Excel でも同じ見た目に
書き出したブックを Excel で開くと、グリッドとの違いがいくつかありました。原因はすべて書き出し側にあり、すべて修正しました。
- 行の高さ。 ファイル形式はポイントを期待しているのにピクセルで書いていたため、Excel ではすべての行が3 分の 1 ほど高くなり、読み込み直すたびにさらに高くなっていました。これからは往復しても同じ高さに戻ります。
- 表示形式。
#,##0、[$¥]#,##0、日付、パーセントといった表示形式がまったく書き出されず、すべてのセルが「標準」になっていました。これからは保存され、表示形式を設定した空の入力欄にも残ります。 - セルに入力した数値(
135000)が文字列として書き出されていたため、Excel では計算にも表示形式にも使われませんでした。普通の数値は数値として書き出し、JavaScript から見ると数値に見えるだけの文字列(007や1e3)は文字列のままにします。 - 改行。 Excel は「折り返して全体を表示する」がオンのときしか改行を表示しないため、複数行の住所欄が 1 行に詰まって表示されていました。改行を含む値には、折り返しを自動でオンにして書き出します。
- 書式だけ設定した空のセルを空文字列として書いていました。Excel はそれを「入力あり」とみなすため、右揃えのラベルが左へはみ出せずに切れていました。これからは値なしで書き出します。
Pro の機能です。xlsx 入出力のドキュメントをご覧ください。
iPad と iPhone
- 入力が勝手に大文字化・自動修正されなくなりました。 ハードウェアキーボードを使うと、iPadOS が文頭の大文字化と自動修正をセルエディタにも適用し、
m2がM2として格納されていました。エディタでは両方とも、スペルチェックも含めてオフにしました。 - ダブルタップでズームしなくなりました。 セルを続けて素早くタップしてもページが拡大されず、グリッドをタップしたときの灰色のハイライトも出なくなりました。
- Safari でダウンロードできるようになりました。
saveAsPdfとsaveAsXlsxは、Safari が「表示/ダウンロード」の確認を経てファイルを読みに行く前にファイルを解放してしまい、何も保存されていませんでした。今はファイルを十分な時間保持します。 - iPad で印刷できるようになりました。 iPadOS は印刷シートを開いている間にページをレイアウトしますが、その時点でグリッドが印刷用の隠しページをすでに削除してしまうことがありました。
日本語入力とフォーム
- 全角数字は数値として格納します。 IME がオンのまま
120と入力すると文字列として入り、それを掛け算する数式は空欄になり、小数の入力規則には弾かれていました。Excel と同じく、全角の数字(と + - .)を半角に直せば数値になる入力は、半角の120として格納します。3階のように数字を含むだけの文字列は、入力したとおりに残します。 - フォームでは Enter 1 回で次へ。 IME の変換を確定する Enter は IME のものなので、フォームの数量欄では
120を確定する Enter と、次の欄へ進む Enter の 2 回が必要でした。formNavigationがオンのとき、確定した結果が数値だけなら、その Enter で値を確定して次の欄へ進みます。文字列は従来どおり確定のみなので、単語を確定してそのまま入力を続けられます。 - ドロップダウンの選択を元に戻せます。 セルの ▼ リストからマウスやタップで選ぶと値が直接書き込まれ、Ctrl/Cmd+Z で取り消せませんでした。これからはキーボードで選んだときと同じ経路を通るので取り消しが効き、フォームでは次の欄へ進みます。
その他
- ドロップダウンの後のセルが太字を失わなくなりました。 ドロップダウンのセルが Canvas のフォントを変えたまま戻さなかったため、その後に描かれるセルの太字や文字色が失われることがありました。すべてのセル型ハンドラーの呼び出しを囲って状態を戻すようにしたので、
registerCellTypeHandlerで登録した独自のハンドラーが後始末を忘れても影響しません。 - 数式を値で上書きすると、それを参照する数式が再計算されます。 これまでは次の全体再計算まで、古い結果のままでした。
- Lite の「Pro 機能」のコンソール警告が、実在するページを指すようになりました。 404 ではなく料金ページにリンクします。
- npm のパッケージページを整備しました。
@reogrid/proはプレースホルダーではない正式な README を同梱し、@reogrid/liteの README はcreateReogrid()を中心に書き直しました。ライセンスも両パッケージで明記しています。Lite は無料で商用利用可、Pro は有償ライセンスで、どちらもオープンソースではありません。
アップグレード
公開 API に破壊的変更はありませんが、挙動が 3 点変わります。
- グリッド上の Ctrl/Cmd+ホイールは、ブラウザのページではなくシートを拡大縮小します。
createReogrid({ wheelZoom: false })で従来の挙動に戻せます。getCellRect()/getRangeRect()/getImageRect()で独自の要素を重ねている場合は、worksheet.getZoom()を掛けてください。 - 読み込んだ xlsx の書式なしセルは、Arial 10 ではなくブックの既定フォントで表示されます。
- 全角数字で入力した数値は、数値として格納されます。
また、ワークシートの loadXlsx / loadFromBuffer / loadFromFile / loadFromUrl は非推奨になりました。引き続き動き、警告を一度だけ出し、2.0 で削除します。ブックを開くなら grid.loadFromFile() などのインスタンスのメソッドに、シート 1 枚だけなら worksheet.loadSheetFrom* に切り替えてください。
npm i @reogrid/[email protected]
Lite はライセンスキー不要で npm から利用できます。
npm i @reogrid/[email protected]
v1.7 の完全な変更履歴はリリースノート、API の詳細は表示倍率、ハイパーリンク、PDF エクスポート、ページレイアウト、xlsx 入出力の各ドキュメント、体験は表示倍率とハイパーリンクのデモをご覧ください。お手元のファイルを xlsx ビューアーで開けば、リンクが開き、Ctrl+ホイールで拡大縮小できます。ブック全体を 1 つの PDF にするなら xlsx → PDF 変換ツールをどうぞ。Lite / Pro の比較は料金表にあります。