ArcGIS JS 学习笔记3 实现百度风格的BubblePopup

1. 开篇

模仿是最好的学习,这次我们继续山寨百度,通过自定义Infowindow来实现百度风格的BubblePopup

image

 

2.准备

2.1 Copy模板

先打开百度地图,按下f12吧BubblePopup的HTML代码和CSS代码拷贝下来,这里我无耻的把类名改了,大家不要在意细节。

HTML模板

  1 <div class="dextra-bubble-pop-center" style="z-index: 3; position: relative; height: 50px; width: 160px;">
  2     <div class="dextra-bubble-pop-content"
  3          style="display: block; width: 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; width: 160px; left: 72px;">
 10     <span></span>
 11 </div>
View Code

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 }
View Code

2.2 编写BubblePopup

    要实现BubblePopup,实际上就是自定义一个InfoWindow,我们可以通过继承InfoWindowBase来实现。要实现自定义的InfoWindow。我们可以先参考一下官方的例子Custom info window注意,这个例子是有缺陷的,如果当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 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 });
View Code

我们就在此基础上进行改造,不但要实现需求还要解决缺陷。infoWindowBase是继承自_WidgetBase的,我们先来看一下infoWindowBase的官方描述.

image

我们可以重写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               });
View Code

      对比官方的例子,我去掉了部分模块(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             },
View Code

     注意,我在这里创建了一个div节点,并把它添加到一个id为{map.id}_root({map.id}占位符,用于表示地图的id)的dom节点中,这一步就是解决当infowindow超出当前视图范围时会出现滚动条。我们可以先用arcgis提供的infowindow来试一试,在浏览器中按

f12,我们看一看infowindow是放在哪的。

image

     利用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             },
View Code

      _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 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 
View Code

DEMO:

  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>
View Code
 

效果截图:

image

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

posted @ 2016-06-07 08:41  好吃的益达  阅读(4198)  评论(16编辑  收藏  举报