1. 开篇
模仿是最好的学习,这次我们继续山寨百度,通过自定义Infowindow来实现百度风格的BubblePopup
2.准备
2.1 Copy模板
先打开百度地图,按下f12吧BubblePopup的HTML代码和CSS代码拷贝下来,这里我无耻的把类名改了,大家不要在意细节。
HTML模板
129 810 11
CSS代码
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。我们可以先参考一下官方的例子,注意,这个例子是有缺陷的,如果当infowindow超出当前视图边界就会出现滚动条。下载官方的实例,我们打开infoWindow.js文件。
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 location115 domStyle.set(this.domNode,{116 "left": (location.x + 10) + "px",117 "top": (location.y + 10) + "px"118 });119 120 //display the info window121 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。
首先我们先引入我们要用到的模块
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节点,可以通过在构造函数里用第二个参数来进行传入,或者在内部自己定义。
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 width: "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。
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投影时,就需要先把经纬度坐标转化成米制坐标,才能在正确的位置显示出来来。
关键的部分已经完成,下面贴出全部代码
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 width: "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 window121 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:
1 2 3 4 5DExtra-BubublPoopup 6 7 8 9 22 23 81 89 90 9192 9394 95 96 97
效果截图:
3.1 小结
可以看到,通过继承InfoWindowBase我们完全可以实现自己的的infoWindow,编写更具个性化的插件。最后像新手玩家推荐一下
esri的github,这里有很多有用的东西,非常值得学习。
本文参考了 谢谢的分享。
欢迎转载 http://www.cnblogs.com/deliciousExtra/p/5565787.html