Ionicで任意の要素だけを印刷するための発想とスタイルシート
アプリ「食品表示印刷」では、食品表示ラベルを任意の枚数並べて、一般的なプリンタ(家庭用プリンタ)で印刷する機能があります。
簡単にですが、この機能の実現をどのようにしているかをご紹介します。
ステップ
1. 印刷する要素は画像に変換
プレビュー画面の食品表示ラベルは、(Canvasでいちいちつくっていると手間すぎるということもあり)、HTML要素をレンダリングしています。リアルタイムに文字列を反映させたり、表示非表示のバインディングを行うとなると、やはりHTML要素を扱う方が楽で確実なんですよね。
そして、ユーザが印刷を押すと、最初に行うのがこのDOMの画像化です。 dom-to-image-more
というライブラリを用いて、HTML要素を画像に変換しています。
ただ、微妙にDOMの計測がうまくいかないことがあり、現時点では画像化する時に、DOMに padding-bottom: 4px
を追加して、広げる形で画像化しています。そうじゃないと下部分が切れてしまうので。
さて、そうすると印刷したい画像(base64)が手に入りました。
2. 画像を並べる
次に、印刷する画像を横に並べます。写真では「9枚印刷」となってるので、9枚並べることにします。Angularだったらこんな感じでprintArrayというプロパティをバインディングして、その配列を9回繰り返せばいいですね(Signalを使っています)
this.printArray.set(Array(this.super.options().printNum).fill(0).map((x, i) => i));
@for (item of printArray(); track item) {
<img
class="print-label"
alt=""
[src]="vm.printBase64()"
/>
}
3. 表示・非表示をハンドリング
印刷時「HTMLもBodyも何のデザインもあたっていない状態で、画像だけ並んでる」状態を目指さないといけないわけです。ですので、印刷する時だけ、Ionicのデフォルトのスタイルをすべて無効化します。
@media print {
body {
overflow: auto !important;
max-height: none !important;
position: inherit !important;
}
.ion-page,
.ion-modal,
ion-router-outlet {
overflow: visible !important;
position: inherit !important;
contain: none;
}
ion-toolbar {
display: none;
border: none !important;
--border-style: none !important;
}
ion-header,
ion-content {
display: none;
}
ion-footer {
box-shadow: none !important;
}
}
これで大体のものが無効化されました。続いて、最前列にあるページで、印刷時に必要なもの、不必要なものの表示・非表示をハンドリングします。先ほどのテンプレートでは割愛したのですが、HTMLテンプレートは以下のように用意してたとしましょう。
...
<ion-content>
<main class="print-area">
<div class="label-preview">
<table>...(ユーザがプレビューとして確認するエリア)...</table>
</div>
@for (item of printArray(); track item) {
<img
class="print-label"
alt=""
[src]="vm.printBase64()"
/>
}
</main>
</ion-content>
そうすると、まずユーザがプレビューでみてる時のスタイルはこんな感じですね。 div.label-preview
要素は直接みることができますが img.print-label
は display: none
なので確認することができません。
@media screen {
main.print-area {
.label-preview {
display: block;
}
img.print-label {
display: none;
}
}
}
印刷時はこんな感じです。印刷ボタンを押すまでは見れてた div.label-preview
は非表示になり、 img.print-label
が表示されます。
@media print {
main.print-area {
.label-preview {
display: none;
}
img.print-label {
display: inline-block;
page-break-inside: avoid;
break-inside: avoid;
}
}
section.preview-component {
overflow: visible;
max-height: 100% !important;
}
}
こうすると、ユーザがプレビューで確認しているものと、実際に印刷されるものがまるで一致してるようにみえます。
まとめ
アプリで印刷物をハンドリングする時、モバイルアプリだと印刷APIにBase64なりPDFなりを送る方法はありますが、Webアプリだとシームレスに印刷してもらうにはHTML上で予期していた印刷レイアウトを再現する必要があります。そのために、こういったやり方はいかがでしょうか。
それではまた。
Discussion