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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ArcGIS JS 学习笔记3 实现百度风格的BubblePopup相关的知识,希望对你有一定的参考价值。

1. 开篇

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

技术分享

 

2.准备

2.1 Copy模板

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

HTML模板

技术分享
  1<divclass="dextra-bubble-pop-center"style="z-index: 3; position: relative; height: 50px; width: 160px;">  2<divclass="dextra-bubble-pop-content"  3style="display: block; width: 160px; height: 50px; overflow-x: auto; overflow-y: hidden;">  4<divid="poi_info_window"class="dextra-poi-info-window">  5<divclass="left name-wrap"><spanclass="name"></span></div>  6</div>  7</div>  8</div>  9<divclass="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 {
  3position: absolute;
  4z-index: 100;
  5box-sizing: border-box;
  6box-shadow: 1px 2px 1px rgba(0, 0, 0, .15);
  7background-color: #FFF;
  8 }
  9 10 .dextra-poi-info-window {
 11padding: 4px 0;
 12 }
 13 14 .dextra-poi-info-window .left {
 15padding-left: 10px;
 16padding-right: 10px;
 17height: 40px;
 18line-height: 40px;
 19display: table;
 20table-layout: fixed;
 21width: 140px;
 22text-align: center;
 23 }
 24 25 .dextra-poi-info-window .name-wrap .name {
 26vertical-align: middle;
 27font-size: 14px;
 28font-weight: 700;
 29white-space: nowrap;
 30overflow: hidden;
 31text-overflow: ellipsis;
 32display: block;
 33 }
 34 35 .dextra-bubble-pop-bottomspan {
 36position: absolute;
 37left:72px;
 38width: 16px;
 39height: 10px;
 40background-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 ],
 18function(
 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 ) {
 34return declare([InfoWindowBase, Evented], {
 35 36         isContentShowing :false,
 37 38constructor: function(parameters) {
 39 40 41           lang.mixin(this, parameters);
 42 43 44           domClass.add(this.domNode, "myInfoWindow");
 45 46this._closeButton = domConstruct.create("div",{"class": "close", "title": "Close"}, this.domNode);
 47this._title = domConstruct.create("div",{"class": "title"}, this.domNode);
 48this._content = domConstruct.create("div",{"class": "content"}, this.domNode);
 49 50this._toggleButton = domConstruct.create("div",{"class": "toggleOpen", "title": "Toggle"}, this.domNode);
 51 52var 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. 61this.hide();
 62if(this.isContentShowing){
 63               toggler.hide();
 64this.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  71if(this.isContentShowing){
 72 73                 toggler.hide();
 74this.isContentShowing = false;
 75                 domClass.remove(this._toggleButton);
 76                 domClass.add(this._toggleButton,"toggleOpen");
 77 78               }else{
 79                 toggler.show();
 80this.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);
 88this.isShowing = false;
 89 90         },
 91         setMap: function(map){
 92this.inherited(arguments);
 93           map.on("pan-start", lang.hitch(this, function(){
 94this.hide();
 95           }));
 96           map.on("zoom-start", lang.hitch(this, function(){
 97this.hide();
 98           }));
 99// map.on("zoom-start", //this, this.hide);100101         },
102         setTitle: function(title){
103this.place(title, this._title);
104105         },
106         setContent: function(content){
107this.place(content, this._content);
108         },
109         show: function(location){
110if(location.spatialReference){
111location = this.map.toScreen(location);
112           }
113114//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           });
119120//display the info window121           domUtils.show(this.domNode);
122this.isShowing = true;
123this.onShow();
124         },
125         hide: function(){
126           domUtils.hide(this.domNode);
127this.isShowing = false;
128this.onHide();
129130         },
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           });
139140         },
141         destroy: function(){
142           domConstruct.destroy(this.domNode);
143this._closeButton = this._title = this._content = null;
144145         }
146147148       });
149150 });
View Code

