1. 开篇
模仿是最好的学习,这次我们继续山寨百度,通过自定义Infowindow来实现百度风格的BubblePopup
2.准备
2.1 Copy模板
先打开百度地图,按下f12吧BubblePopup的HTML代码和CSS代码拷贝下来,这里我无耻的把类名改了,大家不要在意细节。
HTML模板
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <div class="dextra-bubble-pop-center" style="z-index: 3; position: relative; height: 50px; 160px;"> 2 <div class="dextra-bubble-pop-content" 3 style="display: block; 160px; height: 50px; overflow-x: auto; overflow-y: hidden;"> 4 <div id="poi_info_window" class="dextra-poi-info-window"> 5 <div class="left name-wrap"><span class="name"></span></div> 6 </div> 7 </div> 8 </div> 9 <div class="dextra-bubble-pop-bottom" style="display: block; z-index: 2; 160px; left: 72px;"> 10 <span></span> 11 </div>
CSS代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 2 .dextra-bubble-pop { 3 position: absolute; 4 z-index: 100; 5 box-sizing: border-box; 6 box-shadow: 1px 2px 1px rgba(0, 0, 0, .15); 7 background-color: #FFF; 8 } 9 10 .dextra-poi-info-window { 11 padding: 4px 0; 12 } 13 14 .dextra-poi-info-window .left { 15 padding-left: 10px; 16 padding-right: 10px; 17 height: 40px; 18 line-height: 40px; 19 display: table; 20 table-layout: fixed; 21 width: 140px; 22 text-align: center; 23 } 24 25 .dextra-poi-info-window .name-wrap .name { 26 vertical-align: middle; 27 font-size: 14px; 28 font-weight: 700; 29 white-space: nowrap; 30 overflow: hidden; 31 text-overflow: ellipsis; 32 display: block; 33 } 34 35 .dextra-bubble-pop-bottom span { 36 position: absolute; 37 left:72px; 38 width: 16px; 39 height: 10px; 40 background-image: url("../images/tail_shadow.png"); 41 }
2.2 编写BubblePopup
要实现BubblePopup,实际上就是自定义一个InfoWindow,我们可以通过继承InfoWindowBase来实现。要实现自定义的InfoWindow。我们可以先参考一下官方的例子Custom info window,注意,这个例子是有缺陷的,如果当infowindow超出当前视图边界就会出现滚动条。下载官方的实例,我们打开infoWindow.js文件。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 define([ 2 "dojo/Evented", 3 "dojo/parser", 4 "dojo/on", 5 "dojo/_base/declare", 6 "dojo/dom-construct", 7 "dojo/_base/array", 8 "dojo/dom-style", 9 "dojo/_base/lang", 10 "dojo/dom-class", 11 "dojo/fx/Toggler", 12 "dojo/fx", 13 "dojo/Deferred", 14 "esri/domUtils", 15 "esri/InfoWindowBase" 16 17 ], 18 function( 19 Evented, 20 parser, 21 on, 22 declare, 23 domConstruct, 24 array, 25 domStyle, 26 lang, 27 domClass, 28 Toggler, 29 coreFx, 30 Deferred, 31 domUtils, 32 InfoWindowBase 33 ) { 34 return declare([InfoWindowBase, Evented], { 35 36 isContentShowing :false, 37 38 constructor: function(parameters) { 39 40 41 lang.mixin(this, parameters); 42 43 44 domClass.add(this.domNode, "myInfoWindow"); 45 46 this._closeButton = domConstruct.create("div",{"class": "close", "title": "Close"}, this.domNode); 47 this._title = domConstruct.create("div",{"class": "title"}, this.domNode); 48 this._content = domConstruct.create("div",{"class": "content"}, this.domNode); 49 50 this._toggleButton = domConstruct.create("div",{"class": "toggleOpen", "title": "Toggle"}, this.domNode); 51 52 var toggler = new Toggler({ 53 "node": this._content, 54 showFunc: coreFx.wipeIn, 55 hideFunc: coreFx.wipeOut 56 }); 57 toggler.hide(); 58 59 on(this._closeButton, "click", lang.hitch(this, function(){ 60 //hide the content when the info window is toggled close. 61 this.hide(); 62 if(this.isContentShowing){ 63 toggler.hide(); 64 this.isContentShowing = false; 65 domClass.remove(this._toggleButton); 66 domClass.add(this._toggleButton, "toggleOpen"); 67 } 68 })); 69 on(this._toggleButton, "click", lang.hitch(this, function(){ 70 //animate the content display 71 if(this.isContentShowing){ 72 73 toggler.hide(); 74 this.isContentShowing = false; 75 domClass.remove(this._toggleButton); 76 domClass.add(this._toggleButton,"toggleOpen"); 77 78 }else{ 79 toggler.show(); 80 this.isContentShowing=true; 81 domClass.remove(this._toggleButton); 82 domClass.add(this._toggleButton,"toggleClose"); 83 } 84 85 })); 86 //hide initial display 87 domUtils.hide(this.domNode); 88 this.isShowing = false; 89 90 }, 91 setMap: function(map){ 92 this.inherited(arguments); 93 map.on("pan-start", lang.hitch(this, function(){ 94 this.hide(); 95 })); 96 map.on("zoom-start", lang.hitch(this, function(){ 97 this.hide(); 98 })); 99 // map.on("zoom-start", //this, this.hide); 100 101 }, 102 setTitle: function(title){ 103 this.place(title, this._title); 104 105 }, 106 setContent: function(content){ 107 this.place(content, this._content); 108 }, 109 show: function(location){ 110 if(location.spatialReference){ 111 location = this.map.toScreen(location); 112 } 113 114 //Position 10x10 pixels away from the specified location 115 domStyle.set(this.domNode,{ 116 "left": (location.x + 10) + "px", 117 "top": (location.y + 10) + "px" 118 }); 119 120 //display the info window 121 domUtils.show(this.domNode); 122 this.isShowing = true; 123 this.onShow(); 124 }, 125 hide: function(){ 126 domUtils.hide(this.domNode); 127 this.isShowing = false; 128 this.onHide(); 129 130 }, 131 resize: function(width, height){ 132 domStyle.set(this._content,{ 133 "width": width + "px", 134 "height": height + "px" 135 }); 136 domStyle.set(this._title,{ 137 "width": width + "px" 138 }); 139 140 }, 141 destroy: function(){ 142 domConstruct.destroy(this.domNode); 143 this._closeButton = this._title = this._content = null; 144 145 } 146 147 148 }); 149 150 });
我们就在此基础上进行改造,不但要实现需求还要解决缺陷。infoWindowBase是继承自_WidgetBase的,我们先来看一下infoWindowBase的官方描述.
我们可以重写infoWindowBase的一些方法,来实现自己的infoWindow。
首先我们先引入我们要用到的模块
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 define([ 2 "dojo/Evented", 3 "dojo/on", 4 "dojo/query", 5 "dojo/_base/declare", 6 "dojo/dom-construct", 7 "dojo/dom-attr", 8 "dojo/_base/array", 9 "dojo/dom-style", 10 "dojo/_base/lang", 11 "dojo/dom-class", 12 "dijit/_TemplatedMixin", 13 "esri/domUtils", 14 "esri/InfoWindowBase", 15 "esri/geometry/ScreenPoint", 16 "esri/geometry/screenUtils", 17 "esri/geometry/webMercatorUtils", 18 "dojo/text!./templates/dextraPopup.html" 19 ], 20 function (Evented, 21 on, 22 query, 23 declare, 24 domConstruct, 25 domAttr, 26 array, 27 domStyle, 28 lang, 29 domClass, 30 _TemplatedMixin, 31 domUtils, 32 InfoWindowBase, ScreenPoint, screenUtils, webMercatorUtils, template) { 33 var showMapPoint = null; 34 return declare([InfoWindowBase, Evented, _TemplatedMixin], { 35 isContentShowing: false, 36 templateString: template, 37 _events: [], 38 constructor: function (parameters) { 39 lang.mixin(this, parameters); 40 }, 41 ... 42 });
对比官方的例子,我去掉了部分模块(coreFx,Toggler),加入了dijit/_TemplateMixin,esri/geometry/webMecratorUtils,
esri/geomtry/srcreenUtils模块。_TemplateMixin是为了使用我在第一步拷贝下来的HTML模板,关于编写基于模板的widget可以到
dojo的官网进行查看;webMecratorUtils和srcreenUtils则是为了实现地理坐标和屏幕坐标的准确转换。
showMapPoint是一个全局的变量,用来记录popup的地理坐标位置。
templateString是_TemplateMixin模块的一个属性,用来保存HTML模板。
_events:是一个数组,用来存储相关的事件,在popup被释放时释放注册的事件。
先用一个私有方法来进行初始化。应为InfoWindowBase是继承自_WidgetBase的,domNode是_WidgetBase的一个属性,用于表示生成Widget的dom节点,可以通过在构造函数里用第二个参数来进行传入,或者在内部自己定义。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 _createInfoWindowInstance: function (map) { 2 this.domNode = domConstruct.create("div", null, map.id + "_root"); 3 domClass.add(this.domNode, "dextra-bubble-pop"); 4 domStyle.set(this.domNode, { 5 "160px", 6 }); 7 8 this.domNode.innerHTML = this.templateString; 9 10 this._content = query("div.name-wrap span.name"); 11 this._title=query("div.name-wrap"); 12 //hide initial display 13 domUtils.hide(this.domNode); 14 this.isShowing = false; 15 },
注意,我在这里创建了一个div节点,并把它添加到一个id为{map.id}_root({map.id}占位符,用于表示地图的id)的dom节点中,这一步就是解决当infowindow超出当前视图范围时会出现滚动条。我们可以先用arcgis提供的infowindow来试一试,在浏览器中按
f12,我们看一看infowindow是放在哪的。
利用arcgis自带的infowindow,我们可以看到这个infowindow的dom节点被添加到一个id为map_root的div中。在这里,我的map控件的id为“map”,所以它会生成一个id为“map_root”({map.id}_root)的div。所以我们只要把自定生成的popup放到这个节点中,当popup超出当前视图时,会被裁减了,而不是出现滚动条。这里最关键的部分已经完成了,接下来的操作就是如何在地图上展现这个popup。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 _showInfoWindow: function (extent) { 2 if (showMapPoint == null)return; 3 var showScreenPoint = screenUtils.toScreenGeometry(extent, this.map.width, this.map.height, showMapPoint); 4 domStyle.set(this.domNode, { 5 "left": (showScreenPoint.x - 80) + "px", 6 "top": (showScreenPoint.y - 76 ) + "px" 7 }); 8 9 domUtils.show(this.domNode); 10 this.isShowing = true; 11 this.onShow(); 12 }, 13 14 show: function (location) { 15 showMapPoint = location; 16 if (webMercatorUtils.canProject(location, this.map)) { 17 showMapPoint = webMercatorUtils.project(location, this.map); 18 } 19 if (showMapPoint.spatialReference) { 20 var screenPoint = this.map.toScreen(showMapPoint); 21 domStyle.set(this.domNode, { 22 "left": (screenPoint.x - 80) + "px", 23 "top": (screenPoint.y - 76) + "px" 24 }); 25 } 26 27 //display the info window 28 domUtils.show(this.domNode); 29 this.isShowing = true; 30 this.onShow(); 31 },
_showInfoWindow方法是一个私有方法,用于在地图事件触发时调用。当地图平移,缩放时根据地理坐标从新计算BubblePopup的屏幕坐标。用screenUtils.toScreenGeometry(extent, width, height, mapGeometry)根据地图的范围,宽度,高度,和点计算出相应的屏幕坐标。
show方法是一个公有方法,用于在外部进行调用。在这里利用了arcgis js 提供webMercatorUtils模块,来进行坐标的转换。一般而言,我们都会用经纬度坐标,但是当地图是webMercator投影时,就需要先把经纬度坐标转化成米制坐标,才能在正确的位置显示出来来。
关键的部分已经完成,下面贴出全部代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 define([ 2 "dojo/Evented", 3 "dojo/on", 4 "dojo/query", 5 "dojo/_base/declare", 6 "dojo/dom-construct", 7 "dojo/dom-attr", 8 "dojo/_base/array", 9 "dojo/dom-style", 10 "dojo/_base/lang", 11 "dojo/dom-class", 12 "dijit/_TemplatedMixin", 13 "esri/domUtils", 14 "esri/InfoWindowBase", 15 "esri/geometry/ScreenPoint", 16 "esri/geometry/screenUtils", 17 "esri/geometry/webMercatorUtils", 18 "dojo/text!./templates/dextraPopup.html" 19 ], 20 function (Evented, 21 on, 22 query, 23 declare, 24 domConstruct, 25 domAttr, 26 array, 27 domStyle, 28 lang, 29 domClass, 30 _TemplatedMixin, 31 domUtils, 32 InfoWindowBase, ScreenPoint, screenUtils, webMercatorUtils, template) { 33 var showMapPoint = null; 34 return declare([InfoWindowBase, Evented, _TemplatedMixin], { 35 36 templateString: template, 37 _events: [], 38 constructor: function (parameters) { 39 lang.mixin(this, parameters); 40 }, 41 _createInfoWindowInstance: function (map) { 42 this.domNode = domConstruct.create("div", null, map.id + "_root"); 43 domClass.add(this.domNode, "dextra-bubble-pop"); 44 domStyle.set(this.domNode, { 45 "160px", 46 }); 47 48 this.domNode.innerHTML = this.templateString; 49 50 this._content = query("div.name-wrap span.name"); 51 this._title=query("div.name-wrap"); 52 //hide initial display 53 domUtils.hide(this.domNode); 54 this.isShowing = false; 55 }, 56 57 setMap: function (map) { 58 this.inherited(arguments); 59 this._events = []; 60 this._createInfoWindowInstance(map); 61 this._events.push(map.on("pan", lang.hitch(this, function (evt) { 62 if (this.isShowing) { 63 this._showInfoWindow(evt.extent); 64 } 65 }))); 66 67 this._events.push(map.on("zoom-start", lang.hitch(this, function (evt) { 68 this.hide(); 69 }))); 70 71 this._events.push(map.on("zoom-end", lang.hitch(this, function (evt) { 72 this._showInfoWindow(evt.extent); 73 }))); 74 }, 75 76 unsetMap: function (map) { 77 this.inherited(arguments); 78 array.forEach(this._events, function (event) { 79 event.remove(); 80 }); 81 }, 82 setTitle: function (title) { 83 this._title.forEach(function (node) { 84 domAttr.set(node, "title", title); 85 }); 86 }, 87 88 setContent: function (content) { 89 this._content.forEach(function (node) { 90 node.innerHTML = content; 91 }); 92 }, 93 94 _showInfoWindow: function (extent) { 95 if (showMapPoint == null)return; 96 var showScreenPoint = screenUtils.toScreenGeometry(extent, this.map.width, this.map.height, showMapPoint); 97 domStyle.set(this.domNode, { 98 "left": (showScreenPoint.x - 80) + "px", 99 "top": (showScreenPoint.y - 76 ) + "px" 100 }); 101 102 domUtils.show(this.domNode); 103 this.isShowing = true; 104 this.onShow(); 105 }, 106 107 show: function (location) { 108 showMapPoint = location; 109 if (webMercatorUtils.canProject(location, this.map)) { 110 showMapPoint = webMercatorUtils.project(location, this.map); 111 } 112 if (showMapPoint.spatialReference) { 113 var screenPoint = this.map.toScreen(showMapPoint); 114 domStyle.set(this.domNode, { 115 "left": (screenPoint.x - 80) + "px", 116 "top": (screenPoint.y - 76) + "px" 117 }); 118 } 119 120 //display the info window 121 domUtils.show(this.domNode); 122 this.isShowing = true; 123 this.onShow(); 124 }, 125 hide: function () { 126 if (this.isShowing) { 127 domUtils.hide(this.domNode); 128 this.isShowing = false; 129 this.onHide(); 130 } 131 }, 132 resize: function (width, height) { 133 domStyle.set(this._content, { 134 "width": width + "px", 135 "height": height + "px" 136 }); 137 }, 138 remove: function () { 139 this.hide(); 140 showMapPoint = null; 141 }, 142 destroy: function () { 143 domConstruct.destroy(this.domNode); 144 } 145 }); 146 }); 147
DEMO:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>DExtra-BubublPoopup</title> 6 <link rel="stylesheet" href="https://js.arcgis.com/3.16/esri/css/esri.css"> 7 <link rel="stylesheet" href="../dist/dijit/css/dextraPopup.css"> 8 <link rel="stylesheet" href="css/mainApp.css"> 9 <script> 10 var dojoConfig = { 11 parseOnLoad:true, 12 packages: [{ 13 name: 'custom', 14 location: location.pathname.replace(//[^/]+$/, '') + '/custom'//从cdn加载自己定义的模块方法 15 }, 16 { 17 name: 'dextra', 18 location: '/extra.arcgis.3.x/dist/'//从cdn加载自己定义的模块方法 19 }] 20 }; 21 </script> 22 <script src="https://js.arcgis.com/3.16/"></script> 23 <script> 24 require([ 25 "dojo/dom", 26 "dojo/on", 27 "esri/map", 28 "esri/symbols/SimpleMarkerSymbol", 29 "esri/InfoTemplate", 30 "esri/layers/GraphicsLayer", 31 "dextra/layers/GoogleVectorLayer", 32 "dextra/dijit/DEBubblePopup", 33 "dojo/domReady!"], 34 function (dom, on, 35 Map, Graphic, SimpleMarkerSymbol, InfoTemplate, GraphicsLayer, 36 GoogleVectorLayer,DEBubblePopup) { 37 38 var infoWindow = new DEBubblePopup(); 39 var map = new Map("map", { 40 showAttribution: false, 41 center: [102.3, 24.6], 42 autoResize: true, 43 sliderPosition: "bottom-right", 44 logo: false, 45 infoWindow:infoWindow, 46 zoom:12 47 }); 48 49 var googleVect = new GoogleVectorLayer(); 50 map.addLayer(googleVect); 51 52 var measureLayer = new GraphicsLayer({id: "infoWindowTest"}); 53 map.addLayer(measureLayer); 54 on(dom.byId("infowindow"), "click", function (e) { 55 on.once(map, "click", function (evt) { 56 console.log(map._container); 57 var sms = new SimpleMarkerSymbol({ 58 "color": [255, 0, 0], 59 "size": 12, 60 "xoffset": 0, 61 "yoffset": 0, 62 "type": "esriSMS", 63 "style": "esriSMSCircle", 64 "outline": { 65 "color": [0, 0, 0, 255], 66 "width": 1, 67 "type": "esriSLS", 68 "style": "esriSLSSolid" 69 } 70 }); 71 72 var point = map.toMap(evt.screenPoint); 73 var attr = {"Xcoord": point.x, "Ycoord": point.y, "Plant": "Mesa Mint"}; 74 var infoTemplate = new InfoTemplate("Locations", "Latitude: ${Ycoord} Longitude: ${Xcoord}Plant Name:${Plant}"); 75 var graphic=new Graphic(point, sms,attr,infoTemplate); 76 measureLayer.add(graphic); 77 }); 78 }); 79 }); 80 </script> 81 <style> 82 #measureTools { 83 position: absolute; 84 top: 50px; 85 left: 50px; 86 z-index: 1000; 87 } 88 </style> 89 </head> 90 <body> 91 <div id="measureTools"> 92 <button id="infowindow">弹出框</button> 93 </div> 94 95 <div id="map" ></div> 96 </body> 97 </html>
效果截图:
3.1 小结
可以看到,通过继承InfoWindowBase我们完全可以实现自己的的infoWindow,编写更具个性化的插件。最后像新手玩家推荐一下
esri的github,这里有很多有用的东西,非常值得学习http://esri.github.io/。
本文参考了 http://blog.csdn.net/gisshixisheng/article/details/26132921 谢谢lzugis的分享。
欢迎转载 http://www.cnblogs.com/deliciousExtra/p/5565787.html