Skip to content

Instantly share code, notes, and snippets.

@ulises-jeremias
Last active June 22, 2020 03:10
Show Gist options
  • Save ulises-jeremias/39d7a50ce7d00a9c7984a4ba9e348a45 to your computer and use it in GitHub Desktop.
Save ulises-jeremias/39d7a50ce7d00a9c7984a4ba9e348a45 to your computer and use it in GitHub Desktop.
React to Print
.print {
@media print {
@page {
size: A4; /* auto is the initial value */
margin: 0; /* this affects the margin in the printer settings */
}
html, body {
margin: 0 !important;
}
}
&:last-child {
page-break-after: auto;
}
}
.print-no-mount {
display: none !important;
}
import React, { Component } from 'react'
import { findDOMNode } from 'react-dom'
import PropTypes from 'prop-types'
class ReactToPrint extends Component {
constructor(props) {
super(props)
const { content, on } = this.props
if (on === 'mount') {
const contentEl = content()
// eslint-disable-next-line react/no-find-dom-node
const contentNodes = findDOMNode(contentEl)
this.state = {
contentNodes
}
} else {
this.state = {
contentNodes: null
}
}
this.handlePrint = this.handlePrint.bind(this)
}
componentDidMount() {
const { content, on } = this.props
if (on === 'click') {
const contentEl = content()
this.setState(() => ({
// eslint-disable-next-line react/no-find-dom-node
contentNodes: findDOMNode(contentEl)
}))
}
}
handlePrint() {
const {
bodyClass,
copyStyles,
pageStyle,
} = this.props
const { contentNodes } = this.state
if (!contentNodes) {
return null
}
let printWindow = document.createElement('iframe')
printWindow.style.position = 'absolute'
printWindow.style.top = '-1000px'
printWindow.style.left = '-1000px'
const linkNodes = document.querySelectorAll('link[rel="stylesheet"]')
this.linkTotal = linkNodes.length || 0
this.linkLoaded = 0
const markLoaded = (type) => {
void type
this.linkLoaded++
if (this.linkLoaded === this.linkTotal) {
this.triggerPrint(printWindow)
}
}
printWindow.onload = () => {
/* IE11 support */
if (window.navigator && window.navigator.userAgent.includes('Trident/7.0')) {
printWindow.onload = null
}
let domDoc = printWindow.contentDocument || printWindow.contentWindow.document
const srcCanvasEls = [...contentNodes.querySelectorAll('canvas')]
domDoc.open()
domDoc.write(contentNodes.outerHTML)
domDoc.close()
/* remove date/time from top */
const defaultPageStyle = pageStyle === undefined ?
'@page { size: auto margin: 0mm } @media print { body { -webkit-print-color-adjust: exact } }' :
pageStyle
let styleEl = domDoc.createElement('style')
styleEl.appendChild(domDoc.createTextNode(defaultPageStyle))
domDoc.head.appendChild(styleEl)
if (bodyClass.length) {
bodyClass.split(' ').forEach(className => domDoc.body.classList.add(className))
}
const canvasEls = domDoc.querySelectorAll('canvas')
Array.from(canvasEls).forEach((node, index) => {
node.getContext('2d').drawImage(srcCanvasEls[index], 0, 0)
})
if (copyStyles !== false) {
const headEls = document.querySelectorAll('style, link[rel="stylesheet"]')
Array.from(headEls).forEach((node, index) => {
let newHeadEl = domDoc.createElement(node.tagName)
let styleCSS = ''
if (node.tagName === 'STYLE') {
if (node.sheet) {
for (let i = 0; i < node.sheet.cssRules.length; i++) {
styleCSS += node.sheet.cssRules[i].cssText + '\r\n'
}
newHeadEl.setAttribute('id', `react-to-print-${index}`)
newHeadEl.appendChild(domDoc.createTextNode(styleCSS))
}
} else {
let attributes = [...node.attributes]
attributes.forEach(attr => {
newHeadEl.setAttribute(attr.nodeName, attr.nodeValue)
})
newHeadEl.onload = markLoaded.bind(null, 'link')
newHeadEl.onerror = markLoaded.bind(null, 'link')
}
domDoc.head.appendChild(newHeadEl)
})
}
if (this.linkTotal === 0 || copyStyles === false) {
this.triggerPrint(printWindow)
}
}
document.body.appendChild(printWindow)
}
removeWindow(target) {
void this
setTimeout(() => {
target.parentNode.removeChild(target)
}, 500)
}
triggerPrint(target) {
const { onBeforePrint, onAfterPrint } = this.props
if (onBeforePrint) {
onBeforePrint()
}
setTimeout(() => {
target.contentWindow.focus()
target.contentWindow.print()
this.removeWindow(target)
if (onAfterPrint) {
onAfterPrint()
}
}, 500)
}
render() {
const { on, trigger, content } = this.props
if (!content) {
return null
}
if (on !== 'click') {
this.handlePrint()
return null
}
return React.cloneElement(trigger(), {
ref: el => this.triggerRef = el,
onClick: this.handlePrint
})
}
}
ReactToPrint.propTypes = {
/** Copy styles over into print window. default: true */
copyStyles: PropTypes.bool,
/** Trigger action used to open browser print */
trigger: PropTypes.func,
/** Content to be printed */
content: PropTypes.func.isRequired,
/** Callback function to trigger before print */
onBeforePrint: PropTypes.func,
/** Callback function to trigger after print */
onAfterPrint: PropTypes.func,
/** Optional class to pass to the print window body */
bodyClass: PropTypes.string,
on: PropTypes.oneOf(['click', 'mount']),
pageStyle: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object,
]),
// closeAfterPrint: PropTypes.bool,
}
ReactToPrint.defaultProps = {
copyStyles: true,
// closeAfterPrint: true,
bodyClass: '',
onBeforePrint: null,
onAfterPrint: null,
trigger: null,
on: 'click',
pageStyle: null,
}
export default ReactToPrint
import React, { useRef } from 'react'
import ReactToPrint from './ReactToPrint'
const Example = () => {
const printableComponentRef = useRef(null);
return (
{printableComponentRef && (
<ReactToPrint
bodyClass='print'
on='mount'
content={() => printableComponentRef}
/>
)}
<div className='print-no-mount'>
<div ref={printableComponentRef} />
</div>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment