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