Files
2nd/10_Wiki/Topics/Coding/Frontend_Print_Stylesheet.md
T
2026-05-09 21:08:02 +09:00

7.6 KiB
Raw Blame History


id: frontend-print-stylesheet title: Print Stylesheet — @page / 인쇄 / PDF category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [frontend, css, print, vibe-coding] tech_stack: { language: "CSS", applicable_to: ["Frontend"] } applied_in: [] aliases: [@page, @media print, page-break, print preview, paged.js, PDF generation]

Print Stylesheet

Web → 인쇄 / PDF. @media print + @page. Header / footer 반복, page break, 흑백, link URL 표시. Receipt / report / 영수증 / book.

📖 핵심 개념

  • @media print: 인쇄용 styling.
  • @page: 페이지 size / margin.
  • page-break: 강제 break.
  • print preview: 디버깅.

💻 코드 패턴

기본

@media print {
  /* Hide UI */
  .header, .nav, .footer, .ads, .no-print {
    display: none !important;
  }
  
  body {
    font-size: 12pt;
    line-height: 1.5;
    color: black;
    background: white;
  }
  
  /* Link URL 표시 */
  a[href]::after {
    content: " (" attr(href) ")";
    font-size: 10pt;
    color: #555;
  }
  
  /* Internal link 제외 */
  a[href^="#"]::after,
  a[href^="javascript:"]::after {
    content: "";
  }
}

@page

@page {
  size: A4 portrait;
  margin: 2cm;
}

@page :first {
  margin-top: 4cm;
}

@page :left {
  margin-left: 3cm;
}

@page :right {
  margin-right: 3cm;
}

/* Named page */
@page chapter {
  margin: 3cm;
}

.chapter-page { page: chapter; }

Page break

.page-break-before { page-break-before: always; break-before: page; }
.page-break-after { page-break-after: always; break-after: page; }
.no-break { page-break-inside: avoid; break-inside: avoid; }

/* Modern syntax */
.chapter { break-before: page; }
.figure { break-inside: avoid; }
<div class="chapter">Chapter 1</div>
<p>Content...</p>
<div class="page-break-before"></div>
<div class="chapter">Chapter 2</div>

Avoid orphan / widow

p {
  orphans: 3;   /* 페이지 끝 최소 3 line */
  widows: 3;    /* 페이지 시작 최소 3 line */
}

→ 한 줄 만 페이지 끝 / 시작 안 됨.

@page {
  @top-center { content: "My Document"; }
  @bottom-center { content: counter(page) " / " counter(pages); }
  @top-right { content: string(chapter-title); }
}

h1.chapter {
  string-set: chapter-title content();
}

→ 매 page header 자동.

⚠️ Browser 지원 부분적. Paged.js / Prince XML 사용 가능.

Counter (page number)

@page {
  @bottom-right {
    content: counter(page) " / " counter(pages);
    font-size: 9pt;
  }
}

Image / table breaks

img, table, figure {
  page-break-inside: avoid;
  break-inside: avoid;
}

/* Heading 가 단독 페이지 끝 X */
h1, h2, h3 {
  break-after: avoid;
  page-break-after: avoid;
}

Receipts / invoices

@page {
  size: 80mm auto;  /* thermal printer */
  margin: 0;
}

@media print {
  body {
    font-family: monospace;
    font-size: 10pt;
    margin: 0;
    padding: 5mm;
  }
  
  .item-row {
    display: flex;
    justify-content: space-between;
  }
  
  .total {
    font-weight: bold;
    border-top: 1px dashed black;
    padding-top: 5mm;
  }
}

흑백 (cost saving)

@media print {
  * {
    color: black !important;
    background: white !important;
    box-shadow: none !important;
  }
  
  /* 단 logo / chart 등 색 유지 */
  .logo, .chart {
    color: revert !important;
    background: revert !important;
  }
}

색 강제 (cost OK)

@media print {
  .alert {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;  /* Modern */
    background: red !important;
    color: white !important;
  }
}

