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 JPEG
  • domtoimage.toSvg() for SVG
  • domtoimage.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.

Last modified: April 27, 2025