🖨️

React + NextでWebページを印刷する

2023/02/10に公開

はじめに

お疲れ様です。
@いけふくろうです。

バックエンドにて帳票ライブラリを使い、PDFを作成してフロントエンドにレスポンスすることが多いかもしれませんが、フロントエンドでWebページを印刷する場面があった際の方法を整理しました

概要

  • Web画面の表示内容を印刷する
  • react-to-printを使うとDOMを印刷することができるので活用した

環境

  • React v18.0.0
  • Next v12.1.5
  • react-to-print v2.14.11
  • Google Chrome
  • TailwindCSS v3.1.6

実装内容

ディレクトリ構成

├── components ・・・ コンポーネントをまとめるディレクトリ
│   ├── Print
│   │   ├── PrintOrganizer.tsx ・・・ 印刷設定と印刷対象のコンポーネントを管理するコンポーネント
│   │   └── index.ts ・・・ コンポーネントを出力するファイル
│   │   └── PrintExample
│   │       ├── PrintExampleContainer.tsx ・・・ Container層として、ロジックを定義するファイル
│   │       └── PrintExample.tsx ・・・ Container層から表示に必要なpropsを受領し、画面表示するためのファイル
│   │       └── index.ts ・・・ コンポーネントを出力するファイル
│   │       └── mockData.ts ・・・ テスト用のモックデータ

印刷対象のコンポーネント

  • ref属性をpropsとして用意しておき、対象の要素に設定します
import { mockData } from "./mockData";

interface Props {
  componentRef: React.MutableRefObject<HTMLDivElement | null>;
}

const PrintExample: React.FC<Props> = ({ componentRef }) => {
  return (
    <>
      <div ref={componentRef}>
        <div className="flex flex-col">
          <div className="flex items-center justify-center my-4">
            <label className="text-lg font-bold">タイトル</label>
          </div>
          <table className="w-full border-collapse whitespace-pre-line rounded border border-gray-300 bg-white text-left align-top">
            <thead
              className={"px-2 font-bold text-base bg-lime-100 border-b-2"}
            >
              <tr>
                <th className="p-2">列1</th>
                <th className="p-2">列2</th>
                <th className="p-2">列3</th>
                <th className="p-2">列4</th>
                <th className="p-2">列5</th>
                <th className="p-2">列6</th>
                <th className="p-2">列7</th>
              </tr>
            </thead>

            <tbody>
              {mockData.map((item) => (
                <tr key={item.id}>
                  <td className="p-2">{item.column1}</td>
                  <td className="p-2">{item.column2}</td>
                  <td className="p-2">{item.column3}</td>
                  <td className="p-2">{item.column4}</td>
                  <td className="p-2">{item.column5}</td>
                  <td className="p-2">{item.column6}</td>
                  <td className="p-2">{item.column7}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
    </>
  );
};

export default PrintExample;
  • <div ref={componentRef}> ... </div>のエリアが印刷対象となるように設定します

Container層のコンポーネント

  • 本記事の趣旨とは離れますが、Container/Presentationalパターンとしているため、今回は特にロジックがありませんので、コンポーネントを呼び出しているのみです
import PrintExample from "./PrintExample";

interface Props {
  componentRef: React.MutableRefObject<HTMLDivElement | null>;
}

const PrintExampleContainer: React.FC<Props> = ({ componentRef }) => {
  return (
    <>
      <PrintExample componentRef={componentRef} />
    </>
  );
};

export default PrintExampleContainer;

印刷設定と印刷対象のコンポーネントを管理するコンポーネント

  • こちらが、react-to-printの部分です
import React, { useCallback } from "react";
import { useRef } from "react";
import { useReactToPrint } from "react-to-print";
import PrintExample from "./PrintExample";

const PrintOrganizer: React.FC = () => {
  const componentRef = useRef<HTMLDivElement | null>(null);

  /**
   * 印刷時のスタイル設定(印刷仕様に応じてスタイリングを調整します)
   *  NOTE
   *  ・背景色/背景画像を表示させる設定(Chrome):-webkit-print-color-adjust: exact
   *  ・pageのmargin指定でプレビュー時にデフォルト表示されるURLと時刻(ヘッダーとフッター)を表示エリアから外している
   */
  const pageStyle = `
    @page { 
      size: auto;
      margin: 5mm;
    }
    
    @media print {
      body { -webkit-print-color-adjust: exact; }
      table { break-after: auto; }
      tr    { break-inside:avoid; break-after:auto }
      td    { break-inside:avoid; break-after:auto }
    }
  `;

  /**
   * 印刷対象のコンポーネントを設定します
   */
  const reactToPrintContent = useCallback(() => {
    if (!componentRef.current) return null;
    return componentRef.current;
  }, []);

  /**
   * 印刷プレビューを表示します
   */
  const handlePrint = useReactToPrint({
    pageStyle, // 印刷のスタイリングを指定
    content: reactToPrintContent, // 印刷エリアを指定
    removeAfterPrint: true, // 印刷後に印刷用のiframeを削除する
  });

  return (
    <>
      <div className="p-4">
        <div className="flex justify-center">
          <div className="w-1/4">
            <button
              onClick={handlePrint}
              className={
                "w-full h-9 font-semibold rounded-medium border border-gray-darkest text-white bg-blue-500 hover:bg-indigo-700"
              }
            >
              印刷
            </button>
          </div>
        </div>
        <PrintExample componentRef={componentRef} />
      </div>
    </>
  );
};

export default PrintOrganizer;
  • propsに関しては、<こちら>に記載があります

    • pageStyle
      • 印刷時のスタイリングをカスタマイズできますので、こちらで印刷仕様に応じてCSSを書くことになります
    • content
      • 印刷対象のコンポーネント(要素)を指定します
    • removeAfterPrint
      • 印刷後に印刷用に生成されたiframeを削除する場合にはtrueを指定します
  • handlePrint

    • 印刷ボタン押下時にreact-to-printが提供するuseReactToPrintを実行します

動作イメージ

おわりに

react-to-printはキャッチアップしやすかったため、クライアントサイドで印刷する機能があった場合には今後も活用しようと思いました!!

以上です。
本記事が何かの一助になれば幸いです。

Discussion