• 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;  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>
    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                      "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                      "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

  • 相关阅读:
    Jwt访问api提示401错误 Authorization has been denied for this request
    git commit的规范
    postman中如何使用OAuth
    在outlook中查找Skype的聊天记录
    nuget sources
    NuGet version
    Forcing restore from package sources
    同时打印多个worksheets
    Redis使用认证密码登录
    Linux wait函数详解
  • 原文地址:https://www.cnblogs.com/deliciousExtra/p/5565787.html
Copyright © 2020-2023  润新知