📝

Ionicで任意の要素だけを印刷するための発想とスタイルシート

2024/12/05に公開

アプリ「食品表示印刷」では、食品表示ラベルを任意の枚数並べて、一般的なプリンタ(家庭用プリンタ)で印刷する機能があります。

ionic-print.png

簡単にですが、この機能の実現をどのようにしているかをご紹介します。

ステップ

1. 印刷する要素は画像に変換

プレビュー画面の食品表示ラベルは、(Canvasでいちいちつくっていると手間すぎるということもあり)、HTML要素をレンダリングしています。リアルタイムに文字列を反映させたり、表示非表示のバインディングを行うとなると、やはりHTML要素を扱う方が楽で確実なんですよね。

ionic-preview.png

そして、ユーザが印刷を押すと、最初に行うのがこのDOMの画像化です。 dom-to-image-more というライブラリを用いて、HTML要素を画像に変換しています。

https://github.com/1904labs/dom-to-image-more

ただ、微妙に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-labeldisplay: 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