如何截取包含HTML内容与Three.js场景的完整页面截图?
实现方案
最稳定的实现方式是分层截取后手动合成,不需要依赖复杂第三方库,兼容性最好,具体方案如下:
方案1:手动分层合成(推荐)
核心思路是分别截取WebGL画布内容和普通HTML内容,按照元素在页面的实际位置合并到同一张画布上导出。
前置配置
初始化Three.js渲染器时必须开启preserveDrawingBuffer配置,否则WebGL缓冲区会在渲染后清空,无法读取画布内容:
const renderer = new THREE.WebGLRenderer({ preserveDrawingBuffer: true, // 必须添加该配置 alpha: true, antialias: true // 其余原有配置保持不变 })
实现步骤
- 记录当前页面滚动位置,临时隐藏Three.js对应的canvas元素,避免html2canvas重复渲染画布内容
- 调用html2canvas截取整页HTML内容,得到HTML层的canvas对象
- 恢复canvas元素的显示,调用
renderer.domElement.toDataURL()拿到Three.js场景的截图 - 创建和整页尺寸(宽为
document.documentElement.scrollWidth,高为document.documentElement.scrollHeight)一致的空白画布 - 按照元素实际布局位置,先把Three.js场景截图绘制到空白画布底层,再把HTML层截图绘制到上层
- 把最终合成的画布转成图片格式,调用下载方法保存
参考实现代码
// 原有保存文件方法可直接复用,修正了原方法中未定义变量的问题 var saveFile = function(strData, filename) { var link = document.createElement('a'); if (typeof link.download === 'string') { document.body.appendChild(link); //Firefox requires the link to be in the body link.download = filename; link.href = strData; link.click(); document.body.removeChild(link); //remove the link when done } else { location.replace(strData); } } async function takeFullPageScreenshot() { const pageWidth = document.documentElement.scrollWidth; const pageHeight = document.documentElement.scrollHeight; const threeCanvas = core.renderer.domElement; // 记录canvas在页面中的实际位置 const canvasRect = threeCanvas.getBoundingClientRect(); const scrollX = window.scrollX; const scrollY = window.scrollY; // 截取HTML层 threeCanvas.style.visibility = 'hidden'; // 临时隐藏canvas避免重复截取 const htmlCanvas = await html2canvas(document.body, { width: pageWidth, height: pageHeight, scrollX: 0, scrollY: 0, useCORS: true // 允许加载跨域图片 }); threeCanvas.style.visibility = 'visible'; // 读取Three.js场景截图 const threeImg = new Image(); threeImg.src = core.renderer.domElement.toDataURL('image/jpeg', 0.95); await new Promise(resolve => threeImg.onload = resolve); // 合成最终图片 const finalCanvas = document.createElement('canvas'); finalCanvas.width = pageWidth; finalCanvas.height = pageHeight; const ctx = finalCanvas.getContext('2d'); // 先绘制Three.js场景层 ctx.drawImage( threeImg, canvasRect.left + scrollX, canvasRect.top + scrollY, canvasRect.width, canvasRect.height ); // 再绘制HTML内容层 ctx.drawImage(htmlCanvas, 0, 0, pageWidth, pageHeight); // 导出下载 const finalImgData = finalCanvas.toDataURL('image/jpeg', 0.95); saveFile(finalImgData.replace('image/jpeg', 'image/octet-stream'), 'full-screenshot.jpg'); } // 调用方法即可触发截图 takeFullPageScreenshot().catch(err => console.error('截图失败:', err));
方案2:使用支持WebGL的DOM截图库
如果不想手动处理合成逻辑,可以选择原生支持WebGL画布识别的截图库,这类库不会尝试把canvas元素转HTML重绘,而是直接读取WebGL画布的像素内容,自动处理层级合成,直接调用整页截图接口即可得到包含Three.js场景的完整截图。注意不要使用不支持WebGL的旧版截图库,否则会出现画布区域全黑的问题。
方案3:原生渲染层截图(适用于桌面端套壳场景)
如果页面运行在Electron、PWA桌面端等环境,可以直接调用Chromium内核内置的页面捕获API,这个API是从浏览器渲染层直接读取像素,不管是DOM内容还是WebGL、CSS3D内容都可以完整截取,不需要额外处理兼容问题。
常见坑点
- 所有图片资源(包括HTML里的
img标签、Three.js加载的纹理)都要配置CORS跨域允许,否则画布会被跨域污染,调用toDataURL时会抛出安全错误 - 如果页面有fixed定位的元素,截图前要把滚动位置重置到顶部,或者计算位置时把滚动偏移量算准,避免元素错位
- 如果Three.js场景开启了后处理效果,要确保后处理通道渲染完成后再调用截图方法,否则会截到未渲染完成的画面
- 高清屏场景下要把
devicePixelRatio算入画布尺寸,否则导出的截图会模糊
内容的提问来源于stack exchange,提问作者Nino Zurashvili
相关产品推荐
相关产品推荐

