html2canvas:前端海报图片渲染神器

1,225 阅读5分钟

0. 写在前面

随着前端渲染海报图片的需求越来越普遍,上次我们介绍了在服务端使用pptr进行海报渲染,但是在服务端进行渲染也存在诸多问题:

  • 服务器资源,以及nodejs端的开发;
  • 保证高并发。当然也可以借助阿里云的fc来完成,既能保证成本还能保证对高并发的支持。
  • 重复开发:这一点非常重要,我们需要额外为pptr开发一个页面进行海报的渲染。

因此我们今天重点介绍,纯前端的方案来完成海报图片的渲染。

1.html2canvas

html2canvas 是一个流行的 JavaScript 库,它允许开发者将 HTML 文档的可见部分捕获并渲染成 canvas 元素。这个库非常有用,尤其是在需要生成网页的快照或将网页内容保存为图像文件时。

使用方法也非常简单:

html2canvas(document.body).then(canvas => {
    // 处理 canvas,例如将其添加到 DOM 中或转换为图像文件
    document.body.appendChild(canvas);
});

由于html2canvas已经很久没有更新维护了,因此我们选择了html2canvas-pro,它修复了html2canvas中一些已知的问题。

2. 简单封装

我们对html2canvas进行一些封装,只需要传入dom,即可直接得到渲染的base64格式图片。

逻辑很简单,直接上代码:

import html2canvas from 'html2canvas-pro'
import type { Options } from 'html2canvas-pro'

// 渲染选项
export interface useCanvasPosterOptions {
    type?: string;  // 渲染图片格式,默认为image/webp ,可选:image/png,image/jpeg
    quality?: number;   // 在指定图片格式为 image/jpeg 或 image/webp 的情况下,可以从 0 到 1 的区间内选择图片的质量
    ratio?: number; // ratio The scale to use for rendering. Defaults to the browsers device pixel ratio.
}
/**
 * 将DOM元素渲染为canvas
 * @params dom dom元素
 * @params renderOptions html2canvas渲染参数
 * @params opt 额外渲染选项
 */
export const useDomRender = async (dom: HTMLElement | null | undefined, renderOptions?: Options, opt?: useCanvasPosterOptions): Promise<string | null> => {
    // dom 为空
    if (!dom) return null

    // 转换为canvas
    const canvas = await html2canvas(dom, {
        windowWidth: dom.scrollWidth,   // 默认取元素宽度
        windowHeight: dom.scrollHeight, // 默认取元素高度
        logging: true,
        ...renderOptions  // 参数可覆盖默认值
    })
    console.log('canvas: ', canvas);
    const { type = 'image/webp', quality = 1 } = opt || {}
    const img = canvas.toDataURL(type, quality)
    console.log('img: ', img);
    return img

}

接下来,我们封装一个vue组件,来完成渲染dom海报,并进行预览的功能:

<template>
    <section class="flex flex-col justify-center items-center">
        <n-button @click="render2img">生成图片</n-button>
        <n-image v-if="imgBase64" width="400" :src="imgBase64" class="outline-(1px dashed blue) mt-3" />
    </section>
</template>

/**
* 渲染dom
*/
import { ref, type Ref, type PropType } from 'vue'
import { useDomRender } from '@/hooks/useDomRender'

const props = defineProps({
    // 需要渲染的DOM袁术
    domRef: {
      type: Object as PropType<HTMLElement | null>
    }
})
// base64图片
const imgBase64: Ref<null | string> = ref('')
// 调用渲染hook    
const render2img = async () => {
    imgBase64.value = await useDomRender(props.domRef)
}

3.一个入门实例

这里使用了naiveuidatatable组件渲染了一个简单的表格,然后再将其渲染为海报图片。

<section class="p-4">
    <section ref="domRef">
        <n-data-table :columns="columns" :data="data" :bordered="false" />
    </section>
    <renderDom class="mt-4" :dom-ref="domRef" />
</section>
import { h, ref } from 'vue'
import renderDom from '@/components/renderDom.vue';

// 原始DOM
const domRef = ref<HTMLElement | null>(null);

