Old-School Front-End / 2026 ・ No.13

いにしえのフロントエンド入門

第13回 集計と印刷 ── 月ごとに足して、棒グラフを手で描いて、紙に出す

公開

古井 一郎(承認者)のダッシュボード。2026年7月〜10月。集計した経費 282,081 円・件数 18 件・承認待ち 11,900 円。月ごとの合計の棒グラフは 7月 67,720、8月 57,660、9月 65,730、10月 90,971 で、右に同じ数の表。下に区分ごとの内訳(交通費 126,170 円 44.7% など、赤い横棒つき)と、人ごとの合計(新井 太郎 9件 181,301 円、佐藤 健 5件 53,900 円、山田 花子 4件 46,880 円)
今回のゴール。承認済み・精算済みの申請を、月ごと・区分ごと・人ごとに足して見せるダッシュボード(筆者のデモページを、練習用のサーバー経由でクラウド上の Chromium で撮影。Playwright で実際に操作したところ。2026年10月19日撮影、ページの時計は 2026年10月17日 15:00 に固定。アカウントはデモ専用の架空のもの。ブラウザの枠は説明のために描き足したもの)

AI に頼まず、一行ずつ手で書く「いにしえの」フロントエンド入門。第13回は集計と印刷です。
これまでメニューにあった「ダッシュボード」は、押しても何も無いページでした。
今回はそこに、申請を月ごと・区分ごと・人ごとに足した表と、SVG で手描きした棒グラフを置きます。
そして、経理の部長が大好きな紙に、きれいに出るようにします。途中で、日付と時間帯の古い罠も踏みます。

一

今回のゴール

古井経理の部長から頼まれた。「7月からの経費を、月ごとにまとめて紙でくれ」。

新井紙……ですか。PDF じゃなくて?

古井紙だ。赤ペンで丸をつけるらしい。

新井グラフも要りますよね。Chart.js を入れれば一行ですよ。AI に頼めば3秒です。

古井ライブラリを使うのは悪くない。だが今日は手で描く。棒グラフは、結局のところ長方形を並べるだけだからな。中身が分かっていれば、ライブラリが変な絵を出したときに気づける。

いまここ:第13回

1 準備 2 HTML の骨組み 3 フォーム 4 CSS の基本 5 レイアウト 6 Bootstrap 7 JS の基本 8 DOM・イベント 9 入力チェック 10 通信 11 ログイン 12 承認の流れ 13 集計と印刷 14 jQuery 15 仕上げ いまここ 準備 HTML CSS JavaScript 実戦 仕上げ
  • 何を数えるか決める ── 承認済み・精算済みだけを足す。承認待ちは別の枠で見せる
  • グループに分けて足す ── 月ごと・区分ごと・人ごと。どれも同じ関数で、鍵の作り方だけ変える
  • 棒グラフを手で描く ── SVG の長方形を JavaScript で並べる。数字は表にも出す
  • 紙に出す ── 印刷用の CSS で、メニューを消し、日時と印刷した人を足し、ページの切れ目を整える

相手は前回の練習用サーバーに、集計用の /api/report を足したものです。データも少し増やしました。7〜9月の分は、古井さんがこれまで Excel の台帳で管理していたものを移した、という設定です(番号が XL-2026-07-01 のような形なのはそのため)。申請者に佐藤 健さん([email protected] / demo-sato)も加わりました。どれもデモ専用の架空のデータです。

二

何を数えるか

「経費の合計」と一口に言っても、何を足すかで数字が変わります。申請中のものを入れるのか。差し戻されたものは? 下書きは? コードを書く前に、これを決めます。

足すのは「お金が出る・出た」ものだけ

承認済み 精算済み 申請中 下書き 差し戻し 集計に入れる お金が出る・出たもの 承認待ち(別に見せる) 数えない まだ出す前・出し直し待ち 同じ「合計」でも、何を入れたかで数字が変わる。決めたら画面に書いておく 決めるのは画面ではなく、使う人(経理)との約束

今回は経理の部長と相談して(という設定で)、承認済みと精算済みを足すことにしました。申請中のものは、まだ出るか分からないので合計には入れず、「承認待ち」として別のカードに出します。下書きと差し戻しは数えません。この約束は、コードの先頭に名前をつけて置いておきます。

const COUNTED = ['approved', 'paid']; // 集計に入れるのは、お金が出る(出た)ものだけ

だれの申請が見えるかは、第11回と同じくサーバーが決めます。/api/report は、承認者には全員の申請を、申請者には自分の申請だけを返します。画面は、受け取ったものを足すだけです。

三

グループに分けて足す

月ごとの合計は、「申請を月で分けて、それぞれ足す」ことです。分ける目印(鍵)を作って、鍵ごとの箱に足していきます。箱には Map を使います。

鍵を作って、同じ鍵の箱に足していく

例:4件の申請 2026-07-01 1,980 2026-07-18 49,600 2026-08-06 1,240 2026-09-01 38,440 new Map() '2026-07' { count: 2, amount: 51,580 } '2026-08' { count: 1, amount: 1,240 } '2026-09' { count: 1, amount: 38,440 } '2026-10' 無い → monthRange で 0 に ① 鍵を作る:monthOf(c.date) で先頭の7文字 ② Map から取り出して足す。無ければ { count: 0, amount: 0 } から Map に入るのは「あった鍵」だけ。無い月はグラフから消える 区分ごと・人ごとも同じ sumBy。鍵の作り方を変えるだけ
/** items を keyOf(item) ごとに分けて、件数と金額を足す */
function sumBy(items, keyOf) {
  const totals = new Map();
  for (const item of items) {
    const key = keyOf(item);
    const t = totals.get(key) ?? { count: 0, amount: 0 };
    t.count += 1;
    t.amount += item.amount;
    totals.set(key, t);
  }
  return totals;
}

第7回の calcTotal は「全部を1つに足す」関数でした。sumBy は「鍵ごとに足す」版です。鍵の作り方を関数で渡すので、月ごとにも、区分ごとにも、人ごとにも使えます。?? は「左が無ければ右」。初めて出てきた鍵なら、0 件・0 円の箱から始めます。reduce を使って1行で書く人もいますが、ここでは第7回と同じ for…of で、一歩ずつ書きました。

使うところはこうなります。

const counted = report.claims.filter((c) => COUNTED.includes(c.status));
const waiting = report.claims.filter((c) => c.status === 'submitted');
const total = calcTotal(counted); // 第7回の関数

// 月ごと
const byMonth = sumBy(counted, (c) => monthOf(c.date));
const months = monthRange(report.from, report.to).map((m) => ({ label: monthLabel(m), ...(byMonth.get(m) ?? { count: 0, amount: 0 }) }));
drawBars(document.querySelector('#chart-month'), months.map((m) => ({ label: m.label, value: m.amount })));
document.querySelector('#by-month').replaceChildren(...months.map((m) => row([m.label, String(m.count), yen(m.amount)], [1, 2])));
document.querySelector('#by-month-total').replaceChildren(row(['合計', String(counted.length), yen(total)], [1, 2]));

// 区分は申請ではなく明細の行についているので、行を集めてから足す
const lines = counted.flatMap((c) => c.rows);
const byCat = [...sumBy(lines, (r) => r.category)].sort((a, b) => b[1].amount - a[1].amount);
  • 月の一覧は、Map からではなく monthRange で作る。Map には「申請があった月」しか入らないので、申請の無い月はグラフから黙って消えてしまう
  • 区分は申請ではなく明細の行についているので、flatMap で行を一列に並べてから足す。ニューヨーク出張なら、交通費・宿泊費・会議費の3行に分かれて入る

新井Map じゃなくて、ふつうのオブジェクト {} でもできますよね?

古井できる。ただ、オブジェクトには最初から toString なんかがぶら下がっている。鍵に何が来るか分からないときは、何も入っていない Map のほうが安心だ。

四

日付は文字のまま切る

月の鍵は '2026-10-16'.slice(0, 7)、つまり先頭の7文字です。「日付なんだから、Date にして getMonth() で取るのが正しいのでは?」と思うかもしれません。やってみましょう。

# 同じ文字から作った Date の「月」(Chromium で時間帯を変えて実測)
時間帯 new Date('2026-07-01') new Date('2026-07-01T00:00') .slice(0, 7)
東京 7月 7月 2026-07
上海 7月 7月 2026-07
ロンドン 7月 7月 2026-07
ロサンゼルス 6月 7月 2026-07   ← 6月30日の夕方になった

# ロサンゼルスでの new Date('2026-07-01') の中身:Tue Jun 30 2026 17:00:00

ロサンゼルスだけ、7月が6月になりました。日付だけの '2026-07-01' は、世界時(UTC)の7月1日0時として読まれる決まりです。その瞬間、東京は7月1日の朝9時ですが、ロサンゼルスはまだ6月30日の夕方5時。だから「何月?」と聞くと6月と答えます。困ったことに、時刻をつけた '2026-07-01T00:00' のほうは「その場所の0時」として読まれます。第9回では「今日」がずれましたが、今回は「月」がずれます。

同じ new Date('2026-07-01') が、場所によって別の日付になる

UTC 6/30 7/1 7/1 0:00 東京 6/30 7/1 7/1 9:00 ロサンゼルス 6/30 7/1 6/30 17:00 new Date('2026-07-01') 日付だけの '2026-07-01' は、世界時(UTC)の0時として読まれる 時刻つきの '2026-07-01T00:00' は、その場所の0時(ここが紛らわしい) 月ごとに分けるだけなら、Date にしない。'2026-07-01'.slice(0, 7) で十分

ダッシュボードで試しました。ロサンゼルスの時間のブラウザで開き、monthOf を「Date にしてから月を取る」版に差し替えたところです。

# 月ごとの表:ロサンゼルスの時間で開いたとき(Chromium で実測)
月 文字で切る(正しい) Date にしてから月を取る
7月  4 /  67,720  4 /  87,740
8月  4 /  57,660  4 /  74,100
9月  4 /  65,730  3 /  27,290
10月  6 /  90,971  6 /  90,971

表の行を足すと 17 件 / 280,101
合計の行 18 件 / 282,081

1日に出た申請が、ぜんぶ前の月に引っ越しました。7月1日の 1,980 円は6月に行き、表の範囲から消えています。表の行を足すと 17 件・280,101 円なのに、合計の行は 18 件・282,081 円。合計の行は申請を全部足しているので、ずれません。手で集計表を作る経理の人が、最後に縦と横を足して確かめる「検算」は、こういうときのためにあります。東京で試している限り、この罠は見えません。

月で分けるだけなら、日付を Date にしないのがいちばん安全です。画面に「10月」と出すときも、new Date(2026, 9, 1) のように年・月・日を数で渡して作ります。この形は、その場所の時間で作られるので、ずれません。

/** '2026-10-16' → '2026-10'。日付は Date にせず、文字のまま切る(四の罠) */
function monthOf(date) {
  return date.slice(0, 7);
}

/** '2026-07' から '2026-10' までの月を、抜けなく並べる(申請のない月も 0 で出す) */
function monthRange(from, to) {
  const months = [];
  let [y, m] = from.split('-').map(Number);
  const [toY, toM] = to.split('-').map(Number);
  while (y < toY || (y === toY && m <= toM)) {
    months.push(`${y}-${String(m).padStart(2, '0')}`);
    m += 1;
    if (m > 12) { y += 1; m = 1; }
  }
  return months;
}

/** '2026-10' → その言語の月の書き方(ja なら「10月」) */
function monthLabel(month) {
  const [y, m] = month.split('-').map(Number);
  return new Date(y, m - 1, 1).toLocaleDateString(LOC, { month: 'short' }); // 年・月・日を数で渡すと、その場所の時間で作られる
}
五

棒グラフを手で描く

グラフは SVG で描きます。HTML の中に <svg id="chart-month"> を1つ置いておき、JavaScript で長方形(rect)と文字(text)を足していきます。まず座標の決まりを押さえます。

SVG の y は下へ増える

x y (0, 0) h y H − BOTTOM 原点は左上、y は下向き 数学のグラフとは上下が逆 y = H − BOTTOM − h 棒の上の端。ここから下へ h viewBox="0 0 640 240" 座標の広さ(大きさは CSS) 棒の高さは「金額 ÷ 最大の金額」で割り振る。いちばん大きい月が、いっぱいの高さ 要素は createElementNS で作る(五で実測)
/** SVG の要素を作る。createElement ではなく createElementNS(名前空間が要る) */
function svgEl(name, attrs = {}, text = '') {
  const el = document.createElementNS(SVG_NS, name);
  for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, v);
  el.textContent = text;
  return el;
}

/** 縦の棒グラフを描く。data = [{ label, value }] */
function drawBars(svg, data) {
  const W = 640, H = 240, TOP = 28, BOTTOM = 30;
  const max = Math.max(...data.map((d) => d.value), 1);
  const step = W / data.length;
  const barW = step * 0.55;
  const parts = [svgEl('line', { x1: 0, y1: H - BOTTOM, x2: W, y2: H - BOTTOM, class: 'chart-axis' })];
  data.forEach((d, i) => {
    const h = (H - TOP - BOTTOM) * d.value / max;
    const x = step * i + (step - barW) / 2;
    const y = H - BOTTOM - h; // SVG の y は下向き。棒の上の端 = 軸の位置 − 棒の高さ
    parts.push(svgEl('rect', { x, y, width: barW, height: h, class: 'chart-bar' }));
    parts.push(svgEl('text', { x: x + barW / 2, y: y - 8, class: 'chart-value' }, d.value.toLocaleString(LOC)));
    parts.push(svgEl('text', { x: x + barW / 2, y: H - 8, class: 'chart-label' }, d.label));
  });
  svg.setAttribute('viewBox', `0 0 ${W} ${H}`);
  svg.replaceChildren(...parts);
}

いちばん大きい月の金額を「いっぱいの高さ」にして、ほかの月は割合で縮めます。棒の上には金額、下には月を書きます。色は SVG の属性ではなく CSS(.chart-bar { fill: #1f4e6b; })で付けました。viewBox で座標の広さを 640×240 と決めてあるので、画面の幅が変わっても、CSS の width: 100% だけで伸び縮みします。

ここに、いにしえの人が一度は踏む罠があります。svgEl の中は createElement ではなく createElementNS です。ふつうの createElement('rect') で作るとどうなるか。

# <svg> の中に rect を足してみる(Chromium で実測)
作り方 名前空間 中身 大きさ
createElement http://www.w3.org/1999/xhtml HTMLUnknownElement 0 × 0   ← 描かれない
createElementNS http://www.w3.org/2000/svg SVGRectElement 84 × 42

createElement で作った rect は、HTML の「知らない要素」(HTMLUnknownElement)になり、大きさ 0 で何も描かれません。エラーも出ません。SVG の要素は HTML とは別の名前空間(http://www.w3.org/2000/svg)に属しているので、それを名乗って作る必要があります。

グラフには role="img" と aria-labelledby を付け、下の説明文(figcaption)を名前にしました。数字そのものは、隣の表にも全部出しています。グラフは見てすぐ分かるための絵、正確な数字は表。印刷した紙に赤ペンで丸をつけるのも、たぶん表のほうです。

六

割合の端数

区分ごとの内訳には、全体に対する割合を小数1桁で出しました。足してみます。

# 区分ごとの割合(toFixed(1)。Chromium で実測)
区分 金額(円) 割合
交通費   126,170  44.7%
宿泊費    69,058  24.5%
会議・交際費    45,723  16.2%
その他    30,280  10.7%
消耗品    10,850   3.8%
足すと  99.9%

99.9% になりました。計算の間違いではありません。それぞれを四捨五入すると、切り上げた分と切り捨てた分が打ち消し合わずに残ることがあるのです。合計の行に「100.0%」と書くと、紙の上で検算した人に「合わない」と言われます。そこで合計の行には割合を書かず、表の下に「足しても 100.0% にならないことがあります」と一言添えました。どうしても合わせたいときは、いちばん端数の大きいものに差を寄せる、といった決まりを経理と相談して決めます。

新井0.1% くらい、誰も気にしないですよ。

古井経理の部長は気にする。赤ペンで丸がつく。

七

紙に出す

「印刷する」ボタンがすることは、window.print() でブラウザの印刷の画面を開くだけです。紙にどう出るかは、CSS の @media print の中に書きます。画面と紙では、要るものが違います。

紙では押せないものを消し、紙でしか要らないものを足す

画面 紙 CSS ヘッダー(メニュー・ログアウト) d-print-none 「印刷する」ボタン d-print-none 印刷用の見出し(だれが・いつ) d-print-block グラフの棒(SVG の fill) fill 区分の横棒(背景色) print-color-adjust 表の行(ページの切れ目) break-inside 紙では押せないものを消し、紙でしか要らないもの(日時・印刷した人)を足す 背景色は、ブラウザの初期設定では印刷されない(インクの節約)

消す・足すは、Bootstrap のクラスでできます。d-print-none は印刷のときだけ消す、d-none d-print-block は画面では消して印刷のときだけ出す、です。

<header class="d-print-none navbar navbar-expand-md navbar-dark brand-bar"> … </header>

<p class="print-head d-none d-print-block"><strong>経費レポート</strong> <span id="print-meta"></span></p>
<div class="d-flex flex-wrap justify-content-between align-items-center gap-3 mb-1">
  <h1 class="h3 mb-0">ダッシュボード</h1>
  <button type="button" class="btn btn-outline-secondary d-print-none" id="print">印刷する</button>
…
<footer class="d-print-none py-4 text-center text-body-secondary"> … </footer>

印刷用の見出しには、範囲・印刷した日時・印刷した人を JavaScript で入れます。紙は机の上で混ざるので、「いつの、だれが出した紙か」が書いてあると助かります。残りは自分で CSS を書きます。

/* 第13回:区分の割合を表す横棒(背景色で塗る) */
.share-bar {
  display: block;
  height: .6rem;
  border-radius: 2px;
  background-color: #c0392b;
}

/* 第13回:印刷のとき */
@page {
  size: A4;
  margin: 14mm 12mm;
}
@media print {
  body {
    background: #fff !important;
    font-size: 10pt;
  }
  .container {
    max-width: none;
    padding: 0;
  }
  .keep,
  .card,
  tr {
    break-inside: avoid; /* 途中でページが切れないように */
  }
  h2 {
    break-after: avoid; /* 見出しだけがページの最後に残らないように */
  }
  .share-bar {
    -webkit-print-color-adjust: exact; /* 古いブラウザ向け */
    print-color-adjust: exact; /* 背景色も印刷する(ブラウザは初期設定で背景を省く) */
  }
  .report-list th,
  .report-list td {
    white-space: nowrap; /* 番号や日付を途中で折り返さない */
  }
  .report-list td:nth-child(4) {
    white-space: normal; /* 件名だけは折り返してよい */
  }
  .print-head {
    border-bottom: 2px solid #212529;
    padding-bottom: .25rem;
  }
}
  • @page で紙の大きさと余白を決める
  • break-inside: avoid で、カードや表の行、グラフのまとまりを途中で切らない
  • print-color-adjust: exact で、横棒の背景色も印刷させる

最後の1つが要るのは、ブラウザが初期設定では背景色や背景画像を印刷しないからです(インクの節約のため。印刷の画面の「背景のグラフィック」にチェックを入れると印刷されます)。区分の横棒は背景色で塗っているので、そのままだと紙では消えます。一方、グラフの棒は SVG の fill、つまり絵そのものなので消えません。Chromium で PDF に出して確かめました。

# 印刷のときの見え方(Chromium。@media print をまねて、PDF にも出して実測)
調べたもの 結果
ヘッダー(d-print-none) display: none → 印刷されない
「印刷する」ボタン display: none
印刷用の見出し(d-print-block) display: block → 印刷だけに出る
A4 の PDF 3 ページ。3ページ目の表にも見出しの行が付いた
グラフの棒(SVG の fill) 背景を印刷しない設定でも出た
区分の横棒(CSS の背景色) print-color-adjust: exact あり → 出た / なし → 消えた
A4 で PDF に出した3ページを並べたもの。1ページ目は上に「経費レポート 2026年7月 – 2026年10月 ・ 2026/10/17 15:00 ・ 古井 一郎」の見出しがあり、ヘッダーのメニューと印刷のボタンは無い。まとめのカード、月ごとの棒グラフと表。2ページ目は区分ごとの内訳(赤い横棒つき)、人ごとの合計、集計に入れた申請の表。3ページ目は申請の表の続きで、上に見出しの行がもう一度付いている
「印刷する」から A4 に。メニューは消え、日時と印刷した人の見出しが付き、表はページをまたいでも見出しの行が付く(筆者のデモページを、練習用のサーバー経由でクラウド上の Chromium で撮影。Playwright で実際に操作したところ。2026年10月19日撮影、ページの時計は 2026年10月17日 15:00 に固定。アカウントはデモ専用の架空のもの。ブラウザの枠は説明のために描き足したもの)

1ページ目にまとめと月ごとのグラフ、2ページ目に内訳と申請の一覧、3ページ目に一覧の続き。3ページ目の表にも、見出しの行がもう一度付いています(表の thead は、ページをまたぐと繰り返して印刷されます)。区分の内訳は1ページ目の下に入りきらなかったので、break-inside: avoid によって丸ごと2ページ目へ送られました。なお、実際にブラウザから印刷すると、ブラウザが余白に URL や日付を足すことがあります。これは印刷の画面の「ヘッダーとフッター」で消せます。

八

動かしてみる

練習用サーバーを立て、古井さん(承認者)と新井さん(申請者)で、Playwright で実際に開きました。ロサンゼルスの時間のブラウザと、背景を印刷しない PDF も試しています。

3つの場面。1 新井 太郎(申請者)が開いたダッシュボード。集計した経費 181,301 円・9 件・承認待ち 0 円、月ごとは 7月 51,580、8月 5,200、9月 38,440、10月 86,081。人ごとの表は無い。2 ロサンゼルスの時間で、日付を Date にしてから月を取ったときの月ごとの合計。7月 87,740(4件)、8月 74,100(4件)、9月 27,290(3件)、10月 90,971(6件)なのに、合計の行は 18 件 282,081 のまま。3 背景を印刷しない設定で、print-color-adjust を外して PDF にした区分ごとの内訳。数字と割合はあるが、赤い横棒が消えている
①申請者が開くと、②ロサンゼルスの時間で Date にすると、③背景を印刷しない設定で(筆者のデモページを、練習用のサーバー経由でクラウド上の Chromium で撮影。Playwright で実際に操作したところ。2026年10月19日撮影、ページの時計は 2026年10月17日 15:00 に固定。アカウントはデモ専用の架空のもの。ブラウザの枠は説明のために描き足したもの)
# だれが開いたか(Chromium で実測)
 古井(承認者) 新井(申請者)
集計した経費 282,081 円 181,301 円
件数 18 件 9 件
承認待ち 11,900 円 0 円
人ごとの表 出る 出ない
集計に入れた申請 18 行 9 行
  • 同じ dashboard.html でも、古井さんには全員の 18 件、新井さんには自分の 9 件。新井さんの画面には人ごとの表が出ない(サーバーも他人の申請を渡していない)
  • 新井さんの承認待ちは 0 円。前回、0012 と 0007 が承認されたから
  • ロサンゼルスの時間で Date にすると、1日の申請が前の月へずれ、表の行と合計の行が合わなくなった。文字のまま切る版では、どの時間帯でも同じ数字
  • 背景を印刷しない設定では、print-color-adjust を外すと横棒が消え、付けると残った
POINT

集計の画面で大事なのは、見た目より数字が合っていることです。何を数えるかを決めて書いておく。日付は文字のまま扱う。表の行と合計の行が合うか、時間帯を変えて確かめる。グラフはその数字を絵にしたもので、紙に出すときは、紙を読む人に要るものだけを残します。

結 — 一枚まとめと次回予告

  1. 合計の前に、何を数えるかを決める。今回は承認済み・精算済み。承認待ちは別に見せる。
  2. グループに分けて足すのは Map と鍵。月の一覧は monthRange で作り、0 の月も消さない。
  3. 月で分けるだけなら、日付は Date にせず文字のまま切る。'2026-07-01' は世界時の0時として読まれる。
  4. SVG の要素は createElementNS で作る。y は下向き。正確な数字は表にも出す。
  5. 割合の端数は、足しても 100.0% にならないことがある。書き方を決めて、注記する。
  6. 印刷は @media print。d-print-none で消し、@page と break-inside で整え、背景色は print-color-adjust で。

新井部長に紙を渡したら、7月の福岡出張に赤丸がつきました。「これは何だ」って。

古井それはお前の出張だ。説明してこい。……次回は jQuery。第8〜10回で手で書いた DOM・イベント・通信を jQuery で書き直して、並べて比べる。いにしえの中の、さらにいにしえだ。

新井ドルマーク($)だらけのやつですね。

参考

ページの写真は、筆者が作ったデモページを、練習用のサーバー(Node.js)経由で2026年10月19日にクラウド上の Chromium(Playwright)で撮影したものです(ページの時計は 2026年10月17日 15:00 に固定)。撮影環境には Hiragino Sans がないため、日本語は Noto Sans CJK JP で表示されています。アカウント・申請・金額は、練習用サーバーの架空のデータです。集計の数字、時間帯ごとの Date の月、createElement と createElementNS の違い、割合の合計、印刷のときの表示と A4 の PDF(ページ数、横棒とグラフの有無)は、同じ Chromium を Playwright で操作・実行して記録した実際の値です。印刷の写真は、その PDF を画像にして並べたものです。各ページの HTML は Nu Html Checker(vnu)でエラーがないことを確かめています。登場人物は架空の人物です。

いにしえのフロントエンド入門 第13回 — 集計と印刷 / 2026.10

← 記事一覧へ