我们就在此基础上进行改造,不但要实现需求还要解决缺陷。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     ],
 20function (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) {
 33var showMapPoint = null;
 34return declare([InfoWindowBase, Evented, _TemplatedMixin], {
 35             isContentShowing: false,
 36             templateString: template,
 37             _events: [],
 38constructor: 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) {
  2this.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  8this.domNode.innerHTML = this.templateString;
  9 10this._content = query("div.name-wrap span.name");
 11this._title=query("div.name-wrap");
 12//hide initial display 13                 domUtils.hide(this.domNode);
 14this.isShowing = false;
 15             },
View Code

     注意,我在这里创建了一个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) {
  2if (showMapPoint == null)return;
  3var 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);
 10this.isShowing = true;
 11this.onShow();
 12             },
 13 14             show: function (location) {
 15                 showMapPoint = location;
 16if (webMercatorUtils.canProject(location, this.map)) {
 17                     showMapPoint = webMercatorUtils.project(location, this.map);
 18                 }
 19if (showMapPoint.spatialReference) {
 20var 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);
 29this.isShowing = true;
 30this.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     ],
 20function (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) {
 33var showMapPoint = null;
 34return declare([InfoWindowBase, Evented, _TemplatedMixin], {
 35 36             templateString: template,
 37             _events: [],
 38constructor: function (parameters) {
 39                 lang.mixin(this, parameters);
 40             },
 41             _createInfoWindowInstance: function (map) {
 42this.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 48this.domNode.innerHTML = this.templateString;
 49 50this._content = query("div.name-wrap span.name");
 51this._title=query("div.name-wrap");
 52//hide initial display 53                 domUtils.hide(this.domNode);
 54this.isShowing = false;
 55             },
 56 57             setMap: function (map) {
 58this.inherited(arguments);
 59this._events = [];
 60this._createInfoWindowInstance(map);
 61this._events.push(map.on("pan", lang.hitch(this, function (evt) {
 62if (this.isShowing) {
 63this._showInfoWindow(evt.extent);
 64                     }
 65                 })));
 66 67this._events.push(map.on("zoom-start", lang.hitch(this, function (evt) {
 68this.hide();
 69                 })));
 70 71this._events.push(map.on("zoom-end", lang.hitch(this, function (evt) {
 72this._showInfoWindow(evt.extent);
 73                 })));
 74             },
 75 76             unsetMap: function (map) {
 77this.inherited(arguments);
 78                 array.forEach(this._events, function (event) {
 79                     event.remove();
 80                 });
 81             },
 82             setTitle: function (title) {
 83this._title.forEach(function (node) {
 84                     domAttr.set(node, "title", title);
 85                 });
 86             },
 87 88             setContent: function (content) {
 89this._content.forEach(function (node) {
 90                     node.innerHTML = content;
 91                 });
 92             },
 93 94             _showInfoWindow: function (extent) {
 95if (showMapPoint == null)return;
 96var 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                 });
101102                 domUtils.show(this.domNode);
103this.isShowing = true;
104this.onShow();
105             },
106107             show: function (location) {
108                 showMapPoint = location;
109if (webMercatorUtils.canProject(location, this.map)) {
110                     showMapPoint = webMercatorUtils.project(location, this.map);
111                 }
112if (showMapPoint.spatialReference) {
113var screenPoint = this.map.toScreen(showMapPoint);
114                     domStyle.set(this.domNode, {
115                         "left": (screenPoint.x - 80) + "px",
116                         "top": (screenPoint.y - 76) + "px"
117                     });
118                 }
119120//display the info window121                 domUtils.show(this.domNode);
122this.isShowing = true;
123this.onShow();
124             },
125             hide: function () {
126if (this.isShowing) {
127                     domUtils.hide(this.domNode);
128this.isShowing = false;
129this.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 () {
139this.hide();
140                 showMapPoint = null;
141             },
142             destroy: function () {
143                 domConstruct.destroy(this.domNode);
144             }
145         });
146     });
147
View Code

DEMO:

技术分享
  1<!DOCTYPE html>  2<htmllang="en">  3<head>  4<metacharset="UTF-8">  5<title>DExtra-BubublPoopup</title>  6<linkrel="stylesheet"href="https://js.arcgis.com/3.16/esri/css/esri.css">  7<linkrel="stylesheet"href="../dist/dijit/css/dextraPopup.css">  8<linkrel="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<scriptsrc="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<divid="measureTools"> 92<buttonid="infowindow">弹出框</button> 93</div> 94 95<divid="map"></div> 96</body> 97</html>
View Code
 

效果截图:

技术分享

以上是关于ArcGIS JS 学习笔记3 实现百度风格的BubblePopup的主要内容,如果未能解决你的问题,请参考以下文章

ArcGIS JS 学习笔记1 用ArcGIS JS 实现仿百度地图的距离量测和面积量测

ArcGIS JS 学习笔记1 用ArcGIS JS 实现仿百度地图的距离量测和面积量测

ArcGIS JS 学习笔记2 实现仿百度的拖拽画圆

ArcGIS JS 学习笔记2 实现仿百度的拖拽画圆

ArcGIS JS 学习笔记2 实现仿百度的拖拽画圆

ArcGIS JS 学习笔记4 实现地图联动