AR.js+Three.js场景截图功能失效求助:已参照旧帖仍无法实现
解决AR.js + Three.js 无法截取WebGL页面截图的问题
我仔细看了你的代码和问题描述,发现几个关键问题导致截图功能失效,咱们一步步来修复:
1. 首先解决Three.js版本冲突问题
你在head里同时引入了两个不同版本的Three.js:
<script src='js/three.js'></script> <!-- 下面这个旧版本会和上面的新版本冲突 --> <script src="http://cdnjs.cloudflare.com/ajax/libs/three.js/r69/three.min.js"></script>
这会导致全局THREE对象被覆盖,很多新API(比如你用的MTLLoader、OBJLoader的写法)会失效。直接删掉r69版本的引用,确保只保留项目里的js/three.js,同时要保证OBJLoader.js和MTLLoader.js的版本和它匹配。
2. 修正Renderer的截图配置与时机
虽然你设置了preserveDrawingBuffer: true,但在AR场景中,因为实时渲染的特性,有时候buffer会被快速覆盖。可以在截图前手动触发一次渲染,确保当前帧被正确捕获:
function saveAsImage() { var imgData; try { // 截图前手动渲染一次当前场景 renderer.render(scene, camera); var strMime = "image/jpeg"; imgData = renderer.domElement.toDataURL(strMime, 0.9); // 第二个参数是画质,范围0-1 saveFile(imgData.replace(strMime, strDownloadMime), "test.jpg"); } catch (e) { console.error("截图失败:", e); alert("截图失败,请查看控制台日志"); return; } }
3. 修复DOM结构的错误
你的script标签嵌套在了<div id="capture">里面,而且saveLink的创建逻辑放在了初始化函数里,容易导致DOM层级问题。建议把HTML结构整理清晰:
<body style='margin : 0px; overflow: hidden; font-family: Monospace;'> <!-- 把saveLink的容器放在最外层,避免嵌套问题 --> <div id="capture" style='position: absolute; top: 10px; width:100%; z-index:1'> <a href="#" id="saveLink" style="color: white; text-align: center; display: block;">Save Frame</a> </div> <!-- 脚本放在body末尾,确保DOM加载完成 --> <script> // 你的所有JS代码放在这里 </script> </body>
同时把JS里创建saveLink的代码删掉,直接用HTML里的元素绑定事件:
// 初始化函数里只需要绑定事件 document.getElementById("saveLink").addEventListener('click', saveAsImage);
4. 优化saveFile函数的兼容性
有些浏览器对dataURL的处理有大小限制,改用Blob对象来生成下载链接会更可靠:
var saveFile = function (strData, filename) { // 将dataURL转为Blob var byteString = atob(strData.split(',')[1]); var mimeString = strData.split(',')[0].split(':')[1].split(';')[0]; var ab = new ArrayBuffer(byteString.length); var ia = new Uint8Array(ab); for (var i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); } var blob = new Blob([ab], {type: mimeString}); var link = document.createElement('a'); if (typeof link.download === 'string') { link.download = filename; link.href = URL.createObjectURL(blob); link.click(); // 释放Blob URL,避免内存泄漏 setTimeout(() => URL.revokeObjectURL(link.href), 100); } else { console.error("当前浏览器不支持下载功能"); alert("当前浏览器不支持下载功能"); } }
修改后的完整代码
<head> <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> <title>TESTE 4</title> <!-- 只保留一个Three.js版本 --> <script src='js/three.js'></script> <script src='js/OBJLoader.js'></script> <script src='js/MTLLoader.js'></script> <script src="jsartoolkit5/artoolkit.min.js"></script> <script src="jsartoolkit5/artoolkit.api.js"></script> <script src="threex/threex-artoolkitsource.js"></script> <script src="threex/threex-artoolkitcontext.js"></script> <script src="threex/threex-arbasecontrols.js"></script> <script src="threex/threex-armarkercontrols.js"></script> <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script> <script src="http://threejs.org/examples/js/libs/stats.min.js"></script> </head> <body style='margin : 0px; overflow: hidden; font-family: Monospace;'> <div id="capture" style='position: absolute; top: 10px; width:100%; z-index:1'> <a href="#" id="saveLink" style="color: white; text-align: center; display: block;">Save Frame</a> </div> <script> var scene, camera, renderer, clock, deltaTime, totalTime; var arToolkitSource, arToolkitContext; var markerRoot1; var mesh0; // 统一变量名,避免之前的命名混乱 var strDownloadMime = "image/octet-stream"; initialize(); animate(); function initialize() { scene = new THREE.Scene(); let ambientLight = new THREE.AmbientLight(0xcccccc, 1.0); scene.add(ambientLight); camera = new THREE.Camera(); scene.add(camera); renderer = new THREE.WebGLRenderer({ preserveDrawingBuffer: true, precision: "mediump", antialias: true, alpha: true }); renderer.setClearColor(new THREE.Color('lightgrey'), 0) renderer.setSize(window.innerWidth, window.innerHeight); // 用窗口大小适配不同设备 renderer.domElement.style.position = 'absolute' renderer.domElement.style.top = '0px' renderer.domElement.style.left = '0px' document.body.appendChild(renderer.domElement); // 绑定截图按钮事件 document.getElementById("saveLink").addEventListener('click', saveAsImage); clock = new THREE.Clock(); deltaTime = 0; totalTime = 0; arToolkitSource = new THREEx.ArToolkitSource({ sourceType: 'webcam', }); function onResize() { arToolkitSource.onResize() arToolkitSource.copySizeTo(renderer.domElement) if (arToolkitContext.arController !== null) { arToolkitSource.copySizeTo(arToolkitContext.arController.canvas) } } arToolkitSource.init(function onReady() { onResize() }); window.addEventListener('resize', function () { onResize() }); arToolkitContext = new THREEx.ArToolkitContext({ cameraParametersUrl: 'data/camera_para.dat', detectionMode: 'mono' }); arToolkitContext.init(function onCompleted() { camera.projectionMatrix.copy(arToolkitContext.getProjectionMatrix()); }); markerRoot1 = new THREE.Group(); scene.add(markerRoot1); let markerControls1 = new THREEx.ArMarkerControls(arToolkitContext, markerRoot1, { type: 'pattern', patternUrl: "data/boxa.patt", }) function onProgress(xhr) { console.log((xhr.loaded / xhr.total * 100) + '% loaded'); } function onError(xhr) { console.log('An error happened'); } new THREE.MTLLoader() .setPath('models/') .load('space.mtl', function (materials) { materials.preload(); new THREE.OBJLoader() .setMaterials(materials) .setPath('models/') .load('space.obj', function (group) { mesh0 = group.children[0]; mesh0.scale.set(0.25, 0.25, 0.25); markerRoot1.add(mesh0); }, onProgress, onError); }); } var posi = 1; // 统一变量初始化位置 function update() { if (!mesh0) return; // 避免模型未加载时触发报错 if (posi == 1 && mesh0.position.y <= 1.30) mesh0.position.y += 0.01; if (mesh0.position.y > 1.30) posi = 2; if (posi == 2) mesh0.position.y -= 0.01; if (mesh0.position.y <= 0.00) posi = 1; if (arToolkitSource.ready !== false) arToolkitContext.update(arToolkitSource.domElement); } function render() { renderer.render(scene, camera); } function animate() { requestAnimationFrame(animate); deltaTime = clock.getDelta(); totalTime += deltaTime; update(); render(); } function saveAsImage() { var imgData; try { // 手动渲染当前帧确保截图正确 renderer.render(scene, camera); var strMime = "image/jpeg"; imgData = renderer.domElement.toDataURL(strMime, 0.9); saveFile(imgData.replace(strMime, strDownloadMime), "test.jpg"); } catch (e) { console.error("截图失败:", e); alert("截图失败,请查看控制台日志"); return; } } var saveFile = function (strData, filename) { var byteString = atob(strData.split(',')[1]); var mimeString = strData.split(',')[0].split(':')[1].split(';')[0]; var ab = new ArrayBuffer(byteString.length); var ia = new Uint8Array(ab); for (var i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); } var blob = new Blob([ab], { type: mimeString }); var link = document.createElement('a'); if (typeof link.download === 'string') { link.download = filename; link.href = URL.createObjectURL(blob); link.click(); setTimeout(() => URL.revokeObjectURL(link.href), 100); } else { console.error("当前浏览器不支持下载功能"); alert("当前浏览器不支持下载功能"); } } </script> </body> </html>
最后测试注意事项
- 确保你的摄像头权限已经被允许
- 确保AR标记(boxa.patt)可以被正确识别,模型加载完成
- 点击页面顶部的「Save Frame」按钮,就能正常下载截图了
内容的提问来源于stack exchange,提问作者Xand Fernandes
相关产品推荐
相关产品推荐

