Puppeteer + Nodejs 通用全屏网页截图方案(四)页面处理

语言: CN / TW / HK

theme: scrolls-light

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第28天,点击查看活动详情

# (一) 基本功能

# (二) 常用参数实现

# (三) 进阶处理

如何在页面中判断图片已加载完成

主动调用注入函数进行截图的场景,通常都是我们自己的业务页面,这时我们可以在页面中对资源加载情况进行判断,来决定截图的时机。

起初我的想法是把图片url链接传进一个处理函数,函数中用Image对象加载src,当实例对象触发onload时就接着下一个,直到全部处理完毕那么也就算资源加载完成,但是很快我发现这样处理并不对,因为这个函数实际是异步的,很可能先加载完了资源但是该资源在页面中却并未加载完成,所以正确的方式应该是获取到页面当中真实的资源DOM节点,传入这个函数中处理,而且onload回调也不一定正确,经测试最稳定的方式是轮询complete属性来确定是否真的加载完成。

js // Preload.ts 参考代码 export default class PreLoad { private i: number private arr: any[] constructor(arr: string[]) { this.i = 0 this.arr = arr } public doms() { return new Promise((resolve: Function) => { const work = () => { if (this.i < this.arr.length) { this.arr[this.i].complete && this.i++ // 核心是轮询img节点的complete属性 setTimeout(() => { work() }, 100) } else { resolve() } } work() }) } } 假设业务页面当中,内容是通过接口请求到前端渲染,为每个图片的div容器我都加上了img__box这个class样式,即<div class="img__box"> <img /> </div>这种形式,那么我可以这么处理:

```js const imgsData = [] const cNodes = document.querySelectorAll('.img__box') for (const el of cNodes) { imgsData.push(el.firstChild) }

const preload = new Preload(imgsData) await preload.doms() // 实例化上面的Preload函数,开始轮询资源

console.log('--> 加载完成,可以开始截图') try { window.loadFinishToInject('done') // 触发Puppeteer的注入方法 } catch (err) {} ```

懒加载页面处理方法

有时我们会遇到截取页面资源是懒加载的情况,像生成第三方网页文章时会非常不稳定,而且也不能通过单纯的sleep等待函数来解决问题。

所以我们需要加一个自动滚动的方法,来模拟真实的页面浏览触发页面的资源懒加载。

实现的核心是利用 Puppeteerevaluate 函数,改方法可以在目标页面上下文中执行JS代码,简单的触底判断:比较两次滚动后的scrollTop是否一致,如果一致就是页面不再往下滚了,即判断为触底。

js // 创建自动滚动函数 async function autoScroll() { await page.evaluate(async () => { await new Promise((resolve, reject) => { try { const maxScroll = Number.MAX_SAFE_INTEGER let lastScroll = 0 const interval = setInterval(() => { window.scrollBy(0, 100) const scrollTop = document.documentElement.scrollTop || window.scrollY if (scrollTop === maxScroll || scrollTop === lastScroll) { // 判断触底,或超出js最大安全长度 clearInterval(interval) resolve() } else { lastScroll = scrollTop } }, 100) // 100毫秒执行间隔 } catch (err) { console.log(err) reject(err) } }) }) } 在截图前加入该函数: js page.on('load', async () => { await autoScroll() // 自动截图时先模拟滚动 await sleep(wait) // 前面实现的等待方法 // 开始截图 await page.screenshot({ path, fullPage: true }) // 关闭浏览器 await browser.close() }) 这样当页面加载完成后,就会触发自动滚动,每100毫秒向下滚100像素,直到触底为止,跳出Promise,配合前面我们实现的wait参数,等待 x 毫秒后开始截图(这里滚动只是触发了资源加载,如果不等待一下资源有可能没加载完)

最终结果如下,大部分页面的情况应该都差不多:

| 正常截图 | 加入自动滚动 | | --- | --- | | image.png | image.png |

下一篇文章讲讲服务器部署的相关细节。