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

Puppeteer导出含Apple MapKit瓦片的Leaflet页面为PDF时瓦片缩放异常

Puppeteer导出搭载Apple MapKit瓦片的Leaflet地图页面为PDF异常

使用Puppeteer导出搭载Apple MapKit瓦片的Leaflet地图页面为PDF时出现瓦片缩放层级错误的异常,使用谷歌地图、必应地图瓦片时导出功能完全正常。

效果对比

预期输出

预期输出效果

实际输出

实际输出效果
可观察到瓦片缩放层级错误。

复现步骤

本地启动Puppeteer服务后执行如下curl命令:

curl -X GET -G "http://localhost:8080" --data-urlencode "url=https://en.mycoursewalk.com/coursewalks/18624/print?print_pdf=true&unlisted_id=e61d9b86d7" --data-urlencode "page_orientation=Landscape" --data-urlencode "paper_size=Letter" --output "test.pdf"

也可以直接通过浏览器访问测试页面:https://en.mycoursewalk.com/coursewalks/18624/print?unlisted_id=e61d9b86d7

项目配置

package.json

{
  "name": "chrome-puppeteer-pdf-export",
  "version": "1.0.1",
  "description": "",
  "engines": {
    "node": "12.22.6"
  },
  "main": "app.js",
  "directories": {
    "doc": "doc",
    "lib": "lib",
    "test": "test"
  },
  "scripts": {
    "start": "node app.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "dependencies": {
    "express": "^4.17.1",
    "valid-url": "^1.0.9",
    "puppeteer": "10.4.0"
  }
}

核心代码app.js

const express = require('express');
const app = express();
const puppeteer = require('puppeteer');
const port = process.env.PORT || 8080;
const validUrl = require('valid-url');

const parseUrl = function(url) {
    url = decodeURIComponent(url);
    if (!/^(?:f|ht)tps?\:\/\//.test(url)) {
        url = 'http://' + url;
    }

    return url;
};

app.get('/', function(req, res) {
    const urlToScreenshot = parseUrl(req.query.url);

    if (validUrl.isWebUri(urlToScreenshot)) {
        console.log('Screenshotting: ' + urlToScreenshot);

        const page_orientation = req.query.page_orientation;
        const paper_size = req.query.paper_size;

        (async() => {

            let browser;
            try {
                browser = await puppeteer.launch({
                    args: ['--no-sandbox', '--disable-setuid-sandbox']
                });

                const page = await browser.newPage();
                await page.emulateMediaType('print');
                await page.setCacheEnabled(false);
                await page.setViewport({ width: 1024, height: 768, deviceScaleFactor: 2 });
                await page.goto(urlToScreenshot, { timeout: 30000, waitUntil: 'networkidle0' });
                await page.waitFor(250);
                await page.pdf({
                    format: paper_size,
                    landscape: (page_orientation === 'Landscape'),
                    margin: { top: 36, right: 36, bottom: 20, left: 36 },
                    printBackground: true
                }).then(function(buffer) {
                    res.setHeader('Content-Disposition', 'attachment;filename="export.pdf"');
                    res.setHeader('Content-Type', 'application/pdf');
                    res.send(buffer)
                });
            } catch (err) {
                console.log(err.message);
            } finally {
                if (browser) {
                    browser.close();
                }
            }

        })();
    } else {
        res.send('Invalid url: ' + urlToScreenshot);
    }

});

app.listen(port, function() {
    console.log('App listening on port ' + port)
});

补充说明

  • 该问题仅在地图使用小数缩放级别时触发,项目允许缩放步长为0.25;
  • 仅Apple MapKit瓦片存在该问题,谷歌、必应瓦片导出正常;
  • 预期输出截图来自谷歌浏览器手动打印导出PDF的结果,地图在浏览器中展示完全正常,仅使用Puppeteer自动生成PDF时出现异常。

解决方案

原因分析

Apple MapKit仅提供整数缩放层级的瓦片资源,而谷歌、必应瓦片支持适配小数缩放的动态拉伸逻辑。当前Puppeteer导出流程会触发页面重排,导致Leaflet在重算缩放时和MapKit的瓦片适配逻辑冲突,出现层级选择错误。

修复方案

  1. 调整媒体模拟类型
    将await page.emulateMediaType('print')改为await page.emulateMediaType('screen'),避免print媒体样式触发的地图布局重算,和浏览器手动打印逻辑保持一致。
  2. 匹配视口与PDF尺寸
    当前设置的视口尺寸1024768和Letter横向PDF的实际像素尺寸(96DPI下为1056816)不匹配,导出时会触发重排。可根据导出的纸张尺寸动态设置对应视口:
// 示例:Letter横向的视口设置,deviceScaleFactor为2时乘以对应倍数
await page.setViewport({ width: 1056, height: 816, deviceScaleFactor: 2 });
  1. 导出前强制对齐缩放层级
    在生成PDF前通过注入代码强制将地图缩放对齐到最近的整数层级,等瓦片加载完成后再导出:
await page.waitFor(250);
// 新增以下代码
await page.evaluate(() => {
  // 替换为页面实际挂载的地图实例变量名
  const map = window.map;
  if (map) {
    map.setZoom(Math.round(map.getZoom()), { animate: false });
  }
});
await page.waitFor(500); // 等待瓦片加载完成
// 再执行page.pdf操作
  1. 优化MapKit瓦片图层配置
    在Leaflet的MapKit TileLayer配置中添加以下参数,提升重排时的瓦片缓存适配能力:
L.tileLayer('your-mapkit-url', {
  // 其他原有配置
  keepBuffer: 2,
  zoomOffset: 0
}).addTo(map);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 06:06:03