How to convert HTML elements to image with javascript?
I want to be able to convert HTML elements to images and download them. I tried using html2canvas but I have an error caused by the oklch color function that I'm using
Error: Attempting to parse an unsupported color function "oklch"
Is there any fix to this or any library alternative? I don't want to change the color library as it is the default from Tailwind
Solution
You can use html2canvas-pro, it has the same api as html2canvas but it supports all the modern color libraries like color, lab, lch, oklab, oklch
Here is an example of how to download a div as an image with it
const downloadElement = () => {
const element = document.getElementById('element-id')
html2canvas(element, { backgroundColor: null }).then((canvas) => {
var myImage = canvas.toDataURL()
downloadURI(myImage)
const link = document.createElement('a')
link.download = 'image-filename.png'
link.href = uri
document.body.appendChild(link)
link.click()
URL.revokeObjectURL(link.href)
})
}
Alternative #1
I've been using dom-to-image as an alternative to html2canvas for years, and it handles modern CSS color functions much better. It's particularly good with Tailwind's color system.
import domtoimage from 'dom-to-image'
const downloadElement = () => {
const element = document.getElementById('element-id')
domtoimage
.toPng(element)
.then(function (dataUrl) {
const link = document.createElement('a')
link.download = 'element.png'
link.href = dataUrl
link.click()
})
.catch(function (error) {
console.error('Error generating image:', error)
})
}
The library also supports different output formats:
domtoimage.toJpeg()for JPEGdomtoimage.toSvg()for SVGdomtoimage.toBlob()for Blob objects
This approach has worked reliably for me across different browsers and CSS frameworks.
Alternative #2
If you want to stick with the standard html2canvas library, you can preprocess your CSS to convert modern color functions to RGB values before rendering. This is a bit of a workaround, but it works well for static content.
const convertModernColors = (element) => {
const computedStyle = window.getComputedStyle(element)
const elements = element.querySelectorAll('*')
elements.forEach((el) => {
const style = window.getComputedStyle(el)
const backgroundColor = style.backgroundColor
const color = style.color
// Convert oklch to rgb if needed
if (backgroundColor.includes('oklch')) {
// Use a color conversion library or CSS custom properties
el.style.backgroundColor =
getComputedStyle(document.documentElement).getPropertyValue('--fallback-color') || '#000000'
}
})
}
const downloadElement = () => {
const element = document.getElementById('element-id')
convertModernColors(element)
html2canvas(element).then((canvas) => {
const link = document.createElement('a')
link.download = 'image.png'
link.href = canvas.toDataURL()
link.click()
})
}
This approach requires some setup but allows you to keep using the standard library.
Alternative #3
For a more robust solution, consider using Puppeteer (server-side) or Playwright to render the HTML and capture screenshots. This approach handles all modern CSS features perfectly.
// Server-side with Puppeteer
const puppeteer = require('puppeteer')
const generateImage = async (htmlContent) => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.setContent(htmlContent)
await page.waitForSelector('#element-id')
const element = await page.$('#element-id')
await element.screenshot({
path: 'element.png',
type: 'png',
})
await browser.close()
}
For client-side, you can use Playwright's browser binaries:
import { chromium } from 'playwright'
const generateImage = async (htmlContent) => {
const browser = await chromium.launch()
const page = await browser.newPage()
await page.setContent(htmlContent)
const element = await page.locator('#element-id')
await element.screenshot({ path: 'element.png' })
await browser.close()
}
This approach is more resource-intensive but handles all CSS features correctly.