我正在使用 puppeteer 使用
screenshot
方法将一些 HTML 转换为 PNG。
首先,我获取一些 SVG,然后创建一个页面,并将 SVG 设置为页面内容。
fetch(url)
.then(data => data.text())
.then((svgText) => {
// res.set('Content-Type', 'text/html');
const $ = cheerio.load(svgText)
return $.html()
})
.then(async (html) => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.setContent(html)
const file = await page.screenshot()
res.set('Content-Type', 'image/png');
await browser.close()
res.send(file)
})
.catch((err) => {
console.log(err);
logger.log({
level: 'error', message: 'GET /product', err
})
})
})
问题是,我的 SVG 中的文本包含特定字体。该字体是使用 @import CSS 标签加载的。如果我将方法设置为返回 HTML,则会加载字体,然后,在稍有延迟后,它们就会应用到我的文本中。不幸的是,当使用
screenshot
方法时,我的文本不再有样式。我想这是因为屏幕截图是在加载和应用字体之前拍摄的,因此使用后备字体渲染文本。
有没有办法在截图之前确保页面完全渲染?
我尝试使用 page.on('load') 事件侦听器,但这不会改变脚本永远运行的任何内容。
当然可以
waitForNetworkIdle()
,但我很确定这个方法不能用于检测字体是否正确加载。
您绝对可以使用 page.on('request')
检查页面的所有请求,并使用 resourceType
过滤此字体作为 font
。
但是你还是得再耽搁一些时间,因为如果字体文件足够大的话,可能会有一些FOUC(Flash Of Unloaded Content)(大的TTF有时可能无法加载到浏览器中)。您可以将时间设置为正确的时间,因为字体已正确加载。因此,在用于生产/测试之前测试此脚本。
如果您不确定浏览器本身会加载什么类型的字体,您可以直接使用字体 URL 过滤页面请求,如 EOT、WOFF、WOFF2、SVG、TTF、OTF 等。
字体文件下载成功后,不要忘记等待一段时间,您可以使用自己的延迟功能。在此示例中,我将一个名为
fontSuccessLoaded
的变量作为用于标记计时的布尔值,将 fontSuccessLoaded
作为用于标记下载失败的布尔值以及用于等待目的的 delayFor
函数。
// Method for waiting delay timing
async delayFor(time) {
return new Promise(function(resolve) {
setTimeout(resolve, time)
})
}
fetch(url)
.then(data => data.text())
.then((svgText) => {
// res.set('Content-Type', 'text/html');
const $ = cheerio.load(svgText)
return $.html()
})
.then(async (html) => {
let fontSuccessLoaded = 0
let fontDownloadFailed = 0
const browser = await puppeteer.launch()
const page = await browser.newPage()
const pathsUrl = 'https://www.example.com/fonts/'
const fontsExt = ['eot', 'otf', 'ttf', 'svg', 'woff', 'woff2']
page.setRequestInterception(true)
page.on('request', async (request) => {
// Filter using resource type
if (request.resourceType() === 'font') {
console.log('Font starting to be requested')
}
// Filter using fonts URL directly
if (request.url().search(pathsUrl) > -1) {
fontsExt.forEach(extension => {
if (request.url().toLowerCase().search(extension) > -1) {
console.log('Font type requested:', extension)
}
})
}
// You'll have to use this continue,
// or all if your page requests will be blocked
await request.continue()
})
page.on('response', async (response) => {
if (response.request().resourceType() === 'font') {
console.log('Font download starting')
}
})
page.on('requestfinished', async (request) => {
if (request.resourceType() === 'font') {
console.log('Font download finished')
await delayFor(1000)
fontSuccessLoaded = 1
}
})
page.on('requestfailed', async (request) => {
if (request.resourceType() === 'font') {
console.log('Font request failed')
fontDownloadFailed = 1
}
})
await page.setContent(html)
// This will delay the screenshot process
// before font file download and loaded
// and not failed to download (aborted)
while (!fontSuccessLoaded && !fontDownloadFailed) {
await delayFor(500)
}
const file = await page.screenshot()
res.set('Content-Type', 'image/png');
await browser.close()
res.send(file)
})
.catch((err) => {
console.log(err);
logger.log({
level: 'error', message: 'GET /product', err
})
})
})
然后可以在 console.log 中显示如下:
Font starting to be requested
Font type requested: woff
Font download starting
Font download finished