在最近的业务中,需要对 OpenLayers 地图上的 feature 要素点击后触发某些操作,任务完成过程中走过了一些坑,总结一下。
前置代码
放置一个实例更容易看明白过程,从官网的实例Hit Tolerance中截取了一段代码。
首先准备了一个 OSM 的地图底图,再写一个线段 feature 用于触发事件,最后在最上方放置一个 id
为 status
的 span 用于展示点击结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.2.1/css/ol.css" type="text/css" /> <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.2.1/build/ol.js"></script> <title>feature点击事件</title> <style> html, body, .map { height: 100%; width: 100%; } </style> </head>
<body> <span id="status">没有要素被点中</span>
<div id="map" class="map"></div>
<script> let statusElement = document.getElementById('status');
let raster = new ol.layer.Tile({ source: new ol.source.OSM() });
let style = new ol.style.Style({ stroke: new ol.style.Stroke({ color: 'black', width: 1 }) });
let feature = new ol.Feature( new ol.geom.LineString([ [-4000000, 0], [4000000, 0] ]) );
let vector = new ol.layer.Vector({ source: new ol.source.Vector({ features: [feature] }), style: style });
let map = new ol.Map({ layers: [raster, vector], target: 'map', view: new ol.View({ center: [0, 0], zoom: 2 }) }); </script> </body> </html>
|
on(‘click’)
按照一个前端的想法,最直接的触发一个点击事件的方式就是给 feature 添加一个 on('click')
事件,查询一下 ol.feature
的 API:
的确有一个 on
方法,也是用来监听事件的,但是试验了一下,on("click")
是不生效的,往上翻 API,发现还有几行:
只有 Fires 中的的事件类型才可以生效,吃了英语差的亏,浪费了时间,猜测原因应该是 OpenLayers 生成的时候直接整体在一个 Canvas 中渲染,内部 layers 相互重叠,并不能判断出用户点击的是哪一层。所以 on("click")
不行,还得看其他办法。
ol.interaction.Select
第二个直观的想法是 ol 中自带有交互,是否可以从 ol.interaction
挑一种交互类型来实现触发点击。浏览后,发现select
交互的 API 如下:
在 toggleCondition
中可设置一个回调函数,函数中添加要做的事情,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| let map = new ol.Map({ interactions: ol.interaction.defaults().extend([ new ol.interaction.Select({ hitTolerance: 20, toggleCondition: () => { statusElement.innerHTML = ' 一个要素被选中了!'; return false; } }) ]), layers: [raster, vector], target: 'map', view: new ol.View({ center: [0, 0], zoom: 2 }) });
|
代码中,触发 select
交互时,把 span
标签中的内容修改为了“一个要素被选中了!”。
forEachFeatureAtPixel
虽然使用 interaction
可以实现目的,但是 ol.interaction
会自动新添加一个交互托管层,并且带有默认的交互样式,仅仅为了触发一个点击事件就去使用 ol.interaction
实在是太重了。
在 ol.map
的 API 中有一个 forEachFeatureAtPixel(pixel, callback, opt_options)
的方法:
在这个方法中,传入一个像素位置
,返回给该像素位置对应的 feature 要素,并且还可以区分 layer,弥补了不能直接给 feature 添加点击事件的缺陷。同时配合 map.on("singleclick")
,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| map.on('singleclick', e => { style.getStroke().setColor('black'); statusElement.innerHTML = ' 没有要素被选中.'; map.forEachFeatureAtPixel( e.pixel, () => { style.getStroke().setColor('green'); statusElement.innerHTML = ' 一个要素被选中了!'; }, { hitTolerance: 20 } ); feature.changed(); });
|
代码中首先使用 map.on("singleclick")
触发一个 map
的点击事件,回调参数为 e
,再使用 forEachFeatureAtPixel
配合 e.pixel
读取该像素下的要素,如果有就把颜色改为绿色,并把内容改为“一个要素被选中了!”,再使用 changed()
方法派发一个 change
事件,触发修改后内容的渲染,最后结果如下: