1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

paged.jsで作るPagedMedia帳票

Last updated at Posted at 2025-01-21
javascript css

帳票、契約書などのビジネス文書をページ化メディアで印刷するまでのロードマップ。

Paged Media

Paged MediaとはCSS3のページ化メディア= 版物の用紙サイズ、ふち、改ページなどのスタイルを指定するためのど真ん中の仕様セット。

ページ化メディアとかページ組版とかPaged Mediaとか、いかにも専門チックな用語が登場するが、ページ概念のあるスタイルセットという理解でOK。

しかしながら、いま現在もメジャーブラウザの対応状況はまちまちだそうで、ブラウザ互換性という意味で安定していないようだ。

ライブプレビュー

それでもPagedMediaで印刷をスタイリング出来るのは結構だが、(印刷ダイアログではなく)スクリーンでも精度の高いライブプレビューを再現したいというもの。

残念ながらPagedMediaはスクリーンのそれとは異なるため、スクリーンのスタイルは別途作り込む必要がある。

単ページの帳票なら印刷ダイアログっぽく用紙サイズのページ要素のコンテナを作ってCSSオンリーでスタイリング出来るが、
可変ページ概念が出てくるとページ要素の増減、ページを横断する段落/テーブル要素の分割&リビルドとか、画像は改ページして、、気の遠くなる調整実装が待っている。

paged.js

そこで、上記の気の遠くなるページ化メディア調整をしてくれるpaged.jsというオープンソースのポリフィルがある。

スクリーンでPagedMediaオリエントにDOMを再構築しれるので、スクリーンを印刷に合わせるのではなくて、印刷にスクリーンを再現させることができる。

使用するPaged Media仕様は@pageディレクティブの用紙サイズsize/マージンmargin/ふち印字@bottom-center/改ページ制御break-insideあたり

実装

スクリプト

次のCDNスクリプトを読み込むと自動的にpaged.jsのポリフィルが走る。
Getting Started with Paged.js —

html
<script src="https://unpkg.com/pagedjs/dist/paged.polyfill.js"></script>

2024.3.18現在 CDNで公開中のステーブルバージョンv0.4.3

javascript
/**
 * @license Paged.js v0.4.3 | MIT | https://gitlab.coko.foundation/pagedjs/pagedjs
 */

文書を組む

paged.jsは@media printクエリを展開する

paged.jsはPagedMediaを完全擬態しようとするので@media printクエリを展開する。

逆に@media screenクエリはデフォルトで無視するため、スクリーンにのみ適用したいスタイルはlinkタグやstyleタグにmedia=“screen”指定することで適用してくれる。

html
<!-- linkはmedia=“screen”を入れる -->
<link rel="stylesheet" type="text/css" href="screen.css" media="screen">

<!-- styleタグにmedia="screen" -->
<style media="screen">  
  p {  
    color: red;  
  }  
</style>

じつは上記仕様に至るに破壊的な変更があり、v0.4.0以前はスクリーン向けには本来の@media screenクエリを展開していたが、スクリーンも完全なPagedMediaオリエントへと変遷したようだ。

以前v0.2を利用していて、今回現最新版のv0.4.3を試したところ、プレビュー専用のスタイルが言うことを聞かないので頭を悩ませた。

バージョン 0.4.1 以降、Pages.js は @media screenのメディア クエリ内にある CSS の解析を停止します。
Paged Break, the long overdue update —

キャンバス

以下のスタイルを当てると用紙サイズのキャンバスができるので、あとは画面ノリで組むだけ。

image 3.png

html
<!-- スクリーンにのみあてるスタイル -->
<style>
	@page{
		/* A4・横向きでサイズ指定 */
    	size: A4 landscape;
		/* ふちを20mmとる */
    	margin: 20mm;
		/* 各ページのフッター中央に { 現在ページ } / { 総ページ } を印字 */
    	@bottom-center{
        	content: counter(page) '/' counter(pages);
    	}
	}
</style>

<!-- スクリーンにのみあてるスタイル media="screen" -->
<style media="screen">
body{
	/* 背景色を指定してプレビューぽくする */
    background: #666;
}

