You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Three.js场景打印预览偏移求助:如何匹配截图显示效果

问题:Three.js场景打印预览偏移,无法与截图效果一致

我在运行Three.js项目时,使用window.print()打印场景,功能能运行但效果不符合预期:打印预览画面完全未居中且向左偏移。我希望实现打印预览和场景截图的显示效果完全一致,尝试过调整相机位置和视角但未达预期。

相关图示:

  • 场景图:场景图
  • 正常截图(renderer.render生成):正常截图
  • 打印预览图:打印预览图

以下是项目相关代码:

初始化代码

function init() {
                renderer = new THREE.WebGLRenderer({
                        antialias: true
                    }, { preserveDrawingBuffer: true });
                renderer.setPixelRatio(window.devicePixelRatio);
                renderer.setSize(window.innerWidth, window.innerHeight);
                renderer.shadowMap.enabled = true;
                renderer.domElement.className = 'realCanvas';
                container.appendChild(renderer.domElement);

                renderer.outputEncoding = THREE.sRGBEncoding;
                renderer.toneMapping = THREE.ACESFilmicToneMapping;

                pmremGenerator = new THREE.PMREMGenerator(renderer);
                pmremGenerator.compileEquirectangularShader();

                controls = new OrbitControls(camera, renderer.domElement);
                controls.target.set(0, 50, 0);
                controls.maxPolarAngle = Math.PI / 2;
                controls.minDistance = 350;
                controls.maxDistance = 2550;
                controls.minZoom = 350;
                controls.maxZoom = 2550;
                controls.zoomSpeed = 0.75;
                controls.panSpeed = 0.75;
                controls.rotateSpeed = 0.55;

                var minPan = new THREE.Vector3( - 1500,  - 1500,  - 1500);
                var maxPan = new THREE.Vector3(1500, 1500, 1500);
                var _v = new THREE.Vector3();

                controls.addEventListener("change", function () {
                    _v.copy(controls.target);
                    controls.target.clamp(minPan, maxPan);
                    _v.sub(controls.target);
                    camera.position.sub(_v);
                })

                controls.update();
                window.addEventListener('resize', onWindowResize, false);

                // stats
                stats = new Stats();
                stats.dom.classList.add("myStats");
                $('.myStats').css("display", "none");
                container.appendChild(stats.dom);
            }

背景初始化代码

function initBackground() {
                container = document.createElement('div');
                container.classList.add("myCanvas");
                document.body.appendChild(container);
                container.style.cssText = 'position:fixed; top:0;';
                camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 2.0, 100000);
                camera.position.set(-220, 100, -650); //(-220, 100, -650);

                scene = new THREE.Scene();
                scene.background = new THREE.Color(0xcce0ff);
                scene.fog = new THREE.Fog(0xcce0ff, 5000, 10000);
                scene.add(new THREE.AmbientLight(0x222222, 0.75));

                var light = new THREE.DirectionalLight(0xffffff, 0.01);
                light.position.set(200, 450, 500);
                light.castShadow = true;
                scene.add(light);

                var gt = new THREE.TextureLoader().load("examples/textures/terrain/street4.jpg");
                var gg = new THREE.PlaneBufferGeometry(32000, 32000);
                var gm = new THREE.MeshPhongMaterial({
                        color: 0xffffff,
                        map: gt
                    });

                ground = new THREE.Mesh(gg, gm);
                ground.rotation.x =  - Math.PI / 2;
                ground.material.map.repeat.set(640, 640);
                ground.material.map.wrapS = THREE.RepeatWrapping;
                ground.material.map.wrapT = THREE.RepeatWrapping;
                ground.material.map.encoding = THREE.sRGBEncoding;
                // note that because the ground does not cast a shadow, .castShadow is left false
                ground.receiveShadow = false;
                ground.position.set(-20, -20, -20);
                scene.add(ground);
                $('div myCanvas').hide(); // before canvas but color picker colored square is also canvas
            }

正常截图代码

jQuery('#screenshot').click(function () {
                renderer.render(scene, camera);
                renderer.domElement.toBlob(function(blob) {
                    saveAs(blob, "Configurator");
                });
            });

打印预览代码

const mediaQuery = window.matchMedia('print');
            mediaQuery.addEventListener("change", () => {
                renderer.render(scene, camera);
                /*const c = document.getElementsByClassName("realCanvas");
                if(c.width === -1){
                    renderer.setSize(window.innerWidth, window.innerHeight);
                    c.width=window.innerWidth;
                } else {
                    renderer.setSize(1050, 800);
                    //renderer.setSize(window.innerWidth * 0.8, window.innerHeight * 0.8);
                    c.width=-1;
                }*/
                        
            });

解决方案

方法1:CSS强制打印布局居中

打印偏移的核心原因是打印媒体下Canvas的布局与屏幕布局不一致,添加以下CSS直接控制打印时的Canvas样式:

@media print {
  .realCanvas {
    position: absolute !important;
    top: 50% !important;
    left: 50% !important;
    transform: translate(-50%, -50%) !important;
    max-width: 100% !important;
    max-height: 100% !important;
  }
  .myCanvas {
    width: 100vw !important;
    height: 100vh !important;
    position: relative !important;
  }
  body *:not(.myCanvas):not(.realCanvas) {
    display: none !important;
  }
}

这段代码会在打印时隐藏无关元素,仅保留Canvas容器和Canvas本身,并强制Canvas居中显示。

方法2:基于截图生成图片打印

既然截图效果正常,可直接复用截图结果打印,绕过Canvas打印适配问题:

// 替换原打印监听逻辑,绑定到打印按钮
document.querySelector('#print-btn').addEventListener('click', () => {
  renderer.render(scene, camera);
  renderer.domElement.toBlob((blob) => {
    const img = document.createElement('img');
    img.src = URL.createObjectURL(blob);
    img.style.position = 'fixed';
    img.style.top = '0';
    img.style.left = '0';
    img.style.width = '100%';
    img.style.height = '100%';
    img.style.objectFit = 'contain';
    
    // 临时添加图片到页面
    document.body.appendChild(img);
    // 触发打印
    window.print();
    // 打印完成后清理临时资源
    setTimeout(() => {
      document.body.removeChild(img);
      URL.revokeObjectURL(img.src);
    }, 100);
  });
});

方法3:调整相机适配打印比例

如果坚持使用Canvas直接打印,需在打印状态下重新计算相机宽高比,适配纸张比例:

const mediaQuery = window.matchMedia('print');
mediaQuery.addEventListener("change", () => {
  if (mediaQuery.matches) {
    // 适配A4纸张宽高比(约1.414)
    const printWidth = 800;
    const printHeight = printWidth / 1.414;
    renderer.setSize(printWidth, printHeight);
    // 更新相机参数
    camera.aspect = printWidth / printHeight;
    camera.updateProjectionMatrix();
    renderer.render(scene, camera);
  } else {
    // 回到屏幕状态,恢复原尺寸
    renderer.setSize(window.innerWidth, window.innerHeight);
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.render(scene, camera);
  }
});

内容的提问来源于stack exchange,提问作者sebi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 15:31:12