自定义事件及应用

通过API文档,我们可以看到OpenLayers 3的相关类,都有一些事件,但这些事件大多是和现有引擎相关的,并不能满足我们大多数的业务需求。如果能为OpenLayers 3的类增加自定义事件,那么必然能更好地实现业务需求。接下来就尝试为ol.Feature添加一个mouseover的事件,通过这个事件,就可以实现在鼠标移到Feature上时,改变它的样式。

要添加自定义事件,需要知道这样一个事实:ol.Feature继承于ol.Object,而ol.Object具有派发事件(dispatchEvent)和监听事件(on)的功能。 关于这两个功能的详细信息可以参见API文档。这样,我们要自定义事件就非常容易了,如果注意观察,会发现OpenLayers 3中的类都继承于ol.Object,也就是说,如果自定义事件方法在ol.Feature上有效,那么在其他的OpenLayers 3的类上也是同样有效的。

下面就先展示一下通过自定义的mouseover事件来改变Feature的样式:

为了对比,在地图上添加了两个圆,一个黄色的,一个红色的。鼠标移动到红色的圆上,它会变成蓝色的圆。但是鼠标移到黄色的圆上,不会有任何改变。这个功能在官网的例子中也有,参见select-features。那么用自定义事件的方式来做,又该怎样来实现呢?对应的源码如下:

<!Doctype html>
<html xmlns=http://www.w3.org/1999/xhtml>
<head>                  
    <meta http-equiv=Content-Type content="text/html;charset=utf-8">
    <meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1">
    <meta content=always name=referrer>
    <title>OpenLayers 3地图示例</title>
    <link href="../src/ol3.13.1/ol.css" rel="stylesheet" type="text/css" />
    <script type="text/javascript" src="../src/ol3.13.1/ol.js" charset="utf-8"></script>
</head>

<body>
    <div id="map" style="width: 100%, height: 400px"></div>
    <script>

        // 在原点处创建一个feature
        var feature1 = new ol.Feature({
            geometry: new ol.geom.Point([0, 0])
        });

        // 并设置为半径为100像素的圆,用红色填充
        feature1.setStyle(new ol.style.Style({
            image: new ol.style.Circle({
                radius: 100,
                fill: new ol.style.Fill({
                    color: 'red'
                })
            })
        }));

        // 在坐标[5000000, 5000000]处创建另一个feature
        var feature2 = new ol.Feature({
            geometry: new ol.geom.Point([5000000, 5000000])
        });

        // 并设置为半径为100像素的圆,用黄色填充
        feature2.setStyle(new ol.style.Style({
            image: new ol.style.Circle({
                radius: 100,
                fill: new ol.style.Fill({
                    color: 'yellow'
                })
            })
        }));

      // 创建地图
      var map = new ol.Map({
            // 设置地图图层
            layers: [
              // 创建一个使用Open Street Map地图源的瓦片图层
              new ol.layer.Tile({source: new ol.source.OSM()}),
              // 把之前创建的feature1和feature2放在另一个层里
              new ol.layer.Vector({source: new ol.source.Vector({
                  features: [feature1, feature2]
              })})
            ],
            // 设置显示地图的视图
            view: new ol.View({
              center: [0, 0],    // 定义地图显示中心于经度0度,纬度0度处
              zoom: 2            // 并且定义地图显示层级为2
            }),
            // 让id为map的div作为地图的容器
            target: 'map'    
        });

      // 为地图注册鼠标移动事件的监听
      map.on('pointermove', function(event){
          map.forEachFeatureAtPixel(event.pixel, function(feature){
              // 为移动到的feature发送自定义的mousemove消息
              feature.dispatchEvent({type: 'mousemove', event: event});
          });
      });

      // 为feature1注册自定义事件mousemove的监听
      feature1.on('mousemove', function(event){
          // 修改feature的样式为半径100像素的园,用蓝色填充
          this.setStyle(new ol.style.Style({
                image: new ol.style.Circle({
                    radius: 100,
                    fill: new ol.style.Fill({
                        color: 'blue'
                    })
                })
            }));
      });
    </script>
</body>

</html>

代码中有详细的注释,再辅以API文档,相信应该看懂绝大部分的代码,当然最关键的代码在于最后的两个事件监听,在这两段代码中,可能下面这句不是很明白:

feature.dispatchEvent({type: 'mousemove', event: event});

dispatchEvent的参数具有typeevent属性,必须这样构造吗?在回答这个问题之前,需要先看一下API文档,发现参数类型为goog.events.EventLike,说明它其实用的是google的closure库来实现的,通过closure库的源码我们知道,派发的事件如果是一个对象,那么必须包含type属性,用于表示事件类型。其他的属性可以自由定义,比如此处定义了event属性,并设置对应的值,为的是让鼠标事件传递给feature1的监听函数。

dispatchEvent的参数会被原封不动的传递给事件响应函数,对应代码

feature1.on('mousemove', function(event){

function的参数event,可以通过调试窗口看到此处的eventdispatchEvent的参数是一样的。注意事件名称是可以自定义的,只要派发和监听使用的事件名称是一致的就可以。

除了可以通过dispatchEvent({type: 'mousemove', event: event})这种形式派发一个事件之外,还可以通过dispatchEvent('mousemove')这中形式直接发送mousemove事件。有兴趣的同学可以自行验证。

到此,我们就完成了自定义事件的三件事:定义事件类型,派发事件,监听事件。

使用自定义事件会带来下面几个好处:

  • 灵活控制,对比一下官网例子和这个例子,就能看出一二。
  • 能更好地满足业务需要,适当地进行扩展。
  • 采用更统一的事件处理框架。

有很多同学为了处理多个Feature的鼠标事件,可能会为Map注册很多次pointermove事件监听,从而会导致性能降低,这种做法是不可取的。如果采用上面这种自定义事件的处理方式,就只用注册一次,让真正有需要的Feature通过注册事件响应来处理业务,从而还能简化处理逻辑。

上面这个例子鼠标移到红色圆后变蓝色,但是移出圆后,还是蓝色,能不能再移出圆的时候再变成红色呢? 试试用自定义事件的方式再改进一下,实现mouseinmouseout两种事件。