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的瓦片适配逻辑冲突,出现层级选择错误。
修复方案
- 调整媒体模拟类型
将await page.emulateMediaType('print')改为await page.emulateMediaType('screen'),避免print媒体样式触发的地图布局重算,和浏览器手动打印逻辑保持一致。 - 匹配视口与PDF尺寸
当前设置的视口尺寸1024768和Letter横向PDF的实际像素尺寸(96DPI下为1056816)不匹配,导出时会触发重排。可根据导出的纸张尺寸动态设置对应视口:
// 示例:Letter横向的视口设置,deviceScaleFactor为2时乘以对应倍数 await page.setViewport({ width: 1056, height: 816, deviceScaleFactor: 2 });
- 导出前强制对齐缩放层级
在生成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操作
- 优化MapKit瓦片图层配置
在Leaflet的MapKit TileLayer配置中添加以下参数,提升重排时的瓦片缓存适配能力:
L.tileLayer('your-mapkit-url', { // 其他原有配置 keepBuffer: 2, zoomOffset: 0 }).addTo(map);
内容的提问来源于stack exchange,提问作者fnllc
相关产品推荐
相关产品推荐

