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

Posted

tags:

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

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

1. 开篇

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

技术分享

 

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的官方描述.

技术分享

我们可以重写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是放在哪的。

技术分享

     利用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                         showAttributio

以上是关于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 实现地图联动