Print button

<button onclick="window.print()">Print</button>
window.print();  // 인쇄 dialog 열기

window.addEventListener('beforeprint', () => {
  // Modify before print
});

window.addEventListener('afterprint', () => {
  // 후처리
});

Print preview (dev)

Chrome: File → Print (Cmd+P) → "Save as PDF"
또는 DevTools → 3-dot → More tools → Rendering → Emulate CSS media → print

→ 인쇄 dialog 열어서 preview.

Paged.js (advanced)

yarn add pagedjs
import { Previewer } from 'pagedjs';

const previewer = new Previewer();
previewer.preview();

→ Browser 의 paged media 부족 = polyfill. CSS 표준 paged feature 더 많이 지원.

PDF generation (server)

// Puppeteer
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com/report?id=42');
await page.emulateMediaType('print');

const pdf = await page.pdf({
  format: 'A4',
  printBackground: true,
  margin: { top: '2cm', bottom: '2cm', left: '2cm', right: '2cm' },
  displayHeaderFooter: true,
  headerTemplate: '<div style="font-size:9pt; text-align:center; width:100%;">My Report</div>',
  footerTemplate: '<div style="font-size:9pt; text-align:center; width:100%;"><span class="pageNumber"></span> / <span class="totalPages"></span></div>',
});

await browser.close();
fs.writeFileSync('report.pdf', pdf);

→ HTML + CSS print → PDF.

Receipt 인쇄 (browser → POS printer)

대부분 = browser 의 일반 print dialog → 사용자가 thermal printer 선택.

또는 ESC/POS 직접 (Bluetooth / serial / USB).

CSS 실험 — bookmarks (PDF 안)

h1 {
  bookmark-level: 1;
  bookmark-label: content();
}
h2 {
  bookmark-level: 2;
}

→ PDF 의 navigation pane.

Page size 표준

A4: 210mm × 297mm (most international)
US Letter: 8.5in × 11in
Receipt: 80mm wide
Legal: 8.5in × 14in
A5: 148mm × 210mm
@page { size: letter; }
@page { size: 80mm 200mm; }   /* custom */

Orientation

@page { size: landscape; }

/* Specific page */
@page wide {
  size: A3 landscape;
}

.wide-table { page: wide; }

Test

test('print stylesheet hides nav', () => {
  document.body.classList.add('print-preview');  // 또는 @media print 검증
  
  // Puppeteer test
  await page.emulateMediaType('print');
  const navVisible = await page.evaluate(() => {
    return getComputedStyle(document.querySelector('nav')).display !== 'none';
  });
  expect(navVisible).toBe(false);
});

React component

function ReportPage() {
  return (
    <>
      <header className="no-print">
        <button onClick={() => window.print()}>Print</button>
      </header>
      <main>
        {/* Print content */}
        <h1>Report</h1>
        <table>...</table>
      </main>
    </>
  );
}
@media print {
  .no-print { display: none; }
}

🤔 의사결정 기준

작업 추천
일반 web print @media print + @page
PDF download Puppeteer / Playwright server
Receipt 80mm @page + monospace
Book / chapter Paged.js / Prince XML
단순 export window.print() + CSS
Server batch PDF Puppeteer + queue

안티패턴

  • Print stylesheet 무: 사용자 인쇄 시 깨짐.
  • !important 없는 background hide: 옛 browser 가 무시.
  • page-break-inside: auto 큰 image: 잘림.
  • Widows / orphans 무시: 작은 element 1줄 단독.
  • Color 인쇄 강제 (사용자 흑백 원함): print-color-adjust: economy.
  • JS 가 print 시 작동 가정: 일부 browser 에서 X.
  • Print preview 없이 prod: 디자인 깨짐 모름.

🤖 LLM 활용 힌트

  • @media print 항상 + .no-print class.
  • Server PDF = Puppeteer.
  • 책 / 복잡 = Paged.js.
  • Print preview 가 진짜 test.

🔗 관련 문서