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

388 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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: 디버깅.
## 💻 코드 패턴
### 기본
```css
@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
```css
@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
```css
.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; }
```
```html
<div class="chapter">Chapter 1</div>
<p>Content...</p>
<div class="page-break-before"></div>
<div class="chapter">Chapter 2</div>
```
### Avoid orphan / widow
```css
p {
orphans: 3; /* 페이지 끝 최소 3 line */
widows: 3; /* 페이지 시작 최소 3 line */
}
```
→ 한 줄 만 페이지 끝 / 시작 안 됨.
### Header / footer (running headers)
```css
@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)
```css
@page {
@bottom-right {
content: counter(page) " / " counter(pages);
font-size: 9pt;
}
}
```
### Image / table breaks
```css
img, table, figure {
page-break-inside: avoid;
break-inside: avoid;
}
/* Heading 가 단독 페이지 끝 X */
h1, h2, h3 {
break-after: avoid;
page-break-after: avoid;
}
```
### Receipts / invoices
```css
@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)
```css
@media print {
* {
color: black !important;
background: white !important;
box-shadow: none !important;
}
/* 단 logo / chart 등 색 유지 */
.logo, .chart {
color: revert !important;
background: revert !important;
}
}
```
### 색 강제 (cost OK)
```css
@media print {
.alert {
-webkit-print-color-adjust: exact;
print-color-adjust: exact; /* Modern */
background: red !important;
color: white !important;
}
}
```
### Print button
```html
<button onclick="window.print()">Print</button>
```
```ts
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)
```bash
yarn add pagedjs
```
```ts
import { Previewer } from 'pagedjs';
const previewer = new Previewer();
previewer.preview();
```
→ Browser 의 paged media 부족 = polyfill. CSS 표준 paged feature 더 많이 지원.
### PDF generation (server)
```ts
// 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 안)
```css
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
```
```css
@page { size: letter; }
@page { size: 80mm 200mm; } /* custom */
```
### Orientation
```css
@page { size: landscape; }
/* Specific page */
@page wide {
size: A3 landscape;
}
.wide-table { page: wide; }
```
### Test
```ts
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
```tsx
function ReportPage() {
return (
<>
<header className="no-print">
<button onClick={() => window.print()}>Print</button>
</header>
<main>
{/* Print content */}
<h1>Report</h1>
<table>...</table>
</main>
</>
);
}
```
```css
@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.
## 🔗 관련 문서
- [[Frontend_CSS_Modern_Features]]
- [[Frontend_A11y_Testing]]
- [[Frontend_Image_Optimization]]