html-paginator
v0.1.0
Published
Lightweight library for flexible page breaks in HTML printing using @media print
Maintainers
Readme
HTML Paginator
Dynamic content pagination library that intelligently distributes HTML content across page templates with custom headers, footers, and flexible layouts.
Features
- Dynamic Content Distribution: Automatically flows content across multiple page templates
- Custom Page Layouts: Each page can have unique headers, footers, and styling
- Smart Element Splitting: Splits content at child element boundaries for natural flow
data-no-breakDecorator: Keep specific elements intact across page boundaries- Height-Aware Pagination: Measures available space and distributes content intelligently
- Zero Dependencies: Pure JavaScript with no external requirements
- TypeScript Support: Full type definitions included
- Cross-Environment: Works in both browser and Node.js (with JSDOM)
Installation
Method 1: NPM Install (Recommended)
npm install html-paginatorThen import in your JavaScript:
import { createPaginator } from 'html-paginator';
const paginator = createPaginator({
pageTemplateSelector: '.page-template',
bodyContentSelector: '.body-content',
pageBodySelector: '.page-body'
});
paginator.paginate();Method 2: Direct HTML Import (No Build Step)
Download the src/index.js file and include it directly in your HTML:
<!DOCTYPE html>
<html>
<head>
<title>My Paginated Document</title>
</head>
<body>
<!-- Your page templates and content here -->
<script type="module">
import { createPaginator } from './path/to/index.js';
const paginator = createPaginator({
pageTemplateSelector: '.page-template',
bodyContentSelector: '.body-content',
pageBodySelector: '.page-body'
});
paginator.paginate();
</script>
</body>
</html>Note: The type="module" attribute is required for ES6 imports.
Quick Start
HTML Structure
Create page templates and content container:
<!DOCTYPE html>
<html>
<head>
<style>
/* Hide original page templates */
body > .page-template {
display: none;
}
.page {
width: 8.5in;
min-height: 11in;
background: white;
margin: 20px auto;
}
.page-header {
background: #333;
color: white;
padding: 20px;
}
.page-body {
padding: 40px;
min-height: 9in;
max-height: 9in;
}
.page-footer {
background: #f5f5f5;
padding: 15px;
text-align: center;
}
</style>
</head>
<body>
<!-- Page Templates (hidden by default) -->
<div class="page-template page">
<header class="page-header">
<h1>Document Title</h1>
<span>Page 1</span>
</header>
<div class="page-body"></div>
<footer class="page-footer">Footer content</footer>
</div>
<div class="page-template page">
<header class="page-header">
<h1>Document Title</h1>
<span>Page 2</span>
</header>
<div class="page-body"></div>
<footer class="page-footer">Footer content</footer>
</div>
<!-- Content to Paginate -->
<div class="body-content">
<div data-no-break>
<h2>Section 1</h2>
<p>This section stays together...</p>
</div>
<div>
<h2>Section 2</h2>
<p>This section can be split...</p>
<p>Content continues...</p>
</div>
</div>
<!-- JavaScript -->
<script type="module">
import { createPaginator } from './path/to/index.js';
const paginator = createPaginator({
pageTemplateSelector: '.page-template',
bodyContentSelector: '.body-content',
pageBodySelector: '.page-body',
noBreakAttribute: 'data-no-break'
});
// Perform pagination
paginator.paginate();
// Get paginated pages
const pages = paginator.getPages();
console.log(`Created ${pages.length} pages`);
</script>
</body>
</html>How It Works
- Page Templates: Define the structure of each page (header, body area, footer)
- Body Content: Contains the content items to be distributed
- Distribution: The paginator measures available space in each page body and fills it with content
- Smart Splitting:
- Elements with
data-no-breakstay intact - Elements without it can be split at the child level
- Elements with
- Overflow Handling: Last page gets remaining content, even if it overflows
API
createPaginator(options)
Creates a paginator instance with configuration options.
const paginator = createPaginator({
pageTemplateSelector: '.page-template', // CSS selector for page templates
bodyContentSelector: '.body-content', // CSS selector for content container
pageBodySelector: '.page-body', // CSS selector for page body area
noBreakAttribute: 'data-no-break', // Attribute for non-breaking elements
pageContainerSelector: '.page-container', // Container for paginated pages
hideEmptyPages: true // Hide unused page templates
});Paginator Methods
paginate()
Performs pagination and renders the result.
paginator.paginate();Returns: Paginator (for chaining)
getPages()
Returns array of paginated page elements.
const pages = paginator.getPages();
console.log(`Total pages: ${pages.length}`);Returns: HTMLElement[]
reset()
Resets pagination and shows original content.
paginator.reset();Returns: Paginator (for chaining)
setOptions(newOptions)
Updates configuration options.
paginator.setOptions({ hideEmptyPages: false });Returns: Paginator (for chaining)
getOptions()
Gets current configuration.
const options = paginator.getOptions();Returns: PaginatorOptions
Usage Examples
Report Generation
<style>
.page {
width: 8.5in;
min-height: 11in;
background: white;
padding: 0;
}
.page-header {
background: #333;
color: white;
padding: 20px;
}
.page-body {
padding: 40px;
min-height: 9in;
}
.page-footer {
background: #f5f5f5;
padding: 15px;
text-align: center;
}
</style>
<div class="page-template page">
<header class="page-header">
<h1>Annual Report 2024</h1>
</header>
<div class="page-body"></div>
<footer class="page-footer">Page 1 - Confidential</footer>
</div>
<div class="body-content">
<div data-no-break>
<h2>Executive Summary</h2>
<p>This section stays together...</p>
</div>
<div>
<h2>Financial Data</h2>
<p>Q1 results...</p>
<p>Q2 results...</p>
<p>Q3 results...</p>
<p>Q4 results...</p>
</div>
</div>
<script type="module">
import { createPaginator } from 'html-paginator';
const paginator = createPaginator();
paginator.paginate();
// Optional: Print the result
window.print();
</script>Invoice with Multiple Pages
<div class="page-template invoice-page">
<header class="invoice-header">
<div>Company Logo</div>
<div>Invoice #12345</div>
</header>
<div class="page-body"></div>
<footer class="invoice-footer">
<p>Terms and conditions apply</p>
</footer>
</div>
<div class="body-content">
<!-- Line items that flow across pages -->
<div class="line-item">Item 1 - $100</div>
<div class="line-item">Item 2 - $200</div>
<!-- ... many more items ... -->
<!-- Total section stays together -->
<div class="total-section" data-no-break>
<h3>Total</h3>
<p>Subtotal: $1000</p>
<p>Tax: $100</p>
<p>Total: $1100</p>
</div>
</div>Different Page Layouts
<!-- First page with cover design -->
<div class="page-template cover-page">
<header class="cover-header">
<h1>Document Title</h1>
<p>Author Name</p>
</header>
<div class="page-body"></div>
</div>
<!-- Standard pages -->
<div class="page-template standard-page">
<header class="standard-header">Page Header</header>
<div class="page-body"></div>
<footer class="standard-footer">Page 2</footer>
</div>
<div class="page-template standard-page">
<header class="standard-header">Page Header</header>
<div class="page-body"></div>
<footer class="standard-footer">Page 3</footer>
</div>The data-no-break Attribute
Use data-no-break on elements that should stay together on a single page:
<div class="body-content">
<!-- This entire section stays together -->
<div data-no-break>
<h2>Important Notice</h2>
<p>This content must not be split across pages.</p>
<ul>
<li>Point 1</li>
<li>Point 2</li>
</ul>
</div>
<!-- This section can be split at child level -->
<div>
<h2>Regular Content</h2>
<p>Paragraph 1 might go on page 1...</p>
<p>Paragraph 2 might go on page 2...</p>
</div>
</div>When to use data-no-break:
- Signature blocks
- Tables or charts
- Important notices
- Code blocks
- Terms and conditions sections
- Any content that loses meaning when split
Print Styles & PDF Generation
Remove Browser Headers/Footers
To use only your page-template headers/footers (not browser defaults), add these print styles:
/* Remove browser's default headers/footers */
@page {
size: letter;
margin: 0;
}
@media print {
html, body {
background: white;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
.page-container {
gap: 0;
}
.page {
page-break-after: always;
margin: 0;
width: 100%;
height: 100vh;
box-shadow: none;
}
.page:last-child {
page-break-after: auto;
}
/* Hide controls and original content */
button, .controls, .no-print {
display: none;
}
}Browser Print Settings
When printing or saving as PDF, configure your browser:
Chrome/Edge:
- Open Print dialog (Ctrl+P / Cmd+P)
- Click "More settings"
- Uncheck "Headers and footers"
- Set Margins to "None"
- Enable "Background graphics" (to show your custom headers/footers)
Firefox:
- File → Page Setup
- Margins & Header/Footer tab
- Set all headers/footers to "blank"
- Set margins to 0
Safari:
- File → Print
- Show Details
- Uncheck headers and footers
- Set margins to minimum
The @page { margin: 0; } rule should automatically remove browser headers/footers in most modern browsers, but manual configuration may be needed for some browsers.
TypeScript Support
Full TypeScript definitions included:
import { createPaginator, PaginatorOptions, Paginator } from 'html-paginator';
const options: PaginatorOptions = {
pageTemplateSelector: '.page-template',
bodyContentSelector: '.body-content',
pageBodySelector: '.page-body',
noBreakAttribute: 'data-no-break',
hideEmptyPages: true
};
const paginator: Paginator = createPaginator(options);
paginator.paginate();
const pages: HTMLElement[] = paginator.getPages();Advanced Usage
Dynamic Page Creation
// Create pages dynamically based on content
const contentSections = document.querySelectorAll('.section');
const pagesNeeded = Math.ceil(contentSections.length / 3);
for (let i = 0; i < pagesNeeded; i++) {
const page = document.createElement('div');
page.className = 'page-template';
page.innerHTML = `
<header>Page ${i + 1}</header>
<div class="page-body"></div>
<footer>Footer</footer>
`;
document.body.appendChild(page);
}
const paginator = createPaginator();
paginator.paginate();Programmatic Content Addition
const bodyContent = document.querySelector('.body-content');
// Add items programmatically
for (let i = 0; i < 100; i++) {
const item = document.createElement('div');
item.textContent = `Item ${i + 1}`;
// Mark every 10th item as non-breaking
if (i % 10 === 0) {
item.setAttribute('data-no-break', '');
}
bodyContent.appendChild(item);
}
const paginator = createPaginator();
paginator.paginate();Browser Support
Works in all modern browsers that support:
- ES6+ JavaScript
- DOM manipulation APIs
getComputedStyleand layout measurement
Development
# Install dependencies
npm install
# Run tests
npm test
# Build
npm run build
# Test in watch mode
npm run devLicense
MIT
Contributing
Contributions welcome! Please open an issue or submit a pull request.
