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.一个入门实例
这里使用了naiveui的datatable组件渲染了一个简单的表格,然后再将其渲染为海报图片。
<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上,欢迎自取。
然后看一下页面的效果:
然后点击一下生成图片按钮,即可将上述表格内容渲染为图片:
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>
渲染失败了,因为我们在没有允许图片的跨域请求。
4-1. useCORS
我们先来研究一下html2canvas参数文档,其中有一个useCORS参数,如果将其摄者为true,来看下能否成功:
// 转换为canvas
const canvas = await html2canvas(dom, {
windowWidth: dom.scrollWidth, // 默认取元素宽度
windowHeight: dom.scrollHeight, // 默认取元素高度
logging: true,
useCORS: true, // 默认使用图片跨域加载
...renderOptions
})
没有问题,图片正确的渲染到canvas中:
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:
我们为这张mock的图片添加了一张不存在的Access-Control-Allow-Origin响应头,来验证跨域访问。
结果可想而知,canvas渲染失败:
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
})
然后来看一下实际的效果:
确实,canvas生成成功了,但是不能导出DataURI,因为被污染了。
所以,不管是useCORS还是allowTaint 参数,只要图片源不允许跨域加载,那么都不能解决这个问题。
文章中的详细的代码已经托管在Gitee上,欢迎自取。