印刷用の WEB ページを作成する

印刷用の WEB ページを作成するのに必要なことについて説明します。

具体的なコード例は記事の後ろに記載しています。

# 1. ページ化メディア

印刷するなどしてコンテンツをページごとに分割するような表現方法を「ページ化メディア」と呼びます。

「ページ化メディア」用の CSS プロパティを使用することで、印刷用のスタイルを記述することができます。

参考「[Paged media - CSS: Cascading Style Sheets | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/Paged_Media)」

## 1.1. ブロックレベル要素の区切り方を設定する `break-*` 系プロパティ

改ページ等でブロックレベル要素が区切られるとき、その区切り方を設定する CSS プロパティとして以下のようなものがあります:

- `break-inside`: ボックス中の区切り方を設定
- `break-before`: ボックス前の区切り方を設定
- `break-after`: ボックス後の区切り方を設定

参考「[break-inside - CSS: Cascading Style Sheets | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/break-inside)」
参考「[break-before - CSS: Cascading Style Sheets | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/break-before)」
参考「[break-after - CSS: Cascading Style Sheets | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/break-after)」

ちなみにこれらの CSS プロパティの機能は元々 `page-break-inside`、`page-break-before`、`page-break-after` というプロパティで提供されていました。

この置き換えにより、一部の値の指定方法が変更されています:

- `page-break-before: always;` -&gt; `break-before: page;`
- `page-break-after: always;` -&gt; `break-after: page;`

