如何用JavaScript将带路线的Google Map转为图片?解决Canvas污染问题
嘿,这个问题我之前帮不少开发者踩过坑!你遇到的Tainted canvases may not be exported错误,本质是浏览器同源策略的限制:当你用Google Maps Directions Service加载路线相关资源时,这些资源来自Google的跨域服务器,一旦Canvas加载了这类未正确配置CORS的跨域资源,就会被标记为“污染”,浏览器会直接阻止你调用toDataURL导出它。普通地图没问题,是因为基础瓦片的CORS配置在静态渲染时没触发限制,但路线服务的动态资源刚好踩中了这个规则。
下面给你一套可行的解决方案,一步步来:
1. 先升级html2canvas版本
你现在用的是0.4.1这个超老版本,它对跨域资源的处理有很多bug。直接换成最新的稳定版(比如1.4.1),新版本对CORS的支持完善太多了。
2. 调整html2canvas的配置
要同时开启useCORS和allowTaint(别担心,allowTaint只是允许合法的跨域资源加载,配合useCORS不会有安全问题),再加个scale参数提升导出图片的清晰度:
$("#divclick").click(function() { html2canvas(document.querySelector("#totalimage"), { useCORS: true, allowTaint: true, scale: 2 // 导出2倍图,更清晰 }).then(function(canvas) { try { const dataURL = canvas.toDataURL("image/png"); alert(dataURL); $('#img_val').val(dataURL); $("#show_img").append(canvas); } catch (e) { console.error("导出失败:", e); } }).catch(err => console.error("渲染失败:", err)); });
3. 优化Google Maps的初始化配置
在创建地图的时候,加上imageExportEnabled: true这个关键配置(它专门用来允许地图资源被导出),同时禁用默认UI减少不必要的跨域资源加载:
window.onload = function () { var mapOptions = { center: new google.maps.LatLng(markers[0].lat, markers[0].lng), zoom: 10, mapTypeId: google.maps.MapTypeId.ROADMAP, imageExportEnabled: true, // 允许地图资源导出 disableDefaultUI: true // 禁用默认UI,减少跨域资源 }; var map = new google.maps.Map(document.getElementById("dvMap"), mapOptions); // 后面的标记点、路线代码保持不变就行 // ... };
4. 合并重复的点击事件
你的代码里重复绑定了#divclick的点击事件(一次在开头,一次在$(function(){})里),会导致触发两次导出,直接合并成一次就好。
修正后的完整代码
我把所有调整整合好了,直接用就行:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script> <script src="https://maps.google.com/maps/api/js?&libraries=places&key=AIzaSyAHRgfjMojo9Q6LtvpmB40V39FZtYZTPJ0"></script> <script src="https://cdn.jsdelivr.net/npm/canvas2image@1.0.5/canvas2image.min.js"></script> <div id="totalimage"> <div id="dvMap" style="width: 100%; height: 500px"></div> </div> <div id="show_img"></div> <button id="divclick">print</button> <script> var markers = [ { "title": 'Alibaug', "lat": '18.641400', "lng": '72.872200', "description": 'Alibaug is a coastal town and a municipal council in Raigad District in the Konkan region of Maharashtra, India.' }, { "title": 'Mumbai', "lat": '18.964700', "lng": '72.825800', "description": 'Mumbai formerly Bombay, is the capital city of the Indian state of Maharashtra.' }, { "title": 'Pune', "lat": '18.523600', "lng": '73.847800', "description": 'Pune is the seventh largest metropolis in India, the second largest in the state of Maharashtra after Mumbai.' } ]; window.onload = function () { var mapOptions = { center: new google.maps.LatLng(markers[0].lat, markers[0].lng), zoom: 10, mapTypeId: google.maps.MapTypeId.ROADMAP, imageExportEnabled: true, disableDefaultUI: true }; var map = new google.maps.Map(document.getElementById("dvMap"), mapOptions); var infoWindow = new google.maps.InfoWindow(); var lat_lng = new Array(); var latlngbounds = new google.maps.LatLngBounds(); for (i = 0; i < markers.length; i++) { var data = markers[i]; var myLatlng = new google.maps.LatLng(data.lat, data.lng); lat_lng.push(myLatlng); var marker = new google.maps.Marker({ position: myLatlng, map: map, title: data.title }); latlngbounds.extend(marker.position); (function (marker, data) { google.maps.event.addListener(marker, "click", function (e) { infoWindow.setContent(data.description); infoWindow.open(map, marker); }); })(marker, data); } map.setCenter(latlngbounds.getCenter()); map.fitBounds(latlngbounds); // 路线绘制逻辑 var path = new google.maps.MVCArray(); var service = new google.maps.DirectionsService(); var poly = new google.maps.Polyline({ map: map, strokeColor: '#4986E7' }); for (var i = 0; i < lat_lng.length; i++) { if ((i + 1) < lat_lng.length) { var src = lat_lng[i]; var des = lat_lng[i + 1]; path.push(src); poly.setPath(path); service.route({ origin: src, destination: des, travelMode: google.maps.DirectionsTravelMode.DRIVING }, function (result, status) { if (status == google.maps.DirectionsStatus.OK) { for (var j = 0, len = result.routes[0].overview_path.length; j < len; j++) { path.push(result.routes[0].overview_path[j]); } } }); } } }; $(function() { $("#divclick").click(function() { html2canvas(document.querySelector("#totalimage"), { useCORS: true, allowTaint: true, scale: 2 }).then(function(canvas) { try { const dataURL = canvas.toDataURL("image/png"); alert(dataURL); $('#img_val').val(dataURL); $("#show_img").append(canvas); } catch (e) { console.error("导出Canvas失败:", e); } }).catch(function(err) { console.error("html2canvas渲染失败:", err); }); }); }); </script>
备选方案
如果上面的方法还是不行,你可以试试Google Maps静态地图API——直接生成带路线的图片URL,不存在跨域污染问题,缺点是没法交互,但对于导出图片的需求来说,这也是个非常可靠的选择。
内容的提问来源于stack exchange,提问作者Anuradha

