Leaflet框架下如何通过setTimeout实现L.Icon自动刷新?
解决七日杀Alloc地图模组玩家在线/离线图标自动切换问题
问题背景
我对Java和jQuery了解有限,但2000-2010年做过网页开发,有基础。现在用七日杀的Alloc's server fix实时地图模组(基于Leaflet框架),已经改了样式、替换过图片,还加了位置覆盖层。现在要实现:玩家登录游戏时,离线图标能自动切换为在线图标,不用手动刷新网页。目前地图的在线/离线数量、游戏时间都能自动更新,但L.Icon必须刷新才生效,怀疑和.done(setPlayerMarkers)的逻辑有关。
相关maps.js代码
// player icon var playerIcon = L.icon({ iconUrl: '/static/leaflet/images/player.png', iconRetinaUrl: '/static/leaflet/images/player.png', iconSize: [30, 30], iconAnchor: [15, 15], popupAnchor: [0, -15] }); // player offline icon var playerOfflineIcon = L.icon({ iconUrl: '/static/leaflet/images/player-offline.png', iconRetinaUrl: '/static/leaflet/images/player-offline.png', iconSize: [30, 30], iconAnchor: [15, 15], popupAnchor: [0, -15] }); // =============================================================================================== // Player markers $(" .leaflet-popup-pane").on('click.action', '.inventoryButton', function(event) { ShowInventoryDialog ($(this).data('steamid')); }); var setPlayerMarkers = function(data) { var onlineIds = []; updatingMarkers = true; $.each( data, function( key, val ) { if (val.online) { var marker; if (playersMappingList.hasOwnProperty(val.steamid)) { marker = playersMappingList[val.steamid].currentPosMarker; } else { marker = L.marker([val.position.x, val.position.z], {icon: playerIcon}).bindPopup( "Player: " + $("<div>").text(val.name).html() + (HasPermission ("webapi.getplayerinventory") ? "<br/><a class='inventoryButton' data-steamid='"+val.steamid+"'>Show inventory</a>" : "" ) ); marker.on("move", function ( e ) { if ( this.isPopupOpen () ) { map.flyTo (e.latlng, map.getZoom ()); } }); playersMappingList[val.steamid] = { online: !val.online }; } if (val.online) { onlineIds.push (val.steamid); } oldpos = marker.getLatLng (); if ( playersMappingList[val.steamid].online != val.online ) { if (playersMappingList[val.steamid].online) { playersOnlineMarkerGroup.removeLayer(marker); playersOfflineMarkerGroup.addLayer(marker); } else { playersOfflineMarkerGroup.removeLayer(marker); playersOnlineMarkerGroup.addLayer(marker); } } if ( oldpos.lat != val.position.x || oldpos.lng != val.position.z ) { marker.setLatLng([val.position.x, val.position.z]); if (val.online) { marker.setOpacity(1.0); } else { marker.setOpacity(0.5); } } val.currentPosMarker = marker; playersMappingList[val.steamid] = val; } else { var marker; if (playersMappingList.hasOwnProperty(val.steamid)) { marker = playersMappingList[val.steamid].currentPosMarker; } else { marker = L.marker([val.position.x, val.position.z], {icon: playerOfflineIcon}).bindPopup( "Player: " + $("<div>").text(val.name).html() + (HasPermission ("webapi.getplayerinventory") ? "<br/><a class='inventoryButton' data-steamid='"+val.steamid+"'>Show inventory</a>" : "" ) ); marker.on("move", function ( e ) { if ( this.isPopupOpen () ) { map.flyTo (e.latlng, map.getZoom ()); } }); playersMappingList[val.steamid] = { online: !val.online }; } if (val.online) { onlineIds.push (val.steamid); } oldpos = marker.getLatLng (); if ( playersMappingList[val.steamid].online != val.online ) { if (playersMappingList[val.steamid].online) { playersOnlineMarkerGroup.removeLayer(marker); playersOfflineMarkerGroup.addLayer(marker); } else { playersOfflineMarkerGroup.removeLayer(marker); playersOnlineMarkerGroup.addLayer(marker); } } if ( oldpos.lat != val.position.x || oldpos.lng != val.position.z ) { marker.setLatLng([val.position.x, val.position.z]); if (val.online) { marker.setOpacity(1.0); } else { marker.setOpacity(0.5); } } val.currentPosMarker = marker; playersMappingList[val.steamid] = val; } }); var online = 0; var offline = 0; $.each ( playersMappingList, function ( key, val ) { if ( val.online && onlineIds.indexOf (key) < 0 ) { var marker = val.currentPosMarker; playersOnlineMarkerGroup.removeLayer(marker); playersOfflineMarkerGroup.addLayer(marker); val.online = false; } if (val.online) { online++; } else { offline++; } }); updatingMarkers = false; $( "#mapControlOnlineCount" ).text( online ); $( "#mapControlOfflineCount" ).text( offline ); } var updatePlayerTimeout; var playerUpdateCount = -1; var updatePlayerEvent = function() { playerUpdateCount++; $.getJSON( "../api/getplayerslocation" + ((playerUpdateCount % 15) == 0 ? "?offline=true" : "")) .done(setPlayerMarkers) .fail(function(jqxhr, textStatus, error) { console.log("Error fetching players list"); }) .always(function() { updatePlayerTimeout = window.setTimeout(updatePlayerEvent, 2000); }); } tabs.on ("tabbedcontenttabopened", function (event, data) { if (data.newTab === "#tab_map") { if (HasPermission ("webapi.getplayerslocation")) { updatePlayerEvent (); } } else { window.clearTimeout (updatePlayerTimeout); } }); if (tabs.tabbedContent ("isTabOpen", "tab_map")) { if (HasPermission ("webapi.getplayerslocation")) { updatePlayerEvent (); } }
错误尝试
我曾在以下代码段添加setTimeout,结果导致实时地图崩溃:
.always(function() { updatePlayerTimeout = window.setTimeout(updatePlayerEvent, 2000); window.setTimeout(setPlayerMarker, 2000); // 新增的错误代码 });
解决方案
核心问题是原代码只切换了marker的分组,但没有主动更新marker的图标,另外你加的代码不仅拼写错误(正确函数名是setPlayerMarkers),还会导致重复触发逻辑混乱。
修改后的setPlayerMarkers函数:
var setPlayerMarkers = function(data) { var onlineIds = []; updatingMarkers = true; $.each(data, function(key, val) { var marker; // 判断是否已有该玩家的marker if (playersMappingList.hasOwnProperty(val.steamid)) { marker = playersMappingList[val.steamid].currentPosMarker; } else { // 新玩家根据在线状态创建对应图标 var initialIcon = val.online ? playerIcon : playerOfflineIcon; marker = L.marker([val.position.x, val.position.z], {icon: initialIcon}).bindPopup( "Player: " + $("<div>").text(val.name).html() + (HasPermission("webapi.getplayerinventory") ? "<br/><a class='inventoryButton' data-steamid='"+val.steamid+"'>Show inventory</a>" : "" ) ); marker.on("move", function(e) { if (this.isPopupOpen()) { map.flyTo(e.latlng, map.getZoom()); } }); // 初始化玩家数据 playersMappingList[val.steamid] = { online: !val.online, currentPosMarker: marker }; } // 记录在线玩家ID if (val.online) { onlineIds.push(val.steamid); } // 更新玩家位置 var oldpos = marker.getLatLng(); if (oldpos.lat != val.position.x || oldpos.lng != val.position.z) { marker.setLatLng([val.position.x, val.position.z]); } // 处理在线状态变化 if (playersMappingList[val.steamid].online !== val.online) { // 切换marker分组+更新图标 if (val.online) { playersOfflineMarkerGroup.removeLayer(marker); playersOnlineMarkerGroup.addLayer(marker); marker.setIcon(playerIcon); // 切换为在线图标 marker.setOpacity(1.0); } else { playersOnlineMarkerGroup.removeLayer(marker); playersOfflineMarkerGroup.addLayer(marker); marker.setIcon(playerOfflineIcon); // 切换为离线图标 marker.setOpacity(0.5); } // 更新玩家状态记录 playersMappingList[val.steamid].online = val.online; } // 保存玩家最新数据 val.currentPosMarker = marker; playersMappingList[val.steamid] = val; }); // 处理原本在线但现在离线的玩家 $.each(playersMappingList, function(key, val) { if (val.online && onlineIds.indexOf(key) < 0) { var marker = val.currentPosMarker; playersOnlineMarkerGroup.removeLayer(marker); playersOfflineMarkerGroup.addLayer(marker); marker.setIcon(playerOfflineIcon); // 切换为离线图标 marker.setOpacity(0.5); val.online = false; } }); // 更新在线/离线计数 var online = 0, offline = 0; $.each(playersMappingList, function(key, val) { val.online ? online++ : offline++; }); updatingMarkers = false; $("#mapControlOnlineCount").text(online); $("#mapControlOfflineCount").text(offline); }
关键修改点
- 合并了原代码中重复的if/else分支,减少冗余逻辑
- 在玩家状态切换时,通过
marker.setIcon()主动更新图标(这是原代码缺失的核心步骤) - 修正了玩家数据初始化逻辑,避免状态混乱
- 补充处理了玩家从在线变为离线时的图标切换
内容的提问来源于stack exchange,提问作者GuberX
相关产品推荐
相关产品推荐

