As a developer, you’re probably always looking for new approaches or solutions to make the apps you build even more useful. For example, if you’re working on an app that generates graphical data, you may want to provide users with the ability to export some of the content to share or to refer to at a later date.
Many data visualization libraries allow users to export images. However, if you’re in search of a library that can export multiple tables and graphs from a page in one go, html2canvas may be your solution.
In this tutorial, we’ll demonstrate how to use html2canvas to export React components as images.
But first, let’s begin addressing some basic questions:
html2canvas is a JavaScript library that enables users to produce screenshots of an entire webpage or portions of a webpage. The screenshots are taken directly on the user’s browser.
Technically, html2canvas does not actually take a screenshot, rather it creates a view based on the data that is present on the page. This results in an HTML5 canvas element that can be used to create an image file or to generate a preview of the screenshot on the user’s screen.
html2canvas reads a webpage as a canvas image. It goes through the webpage’s DOM and reads all present elements and styles. After html2canvas gathers all of the page structure information, it creates a representation of the page. Since all images are created on the user’s browser, html2canvas does not require any server-side rendering.
The resulting image is a very close facsimile of the webpage, but it’s not 100 percent perfect. That’s because it’s a representation of the webpage, rather than an actual screenshot.
To render an element with html2canvas, use the following syntax:
html2canvas(element[options]);
The html2canvas
function accepts the DOM element
and returns a P``romise
containing the <canvas>
element.
Next, we’ll use the then()
promise fulfillment handler:
const captureElement = (element) => { html2canvas(element).then(canvas => { document.body.appendChild(canvas) }) }
Alternatively, we can also use the async-await
syntax to avoid the callback.
const captureElement = async (element) => { const canvas = await html2canvas(element); document.body.appendChild(canvas); }
I personally prefer the async-await
syntax, since it’s much cleaner than chaining .then()
to the Promise
.
html2canvas has a few limitations. For example, the library cannot render content plugins like Java Applets or Flash.
Also, html2canvas only renders properties that it can understand. There are many CSS properties, such as box-shadow
or border-image
, that do not currently work with this library. Here’s a full list of the supported and unsupported CSS properties.
Another limitation is that, without a proxy, any canvas elements with cross-origin content will not be readable by html2canvas. Cross-origin images are those that load from a third party or another domain. Examples could include iframes, stylesheets, or scripts.
Now that we have an in-depth understanding of what html2canvas is and how it works, it’s time to learn how to use this library to export React components as images.
First, we’ll create a <div>
container in the JSX markup. We’ll use this to specify an area to download as an image. We’ll also add a button
with an onClick
event handler to download the image.
import React from "react"; import "./styles.css"; export default function App() { return ( <> <div className="parent"> <div> <p> Quis blandit turpis cursus in hac habitasse. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien et ligula. Sit amet dictum sit amet justo donec. Cursus mattis molestie a iaculis. Vel pretium lectus quam id leo in vitae. Quam nulla porttitor massa id neque aliquam vestibulum morbi blandit. </p> </div> </div> <button> Capture Image </button> </> ); }
Once we’ve set up the user interface, the next step is to add code that will allow us to download the React components as images. To do this, we’ll follow three steps:
npm i html2canvas
html2canvas
functionTo do this, we’ll create a utils
directory inside the src
folder and then create a new file, exportAsImage.js
:
import html2canvas from "html2canvas"; const exportAsImage = async (el, imageFileName) => { const canvas = await html2canvas(element); const image = canvas.toDataURL("image/png", 1.0); // download the image };
In the above code snippet, html2canvas
takes a DOM element in the argument and then uses the element to create a canvas image. Then, it returns a promise containing a canvas element.
import html2canvas from "html2canvas";const exportAsImage = async (el, imageFileName) => { const canvas = await html2canvas(element); const image = canvas.toDataURL("image/png", 1.0); downloadImage(image, imageFileName); };const downloadImage = (blob, fileName) => { const fakeLink = window.document.createElement("a"); fakeLink.style = "display:none;"; fakeLink.download = fileName; fakeLink.href = blob; document.body.appendChild(fakeLink); fakeLink.click(); document.body.removeChild(fakeLink); fakeLink.remove(); }; export default exportAsImage;
In the downloadImage
function, we create an anchor link in memory to simulate a click and download the image. Once the click event is simulated, the fake anchor link, fakeLink
, is removed from the DOM.
Next, in the App.js
file, we import the exportAsImage
function and attach it to the button’s onClick
handler. We use the useRef``()
hook to create a reference to the DOM element and pass it to the exportAsImage
function along with the filename.
export default function App() { const exportRef = useRef(); return ( <> <div className="parent"> <div ref={exportRef}> <p>...</p> </div> </div> <button onClick={() => exportAsImage(exportRef.current, "test")}> Capture Image </button> </> ) }
That’s the process for downloading a React component as an image!
html2canvas works nearly perfectly when exporting images that have a vertical layout. However, it has some limitations with horizontal layout. This library can only capture visible elements. Any elements that are outside of the viewport will be excluded from the final screenshot. Similarly, any portions of elements that are not displayed on the screen, will be excluded from the final screenshot as well.
Unfortunately, a vertical layout is not suitable for many React apps. An example is a use case in which many tables, visuals, or graphs need to be included on a single page. Comparing graphs or tables side by side is more difficult in a vertical layout. The images generally end up being very tall, and this does not result in a good user experience.
A workaround for this issue is to set the html
and body
tags to a large value so they can easily accommodate all the available data so that no data is hidden by the container.
Next, we capture the data and create a snapshot. Then, we set the html
and body
tags back to their original values.
Here’s the updated function for this method:
const exportAsImage = async (element, imageFileName) => { const html = document.getElementsByTagName("html")[0]; const body = document.getElementsByTagName("body")[0]; let htmlWidth = html.clientWidth; let bodyWidth = body.clientWidth; const newWidth = element.scrollWidth - element.clientWidth; if (newWidth > element.clientWidth) { htmlWidth += newWidth; bodyWidth += newWidth; } html.style.width = htmlWidth + "px"; body.style.width = bodyWidth + "px"; const canvas = await html2canvas(element); const image = canvas.toDataURL("image/png", 1.0); downloadImage(image, imageFileName); html.style.width = null; body.style.width = null; };
In this function, we first retrieve the initial width of html
and body
tags. Then, we note the inner width of an element and the minimum width required to contain all the data in the viewport without adding a horizontal scrollbar.
We define the following:
clientWidth
: the inner width of an element in pixels, including paddingscrollWidth
: the minimum width that an element needs to fit in the containerWondering why we need these values? Well, by keeping the size dynamic, rather than static, we can accommodate a maximum number of use cases. For example. if there are only two visuals on the screen, we will not need to set the container width very high. But, if there are several visuals on the screen, we may need a high container width.
For each case, we compare the clientWidth
and scrollWidth
to know if a change in width is required. There are two possible scenarios:
clientWidth
is less than scrollWidth
In this scenario, portions of the container are hidden. To overcome this problem, we’ll add the difference between scrollWidth
and clientWidth
to the width of html
and body
tags.
clientWidth
is greater than or equal to scrollWidth
In this scenario, all parts of the container are visible. In this case, there is no need to adjust the width of html
and body
tags.
html2canvas is a robust, easy to use solution for exporting React components as images. It does have some limitations, so be sure to read the documentation and test all possible scenarios to ensure you’re able to generate the desired view. The demo used in this tutorial is available on CodeSandbox.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowSOLID principles help us keep code flexible. In this article, we’ll examine all of those principles and their implementation using JavaScript.
JavaScript’s Date API has many limitations. Explore alternative libraries like Moment.js, date-fns, and the new Temporal API.
Explore use cases for using npm vs. npx such as long-term dependency management or temporary tasks and running packages on the fly.
Validating and auditing AI-generated code reduces code errors and ensures that code is compliant.