/* ポリフィルがあてるシートとそのコンテナブラッシュアップ */

/* コンテナクラス は縦並びでセンタリングする*/
.pagedjs_pages {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    width: calc(var(--pagedjs-width));
    margin: 0 auto;
}
/* ポリフィルがあてるページシートクラス */
.pagedjs_page {
    margin: 5mm;
    border: 1px solid #333;
    background: #fff;
}
</style>

スクリーンに帳票を組んだ結果

image 4.png

印刷プレビュー
スクリーンをそのまま再現できている

image 6.png

シートから溢れた要素を適宜改ページしてくれる

image 5.png

表の改ページ精度問題

シートから溢れた文字列とかブロック要素は問題なく改ページしてくれるが、TableGridは下図のようにちょん切れたり(widthもありえない数字)、不可視領域に吹っ飛んだり動作不安定でまともに使えず、公式フォーラムでもこの手のイシューがちらほらあるが猛者たちが調整で頑張っている。

Paged.JS and CSS Flex / Grid
Paged.js TABLE breaks · Issue #149 · pagedjs/pagedjs
Chrome: Missing row content on page break within table (v0.4.3 vs v0.5.0-beta.0) (#431) · イシュー · pagedjs / pagedjs · GitLab

image 7.png

表に関しては下記のようなFlexテーブルで代替する。それでもちょん切れる場合は行要素に改ページ禁止break-inside: avoid;を当てるとうまくいく。

html
<style>
    .flex-column{ display: flex; flex-flow: column;}
    .flex-row{ display: flex; flex-flow: row;}
    .flex-cell {
        flex-grow: 1; padding: 2pt 6pt 2pt 6pt;
        border: 1px solid #000;
    }

    /* 隣接するセル間のボーダーを重複させない */
    .flex-row .flex-cell:not(:first-child) {
        border-left: none;
    }
    .flex-row:not(:first-child) .flex-cell {
        border-top: none;
    }
    .flex-row.head .flex-cell {
        text-align: center;
    }
    .flex-cell:nth-of-type(1){flex-basis: 30%;}
    .flex-cell:nth-of-type(2){flex-basis: 20%;}
    .flex-cell:nth-of-type(3){flex-basis: 10%;}
    .flex-cell:nth-of-type(4){flex-basis: 10%;}
    .flex-cell:nth-of-type(5){flex-basis: 30%;}

    /* 項目と表途中の改ページ禁止 */
    .no-break{
        break-inside: avoid;
    }

</style>

<div class="flex-column">
    <div class="flex-row head">
        <div class="flex-cell">品名</div>
        <div class="flex-cell">規格</div>
        <div class="flex-cell">単価</div>
        <div class="flex-cell">金額</div>
        <div class="flex-cell">摘要</div>
    </div>

    <div class="flex-row">
        <div class="flex-cell">1</div><div class="flex-cell">2</div><div class="flex-cell">3</div><div class="flex-cell">4</div><div class="flex-cell">5</div>
    </div>
    <div class="flex-row">
        <div class="flex-cell">2</div><div class="flex-cell">2</div><div class="flex-cell">3</div><div class="flex-cell">4</div><div class="flex-cell">5</div>
    </div>
    <div class="flex-row">
        <div class="flex-cell">3</div><div class="flex-cell">2</div><div class="flex-cell">3</div><div class="flex-cell">4</div><div class="flex-cell">5</div>
    </div>
    <div class="flex-row">
        <div class="flex-cell">4</div><div class="flex-cell">2</div><div class="flex-cell">3</div><div class="flex-cell">4</div><div class="flex-cell">5</div>
    </div>
    <div class="flex-row">
        <div class="flex-cell">5</div><div class="flex-cell">2</div><div class="flex-cell">3</div><div class="flex-cell">4</div><div class="flex-cell">5</div>
    </div>
    <div class="flex-row no-break">
        <div class="flex-cell" style="flex-basis: 100%; height: 36mm;">
            【備考欄】
        </div>
    </div>
</div>

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?