参考「[Page break aliases - break-before - CSS: Cascading Style Sheets | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/break-before#page_break_aliases)」
参考「[Page break aliases - break-after - CSS: Cascading Style Sheets | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/break-after#page_break_aliases)」

## 1.2. `@page` ルール

`@page` ルールを使用することで、印刷用の特殊な CSS を記述することができます。

`@page` ルール内で使用可能な記述子およびプロパティは以下の通りです:

- `page-orientation`: 印刷時のページのコンテンツの向きを指定する
- `size`: 印刷時のページの大きさと用紙の向きを指定する
- ページプロパティ
    - `margin`: 印刷時のページの余白を指定する
    - その他 (下記の参考記事参照)
- マージンアットルール
    - `@top-left` ルール等
    - その他 (下記の参考記事参照)

※ `@page` ルールには他にも機能がありますが、本記事では不説明。
**※ `margin` 以外のページプロパティは 2023-03-21 現在は多くの WEB ブラウザで未実装です。**
※ページプロパティおよびマージンアットルールのその他の具体的なプロパティは略。
※マージンアットルールについては本記事では不説明。

それぞれ下記の参考記事を参照して下さい。

参考「[@page - CSS: Cascading Style Sheets | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/@page)」(`@page` ルール)
参考「[page-properties - @page - CSS: Cascading Style Sheets | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/@page#page-properties)」(ページプロパティ)
参考「[margin at-rules - @page - CSS: Cascading Style Sheets | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/@page#margin_at-rules)」(マージンアットルール)
参考「[Table 1 Page-Margin Box Definitions - CSS Paged Media Module Level 3](https://w3c.github.io/csswg-drafts/css-page/#margin-box-def)」(マージンアットルールで使用するページマージンボックスの定義)

## 1.3. ブロックコンテナを区切るときの最小行数を設定するプロパティ

改ページ等でブロックコンテナ要素が区切られるとき、その区切られた内容の最小行数を設定する CSS プロパティとして以下のようなものがあります:

- `orphans`: 区切り前の最小行数を指定する
- `widows`: 区切り後の最小行数を指定する

参考「[orphans - CSS: Cascading Style Sheets | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/orphans)」
参考「[widows - CSS: Cascading Style Sheets | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/widows)」

# 2. `@media print` で印刷時のスタイルを設定する

`@media` ルールでメディアクエリ `print` を指定することで、印刷時用の CSS を設定することができます。

(前述の `@page` ルールに関しては元々印刷用のスタイルなため、`@media print` に含める必要はありません。)

例えば以下のように CSS を記述すると、印刷時は非表示になるクラスを作成できます。

```css:CSS
@media print {
	.no-print {
		display: none;
	}
}
```

逆に印刷時でなく画面上で表示しているときのみスタイルを設定するには `@media screen` を使用します。

参考「[@media - CSS: Cascading Style Sheets | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/@media)」

# 3. 例

## 3.1. 改ページする

スタイル `break-after: page;` を使用することで、印刷時に改ページすることができます。

`break-after: page;` は「改ページを強制する」スタイルなため、最終ページにはこのスタイルが適用されないように `:not(:last-child)` 等で制限します。

```html:index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>印刷用 WEB ページ</title>
    <link rel="stylesheet" href="./main.css?v=0.1.0">
  </head>
  <body>
    <div class="page">Page 1</div>
    <div class="page">Page 2</div>
    <div class="page">Page 3</div>
  </body>
</html>
```

```css:main.css
.page:not(:last-child) {
	break-after: page;
}
```

参考「[break-after - CSS: Cascading Style Sheets | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/break-after)」

(少なくとも自分の環境で確認した限りでは `:not(:last-child)` を付けなくても最終ページで改行されず空白のページは出来なかったのですが、仕様書でも「改ページを強制する」と書かれているため、最終ページには適用されないようにすべきかと思います。)

参考「[Page Break Values - CSS Fragmentation Module Level 3](https://w3c.github.io/csswg-drafts/css-break/#page-break-values)」

## 3.2. A4 サイズで印刷されるようにする

`@page` ルール内で以下の記述子およびプロパティを指定します:

- `size`: 印刷時のページの大きさと用紙の向きを指定する
    - `A4`: 210mm x 297mm (ISO 規格)
    - `portrait`: 縦向き
- ページプロパティ `margin`: 印刷時のページの余白を指定する

```html:index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>印刷用 WEB ページ</title>
    <link rel="stylesheet" href="./main.css?v=0.1.0">
  </head>
  <body>
    <div class="page">Page</div>
  </body>
</html>
```

```css:main.css
@page {
	size: A4 portrait;
	margin: 20mm;
}
```

参考「[@page - CSS: Cascading Style Sheets | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/@page)」
参考「[size - CSS: Cascading Style Sheets | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/@page/size)」

## 3.3. 印刷時のヘッダーとフッターを強制的に非表示にする

`@page` ルール内で、印刷時の `margin` を `0` にすることで、印刷時のヘッダーとフッターを強制的に非表示にすることができます。

仕様上は `@page` ルール内で印刷時の `padding` を設定できるはずですが、2023-03-21 現在は多くの WEB ブラウザで未実装のため、代わりにページとして扱う HTML 要素ごとに (印刷用でない) 通常の `padding` を設定します。

```html:index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>印刷用 WEB ページ</title>
    <link rel="stylesheet" href="./main.css?v=0.1.0">
  </head>
  <body>
    <div class="page">Page</div>
  </body>
</html>
```

```css:main.css
@page {
	size: A4 portrait;
	margin: 0;
}

.page {
	padding: 20mm;
}
```

参考「[@page - CSS: Cascading Style Sheets | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/@page)」

## 3.4. 画面上でも印刷時と同じように表示する

ページとして扱う HTML 要素を A4 サイズにすることで、印刷時と同じレイアウトの表示を画面上でも行うことができます。

ただし、ページとして扱う HTML 要素の周りに余白を表示する場合、印刷時はその余白を取り除くように `@media` ルール内で設定する必要があります。

```html:index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>印刷用 WEB ページ</title>
    <link rel="stylesheet" href="./main.css?v=0.1.0">
  </head>
  <body>
    <div class="page">Page 1</div>
    <div class="page">Page 2</div>
    <div class="page">Page 3</div>
  </body>
</html>
```

```css:main.css
body {
	margin: 0;
}

@page {
	size: A4 portrait;
	margin: 0;
}

.page {
	box-sizing: border-box;
	width: 210mm;
	height: 297mm;
	padding: 20mm;
}

.page:not(:last-child) {
	break-after: page;
}

@media print {
	.page {
		margin: 0;
	}
}

@media screen {
	html {
		background-color: #bbb;
	}

	.page {
		margin: 20px auto;
		background-color: #fff;
		box-shadow: 0 0 8px #666;
	}
}
```

## 3.5. ページ用の独自のヘッダーとフッターを追加する

ここでは `position: absolute;` でヘッダーおよびフッターとして扱う HTML 要素を絶対位置指定します。

ヘッダーとフッターとして扱う HTML 要素の位置をページ基準にするため、ページとして扱う HTML 要素に `position: relative;` を設定します。

(※必要に応じてヘッダーとフッターのスタイルを追加してください。)

```css:main.css
body {
	margin: 0;
}

@page {
	size: A4 portrait;
	margin: 0;
}

.page {
	box-sizing: border-box;
	width: 210mm;
	height: 297mm;
	padding: 20mm;
	position: relative; /* ヘッダーおよびフッターの基準位置にする */
}

.page:not(:last-child) {
	break-after: page;
}

@media print {
	.page {
		margin: 0;
	}
}

@media screen {
	html {
		background-color: #bbb;
	}

	.page {
		margin: 20px auto;
		background-color: #fff;
		box-shadow: 0 0 8px #666;
	}
}

/*
 * ヘッダーおよびフッター
 */
.page-header {
	box-sizing: border-box;
	height: 20mm;
	position: absolute;
	top: 0;
}

.page-footer {
	box-sizing: border-box;
	height: 20mm;
	position: absolute;
	bottom: 0;
}
```

### 3.5.1. HTML に手動で追加する

ヘッダーやフッターとする HTML 要素単純にを手動で追加することで表示できます。

```html:index.html
<!DOCTYPE html>
<html lang="ja" prefix="og: https://ogp.me/ns#">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>印刷用 WEB ページ</title>
    <link rel="stylesheet" href="./main.css?v=0.1.0">
  </head>
  <body>
    <div class="page">
      <header class="page-header">Header 1</header>
      Page 1
      <footer class="page-footer">Footer 1</footer>
    </div>
    <div class="page">
      <header class="page-header">Header 2</header>
      Page 2
      <footer class="page-footer">Footer 2</footer>
    </div>
    <div class="page">
      <header class="page-header">Header 3</header>
      Page 3
      <footer class="page-footer">Footer 3</footer>
    </div>
  </body>
</html>
```

### 3.5.2. JavaScript で自動で追加する

ヘッダーとフッターで、異なるページにおいて共通の内容を持たせたい場合、JavaScript で HTML 要素を再利用する方法もあります。

ここでは `<template>` 要素でヘッダーおよびフッターの HTML を記述しています。

```html:index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>印刷用 WEB ページ</title>
    <link rel="stylesheet" href="./main.css?v=0.1.0">
  </head>
  <body>
    <div class="page">Page 1</div>
    <div class="page">Page 2</div>
    <div class="page">Page 3</div>

    <template id="page-header">
      <header class="page-header">Header <span data-name="page-number"></span></header>
    </template>
    <template id="page-footer">
      <footer class="page-footer">Footer <span data-name="page-number"></span></footer>
    </template>

    <script src="./main.js?v=0.1.0"></script>
  </body>
</html>
```

```js:main.js
(() => {

	const setPageNumbers = (pageElement, pageNumber) => {
		const pageNumberNodes = pageElement.querySelectorAll('[data-name="page-number"]');
		for (const pageNumberNode of pageNumberNodes) {
			pageNumberNode.textContent = pageNumber;
		}
	};

	// 
	const pages = document.getElementsByClassName('page');
	const pageCount = pages.length;

	const pageHeaderTemplate = document.getElementById('page-header');
	const pageFooterTemplate = document.getElementById('page-footer');

	for (let i = 0; i < pageCount; i++) {

		const page = pages[i];

		// 
		const pageHeader = pageHeaderTemplate.content.cloneNode(true);
		setPageNumbers(pageHeader, i + 1);
		page.prepend(pageHeader);

		const pageFooter = pageFooterTemplate.content.cloneNode(true);
		setPageNumbers(pageFooter, i + 1);
		page.append(pageFooter);

	}

})();
```

参考「[&lt;template&gt;: コンテンツテンプレート要素 - HTML: HyperText Markup Language | MDN](https://developer.mozilla.org/ja/docs/Web/HTML/Element/template)」

## 3.6. 印刷されない領域を作る

画面上で表示する際にヘッダーやナビゲーション等を表示し、印刷時には印刷されないようにしたいことがあります。

`@media print` で `display: none;` を指定することで、印刷時に非表示にできます。

```html:index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>印刷用 WEB ページ</title>
    <link rel="stylesheet" href="./main.css?v=0.1.0">
  </head>
  <body>
    <header class="site-header">印刷用 WEB ページ</header>
    <div class="page">Page</div>
  </body>
</html>
```

```css:main.css
body {
	margin: 0;
}

@page {
	size: A4 portrait;
	margin: 0;
}

.page {
	box-sizing: border-box;
	width: 210mm;
	height: 297mm;
	padding: 20mm;
}

.page:not(:last-child) {
	break-after: page;
}

@media print {
	.site-header {
		display: none; /* 印刷時は非表示 */
	}

	.page {
		margin: 0;
	}
}

@media screen {
	html {
		background-color: #bbb;
	}

	body {
		padding-top: 65px;
	}

	.site-header {
		height: 64px;
		width: 100%;
		position: fixed;
		top: 0;
		display: flex;
		justify-content: center;
		align-items: center;
		background-color: #fff;
		border-bottom: 1px solid #ddd;
	}

	.page {
		margin: 20px auto;
		background-color: #fff;
		box-shadow: 0 0 8px #666;
	}
}
```

## 3.7. 印刷ボタンを設置する

JavaScript で `print` 関数を使うことで印刷ダイアログを表示できます。

```html:index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>印刷用 WEB ページ</title>
    <link rel="stylesheet" href="./main.css?v=0.1.0">
  </head>
  <body>
    <header class="site-header"><button type="button" data-type="print" disabled>印刷</button></header>
    <div class="page">Page</div>

    <script src="./main.js?v=0.1.0"></script>
  </body>
</html>
```

```css:main.css
body {
	margin: 0;
}

button {
	font: inherit;
}

@page {
	size: A4 portrait;
	margin: 0;
}

.page {
	box-sizing: border-box;
	width: 210mm;
	height: 297mm;
	padding: 20mm;
}

.page:not(:last-child) {
	break-after: page;
}

@media print {
	.site-header {
		display: none;
	}

	.page {
		margin: 0;
	}
}

@media screen {
	html {
		background-color: #bbb;
	}

	body {
		padding-top: 65px;
	}

	.site-header {
		height: 64px;
		width: 100%;
		position: fixed;
		top: 0;
		display: flex;
		justify-content: center;
		align-items: center;
		background-color: #fff;
		border-bottom: 1px solid #ddd;
	}

	.page {
		margin: 20px auto;
		background-color: #fff;
		box-shadow: 0 0 8px #666;
	}
}
```

```js:main.js
(() => {

	const printButtonNodes = document.querySelectorAll('[data-type="print"]');

	for (const printButtonNode of printButtonNodes) {
		printButtonNode.addEventListener('click', () => {
			print();
		});
		printButtonNode.disabled = false;
	}

})();
```

参考「[Window.print() - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/Window/print)」