You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 04:47:06