由于篇幅关系,这里省略表格数据部分。详细的代码已经托管在Gitee上,欢迎自取。

然后看一下页面的效果:

image.png

然后点击一下生成图片按钮,即可将上述表格内容渲染为图片:

image.png

4. 图片处理

这里新建一个页面,简单的加载一张图片:

<section class="p-4">
        <section ref="domRef" class="flex-inline flex-row items-center">
            <img src="https://apifoxmock.com/m1/4992867-4651676-default/avatar" class="w-50px h-50px rounded-full" />
            <span class="font-bold ml-2 whitespace-nowrap">酷酷的阿云</span>
        </section>
        <renderDom class="mt-4" :dom-ref="domRef" />
    </section>

渲染失败了,因为我们在没有允许图片的跨域请求。

image.png

4-1. useCORS

我们先来研究一下html2canvas参数文档,其中有一个useCORS参数,如果将其摄者为true,来看下能否成功:

// 转换为canvas
    const canvas = await html2canvas(dom, {
        windowWidth: dom.scrollWidth,   // 默认取元素宽度
        windowHeight: dom.scrollHeight, // 默认取元素高度
        logging: true,
        useCORS: true, // 默认使用图片跨域加载
        ...renderOptions
    })

没有问题,图片正确的渲染到canvas中: image.png

useCORS 参数的作用是决定是否尝试使用跨源资源共享(CORS)来加载图片。CORS 是一种安全机制,它允许或限制一个域下的Web应用如何与另一个域下的资源交互。

useCORS 设置为 true 时,html2canvas 会尝试使用 CORS 来加载跨域的图片资源。这意味着,如果图片位于不同的域上,浏览器会发送一个带有 CORS 请求头的 HTTP 请求,请求图片资源。如果图片服务器支持 CORS 并且响应头中包含了正确的 CORS 配置,那么图片就可以被加载到 canvas 中。

如果 useCORS 设置为 false(默认值),则 html2canvas 不会尝试使用 CORS 来加载跨域图片,这可能导致跨域图片无法被正确渲染到 canvas 上。

需要注意的是,即使 useCORS 设置为 true,如果图片服务器没有正确配置 CORS,图片仍然可能无法加载。因此,使用 CORS 加载跨域图片时,需要确保服务器端也支持 CORS。

4-2. 图片跨域

那么问题来了,如果这张图片是不允许跨域访问的,html2canvas还能正确处理吗?

刚才我们所使用的图片地址https://apifoxmock.com/m1/4992867-4651676-default/avatar是用apiFoxmock产生的一张图片,现在我们为其添加一个返回响应header:

image.png

我们为这张mock的图片添加了一张不存在的Access-Control-Allow-Origin响应头,来验证跨域访问。

结果可想而知,canvas渲染失败: image.png

4-3. allowTaint

我们再来关注一下allowTaint这个参数的作用:

allowTaint 设置为 true 时,它允许 html2canvas 将跨域图片绘制到 canvas 上,即使这些图片没有使用 CORS 正确加载。这通常涉及到将图片绘制到一个临时的 canvas 上,然后从这个临时 canvas 将图像数据复制到最终的 canvas 上。这样做的结果是,虽然图片可以被渲染,但是最终的 canvas 会被认为是“污染”的(tainted),这意味着你不能从这个 canvas 上获取图像数据,因为浏览器的安全策略禁止访问被污染的 canvas 的像素数据。

修改一下代码,将allowTaint 设置为 true:

// 转换为canvas
    const canvas = await html2canvas(dom, {
        windowWidth: dom.scrollWidth,
        windowHeight: dom.scrollHeight,
        logging: true,
        useCORS: true,
        allowTaint: true,
        ...renderOptions
    })

然后来看一下实际的效果:

image.png

确实,canvas生成成功了,但是不能导出DataURI,因为被污染了。


所以,不管是useCORS还是allowTaint 参数,只要图片源不允许跨域加载,那么都不能解决这个问题。


文章中的详细的代码已经托管在Gitee上,欢迎自取。