ウェブページ上から印刷をしたいという要望がしばしばあります。印刷対象は帳票であったりウェブページ上の何かであったり様々です。様々ではありますが、ウェブページ全体を印刷したいという要望は少ないです。全体の印刷だけならブラウザでユーザーが自分でやりますし、印刷ボタンも含めて印刷したいことは稀です。この要望を見たすための方法は主に二つあります。
一つめは CSS です。次のように印刷時のみ有効なCSSを@media printで作れます。これで余分なものを隠して必要な要素のみを望んだ場所に出力するのが CSS を用いた手法です。
@media – CSS: カスケーディングスタイルシート | MDN
これはこれで便利です。例えば次の様に印刷時には消えるクラスを作って定義しておき、メニューや操作パネルなどにそのクラスを割り当てると、それだけで解決することもままあります。
@media print {
.display-none-in-print {
/* 印刷時にのみ参照され、かつ印刷時には協力に働いてほしいので !important を使用 */
display: none !important;
}
}
とはいえ、配置を変えたかったり同ページ中のいくつかの要素それぞれの組み合わせについて印刷機能をつけたかったりなど CSS の方法のみで簡単に解決することができない場合もあります。この場合もう一つの方法が役に立ちやすいです。
もう一つの方法として iframe 上で必要なものだけ用意し、それを印刷する手法があります。この記事ではそれを紹介します。印刷を JavaScript 上から動かすには Window.print() メソッドを使います。
Window.print() – Web API | MDN
また iframe には contentWindow という iframe 要素で見ている画面の Window 要素を保持するプロパティがあります。これらを組み合わせることで iframe 内のみを印刷することができます。
HTMLIFrameElement.contentWindow – Web API | MDN
これは例えば次の様にできます。
<body>
<iframe id="print-tgt" src="print_tgt.html" frameborder="1"></iframe>
<div>ここはiframeの外です</div>
<button id="print-btn">印刷</button>
<script>
document.getElementById('print-btn').addEventListener('click',()=>{
document.getElementById('print-tgt').contentWindow.print()
})
</script>
</body>
iframe 要素を取得して、その要素の contentWindow プロパティから print メソッドを動かすだけです。これで iframe 内のみを印刷できます。
これを更に拡張して現在のページの一部要素を iframe 内に書き込み、それを印刷するといった手法がとれます。iframe では contentWindow 同様に contentDocument プロパティで iframe 内の DOM に触れます。よくあるdocument.bodyみたいにiframe.contentDocument.bodyとできるわけです。これを利用することで任意の要素のみを任意の形で iframe 内に展開し、それを印刷するといったことができます。これは例えば次で様にできます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>要素を印刷</title>
<style>
#print-tgt {
background: #cfc;
border: solid #444;
}
</style>
</head>
<body>
<div id="print-tgt">
<span>この枠の要素だけが印刷対象になります</span>
</div>
<div>ここは印刷されません</div>
<button id="print-btn">印刷</button>
</body>
</html>
document.getElementById('print-btn').addEventListener('click', () => {
// 印刷用の iframe を生成
const iframe = document.createElement('iframe');
// 空の HTML ドキュメントをベースとして、印刷したいものを付け足していく形で iframe を構築
iframe.srcdoc = "<!DOCTYPE html>";
// 大本の window 内に入っていないと印刷できないので、appendChild で追加
document.body.appendChild(iframe);
// 印刷中とはいえ、画面内に余分なものが見えるのは好ましくないため display:none で非表示化
iframe.style.display = 'none';
// 大本の window 内にロードし終わったら印刷用の構築を本格化
iframe.onload = () => {
// 印刷したい要素の複製を iframe 内の body に追加
iframe.contentDocument.body.appendChild(document.getElementById('print-tgt').cloneNode(true));
// 大本の window の CSS を反映
document.querySelectorAll("style").forEach(el => iframe.contentDocument.head.appendChild(el.cloneNode(true)))
document.querySelectorAll("link[rel='stylesheet']").forEach(el => iframe.contentDocument.head.appendChild(el.cloneNode(true)))
// 印刷を実行
iframe.contentWindow.print();
// 印刷が終わったら iframe を削除して元通りの DOM にする
iframe.contentWindow.addEventListener('afterprint', () => iframe.remove());
}
})
上記を動かしたデモが次です。
手間はかかりますが、この方針を用いると JavaScript の一画面のみで HTML を用い、画面上の状態を反映した帳票なども作れます。これの応用でサーバーから送られてきた PDF などを iframe で開いて印刷する、なんてこともできます。この手法は昔からよくあるもので任意の要素や範囲を指定して印刷できるようにするライブラリは結構これを使っている印象です。デモの様に単一の要素を印刷するのみならばライブラリを使ったほうが早いですが、応用や少し外れた何かをする時には根本の手法を知っておくと何かと便利です。