找回密码
 会员注册
查看: 14|回复: 0

html2canvas百度地图及覆盖物截图方案

[复制链接]

2万

主题

0

回帖

6万

积分

超级版主

积分
65944
发表于 2024-10-9 15:35:07 | 显示全部楼层 |阅读模式
html2canvas百度地图及覆盖物截图方案 html2canvas百度地图及覆盖物截图方案 曾茂芸 贝壳产品技术 贝壳产品技术 “贝壳产品技术公众号”作为贝壳官方产品技术号,致力打造贝壳产品、技术干货分享平台,面向互联网/O2O开发/产品从业者,每周推送优质产品技术文章、技术沙龙活动及招聘信息等。欢迎大家关注我们。 242篇内容 2021年12月17日 18:14 1.内容看点本文讲的是如何使用html2canvas 进行百度地图+覆盖物截图。在阅读的过程中,可以接收到如下内容:百度地图v3.0使用html2canvas截图的完整流程百度地图v3.0版本和GL v1.0版本截图方式对比注意:本文提到的百度地图v3.0、v3.0都是指JavaScript API v3.0,百度地图GL v1.0、GL v1.0都是指JavaScript API GL v1.0。2.背景介绍在现有的产品模型中,我们将城市按业务规则划分成多个区域,每个区域都是地图上的一个多边形覆盖物。比如下图示例场景(地图API v3.0版本实现),在北京市划分出三个区域:A、B、C。现业务提出需求:得到城市的区域划分概览图,比如北京市+区域A/B/C的地图截图。针对上述需求,接下来我们使用html2canvas截图工具进行实践。3.地图截图实现3.1 html2canvas简介html2canvas可以实现在浏览器中对网页或元素进行“截图”。有两种模式,foreignObject和canvas。由于百度地图v3.0的地图是DOM节点渲染地图瓦片(图片)呈现,GL v1.0地图是canvas渲染呈现,因此选择使用更为通用的canvas模式。APIhtml2canvas(element,options).then(function(canvas){//TODO});使用DEMO1)安装npminstallhtml2canvas2)HTML Helloworld! 3)JavaScriptimporthtml2canvasfrom'html2canvas';html2canvas(document.querySelector("#capture")).then(canvas=>{//返回Promise,得到截图内容的canvas对象document.body.appendChild(canvas)});canvas对象调用html2canvas函数后返回Promise实例,然后得到canvas对象,通过调用canvas对象的toDataURL 方法可以得到包含图片展示的data URI,我们可以将这个data URI:放到img标签的src属性中,让其显示在网页中放到a标签的href属性中,设置download属性,将图片下载到本地html2canvas的使用简单介绍到这里,具体使用到的属性在后续文章使用中会有补充,更多的信息可以到html2anvas官网查看。3.2 地图和覆盖物截取3.2.1 地图模块代码实现下面的代码使用百度地图JavaScript API v3.0 实现。若是要用 JavaScript API GL v1.0,代码实现和v 3.0基本一致,只是需要改变百度地图的引入文件,同时将BMap变成BMapGL。1、HTML2、地图渲染代码/**@function主程序*///初始化地图constkemap=newwindow.BMap.Map('MAP_SHOT_ID')//设置中心点kemap.centerAndZoom(newwindow.BMap.Point(116.4777778293003,40.26420238319829),9)//绘制北京的城市边界addCityOverlay('北京市')//绘制多边形覆盖物drawPolygon()/**@function绘制城市边界,城市边界也是一个多边形覆盖物*/constaddCityOverlay=(cityName,map)=>{newwindow.BMap.Boundary().get(cityName,(data)=>{constboundaries=data.boundariesif(!boundaries.length){return}boundaries.forEach(item=>{if(!item){return}//创建城市边界Point集合const_points=item.split(';').map((poi)=>(newwindow.BMap.Point(poi.split(',')[0],poi.split(',')[1])))//创建多边形const_polygon=newwindow.BMap.Polygon(_points,{strokeColor:'#0984F9',strokeWeight:3,strokeOpacity:0.8,fillOpacity:0,fillColor:''})//绘制城市边界map.addOverlay(_polygon)})})}/**@function绘制覆盖物@mapData[["116.13088682610888,40.13210416329521","116.50803141141803,40.12504285535484","116.50803141141803,39.80297163977847","116.13548615031998,39.81361374229076","116.12714987518737,39.81389085819163"],["116.12305744205753,40.53549608262856","116.50480135157775,40.53900525150335","116.51263073562912,40.25908124043422","116.13685541469079,40.26121043493562","116.13059936834568,40.26172404753967","116.13088682610888,40.266128494821274"],["116.64601113775065,40.54390289932435","116.83458343040522,40.54741162438473","117.0185563988487,40.54741162438473","117.02775504727087,40.26965184453788","116.64601113775065,40.266128494821274"]]*/constdrawPolygon=(map)=>{mapData.forEach(item=>{constpolygon=newwindow.BMap.Polygon(item.map(i=>(newwindow.BMap.Point(i.split(',')[0],i.split(',')[1]))),{strokeColor:'#0984F9',fillColor:'#0984F9',fillOpacity:0.2,strokeWeight:2})map.addOverlay(polygon)});}上面代码实现的就是背景介绍中的示例:北京市划分出三个区域。接下来我们开始截图。3.2.2 百度地图JavaScript API v3.0 截图1、代码实现设置截图宽高都为500,与地图大小一致,将得到的结果直接渲染到页面上(设置成横排):html2canvas(document.querySelector('#MAP_SHOT_ID'),{width:500,height:500,}).then((canvas)=>{document.body.append(canvas)})在chrome上执行上面的代码之后我们看到页面上现在呈现的内容如下(右图为截图):2、问题分析和解决可以看到生成的截图存在两个问题:1)没有地图底图,没有左下角的logo2)没有覆盖物:地图边界+区域问题一:没有地图底图,没有左下角的logo通过控制台Elements,我们可以看到v3.0版本的地图是使用多张地图瓦片组合而成,也就是说截图没有地图底图是因为图片渲染失败,经过分析渲染失败的原因图片跨域。解决方案html2canvas解决图片跨域问题有两种方式:1)配置项useCORS:true是否尝试使用CORS加载图像执行代码得到下图:我们可以看到地图的logo并没有成功截图。打开控制台可以看到copyright_logo.png不支持CORS跨域。在图片的response header中我们也可以看到,地图瓦片支持CORS请求,但是logo等图片不支持,所以无法成功截取。地图瓦片请求:地图logo请求:2)配置项allowTaint:true是否允许图像污染画布执行代码得到下图:可以看到地图瓦片和百度logo都完整的截取出来了,但是配置allowTaint:true之后,canvas会被污染,将不能使用toDataURL导出data URI:综合上述分析,因为我们的使用场景需要将canvas转化为图片下载,所以选择使用CORS模式,同时也能满足本次需求。问题二:没有覆盖物(地图边界+区域)地图底图的问题成功解决了,接下来我们要解决的就是没有覆盖物的问题。再次提醒,这里使用的是百度地图 JavaScript API v3.0。首先我们通过控制台可以看到,地图上的多边形覆盖物(polygon)在DOM中是以SVG的形式展示的(其他的覆盖物可能是非SVG方式)。通过html2canvas的资料阅读和源码分析得知,html2canvas支持SVG。那我们跟踪代码看看是什么原因导致截图没有覆盖物。1)源码分析首先找到源码中渲染内容的代码:1.1)文件位置:src/render/canvas/canvas-renderer.ts1.2)函数名称是:renderNodeContent1.3)npm安装的html2canvas,打包编译后的文件位置:node_modules/html2canvas/html2canvas.js下面截取了部分代码实现,如果元素是SVG,会调用renderReplacedElement方法将内容绘制到canvas上。要找到这个源码也很简单,html2canvas的原理是逐层拆解DOM元素绘制到canvas上,所以全局搜索drawImage就可以定位到这个地方。2)实践分析下面我们进行实践分析(注意如果是在自己的项目中就改动node_modules下打包后的文件,如果是在html2canvas源码中实践就直接改动源文件)。在renderReplacedElement方法的 if 判断内部的最后增加两行代码://源码renderReplacedElement(container:ReplacedElementContainer,curves:BoundCurves,image:HTMLImageElement|HTMLCanvasElement){if(image&container.intrinsicWidth>0&container.intrinsicHeight>0){  constbox=contentBox(container);  constpath=calculatePaddingBoxPath(curves);  this.path(path);  this.ctx.save();  this.ctx.clip(); this.ctx.drawImage(   image,   0,   0,   container.intrinsicWidth,   container.intrinsicHeight,   box.left,   box.top,   box.width,   box.height);  this.ctx.restore();  //ADD-打印渲染元素的宽高等box信息  console.log('renderReplacedElement',  container.intrinsicWidth,container.intrinsicHeight,box)  //ADD-将要渲染的图片绘制在网页上  document.body.append(image) }}执行代码之后我们得到如下信息:从上面的图片我们可以看到覆盖物SVG宽高都是1500px,box.left是-500, box.top是-480,canvas的drawImage方法第六、七个参数解释如下:也就是说在绘制覆盖物SVG的时候,在画布上绘制的起点坐标是( -500,-480 ),而我们设置的需要截取的画布尺寸是500,这显然偏离可视区域,也是覆盖物SVG无法正确截取的原因。至于为什么覆盖物SVG元素的宽高会是1500px,以及存在偏移,这块是百度地图内部实现,暂不详细探究。3)解决方法html2canvas API暴露的一个方法属性onclone,可以修改进行渲染的DOM节点,同时不会影响原网页内容。那么给html2canvas调用增加如下配置项来处理:将SVG元素的left和top设置为0,让渲染起点变为(0,0)(假设当前页面只有这个SVG,若实际网页中存在其他SVG元素,需要区分处理)。onclone处理代码:onclonetarget)=>{constsvgElems=Array.from(target.getElementsByTagName('svg'))for(letsvgofsvgElems){svg.style.left=0svg.style.top=0}returntarget}最后得到结果如下,右侧是是截图内容,成功的将多边形覆盖物截取出来了。[手动撒花]3、总结一下百度地图v3.0 API实现截图的整体代码如下(实际的参数和结果处理按照实际情况自己调整):html2canvas(document.querySelector('#MAP_SHOT_ID'),{useCORS:true,//允许跨域width:500,height:500,onclonetarget)=>{//处理svgconstsvgElems=Array.from(target.getElementsByTagName('svg'))for(letsvgofsvgElems){svg.style.left=0svg.style.top=0}returntarget}}).then((canvas)=>{document.body.append(canvas)//TODO下载图片-见后面下载图片})3.2.3 百度地图JavaScript API GL v1.0截图GL v1.0的地图代码实现和v3.0版本的实现基本一致。但是如果要使用截图功能,需要在初始化地图时配置preserveDrawingBuffer:true,否则截图得到的是黑屏。初始化代码如下所示://初始化地图constkemap=newwindow.BMapGL.Map('MAP_SHOT_ID',{preserveDrawingBuffer:true})初始化之后渲染出来的地图如下:我们可以明显看出和v3.0版本地图的差异:地图上的标注点层级高于多边形覆盖物,比如北京红色五角星,怀柔区。从控制台查看元素,我们可以看到地图整个包括底图和覆盖物都在一个canvas中,因此在GL v1.0版本中使用html2canvas截图不需要额外设置图片跨域。1、GL v1.0 截图方式GL v1.0版本中截图除了可以使用html2canvas之外,地图实例自身也提供了截图功能,下面对两种方式进行分析对比。1)地图实例方法getMapScreenshot()从GL v1.0的类参考中我们可以看到,地图实例本身具备截图功能,得到的是base64数据:1.1)代码实现下面使用地图实例方法getMapScreenshot()截图并且把内容作为图片渲染到页面。注意代码执行时机,要在覆盖物渲染完成之后执行。constdataURI=kemap.getMapScreenshot()constimg=document.createElement('img')img.src=dataURIdocument.body.append(img)1.2)代码执行和分析执行得到如下所示结果,右图为调用getMapScreenshot()方法得到的图片,截图完整的包含地图和覆盖物:分析上面的图片,右侧的图片为截图结果,但是我们发现地图尺寸500 * 500,截图的尺寸是1000 * 1000,这是因为为了保证canvas绘制图片的清晰度,canvas在绘制过程中会将尺寸放大window.devicePixelRatio倍再使用scale缩小,而当前window.devicePixelRatio为2。2)使用html2canvas2.1)代码实现html2canvas(document.querySelector('#MAP_SHOT_ID'),{width:500,height:500,}).then((canvas)=>{document.body.append(canvas)})执行之后我们看到页面现在呈现的内容如下:在本次场景中GL v1.0版本和v3.0版本的实现对比除了地图标注文字比覆盖物的层级高基本没有差别。不过需要注意的是,GL v1.0版本在实现上和具备的功能上与v3.0版本有差异,比如3D效果等,那么在实际应用中需要按照实际的需求来进行选择。4. 下载截图通过上面的方法我们得到的包含截图信息的数据有下面两种:1、canvas实例2、base64若是canvas实例,我们需要调用toDataURL方法得到包含图片展示的 data URI,之后操作同base64数据。4.1 canvas实例产出dataURI//图片格式可以自己设置constdataURI=canvas.toDataURL("image/jpeg",1)4.2 下载图片图片内容的变量都定义为dataURI,下载图片的操作如下:创建下载链接,执行下载。//创建下载链接constlink=document.createElement("a")link.style.display="none"//dataURI是GLv1.0getMapScreenshot()得到的base64或者canvas实例toDataURL得到的dataURIlink.href=dataURIlink.download='区域地图.jpg'document.body.appendChild(link)link.click()document.body.removeChild(link)最后百度地图的两个版本的API的三种截图方式下载的图片如下:5. 总结思考1、百度地图v3.0 API 使用html2canvas对地图和覆盖物进行截图注意点1)图片跨域问题:配置useCORS:true或者allowTaint:true解决图片跨域问题。若配置useCORS:true,截图内容可以调用canvas的toDataURL方法,结合下载链接下载截图到本地,并且截取元素中包含的图片只有本身允许CORS访问才可以成功截取。若配置allowTaint:true,所有图片均可绘制到画布上,但是画布被污染,无法使用toDataURL方法,从而无法下载截图到本地。2)多边形覆盖物在百度地图中是以SVG的形式呈现,因为百度地图多边形覆盖物SVG本身实现的原因,导致html2canvas在截图时无法正确截取,设置onclone方法,将SVG的top和left设置为0。2、百度地图GL v1.0 API可以使用两种方式进行截图1)地图自带截图方法:getMapScreenshot()2)使用html2canvas,因为GL v1.0的地图使用是canvas实现,因此不需要像v3.0版本一样特殊配置3、场景&方案选择因为本次的场景比较简单,是百度地图+多边形覆盖物。实际业务中地图上可能会有点、文本、图标、自定义信息等覆盖物,因此在实际开发中选取截图方式的时候,需要按照实际情况和需求进行对比选择最优。本文内容就到这里,希望能给到你一些收获。参考文档:1、百度地图JavaScript API v3.0(https://lbsyun.baidu.com/index.phptitle=jspopular3.0)2、百度地图JavaScript API GL v1.0(https://lbsyun.baidu.com/index.phptitle=jspopularGL)3、html2canvas:(https://html2canvas.hertzen.com) 预览时标签不可点 大前端69FE33大前端 · 目录#大前端上一篇贝壳鸿蒙开发实践下一篇Native和Flutter混合开发ViewPager的解决方案关闭更多小程序广告搜索「undefined」网络结果
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-31 04:04 , Processed in 0.632993 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表