JavaScript通过IP地址获取用户精确位置的代码实现

 更新时间:2026年01月06日 09:22:12   作者:奶糖 肥晨  
在Web开发中,获取用户地理位置是常见的需求,传统的HTML5 Geolocation API虽然精确,但需要用户授权,且移动端支持较好而桌面端较差,本文将介绍JavaScript如何通过IP地址获取用户精确位置,需要的朋友可以参考下

引言

无需服务器,纯前端技术即可通过IP地址获取用户的经纬度坐标和详细地址信息。

在Web开发中,获取用户地理位置是常见的需求。传统的HTML5 Geolocation API虽然精确,但需要用户授权,且移动端支持较好而桌面端较差。本文将介绍一种无需用户授权的替代方案:通过IP地址获取用户地理位置,并附上完整的可直接运行的代码。

注:有大约5公里-50公里的误差

一、技术原理与可行性分析

1.1 IP定位的基本原理

IP地址定位基于庞大的地理位置数据库。每个IP地址段都被互联网服务提供商(ISP)分配,而这些IP段与物理位置有映射关系:

  1. ISP分配记录:每个ISP在特定区域分配IP段
  2. 路由表信息:网络路由包含地理位置线索
  3. Whois数据库:IP注册信息常包含地理位置
  4. 众包数据:用户反馈的位置数据不断校准数据库

1.2 不同级别的定位精度

定位级别准确率典型精度适用场景
国家级别99%+全国范围内容本地化、广告定向
省级/州级85-95%省级范围地区性 服务、物流预估
城市级别70-85%5-50公里本地新闻、天气预报
经纬度坐标60-80%1-50公里大致位置标记、地理围栏

1.3 与传统Geolocation对比

特性IP定位HTML5 Geolocation
需要用户授权❌ 不需要✅ 需要
桌面端支持✅ 优秀⚠️ 一般
移动端支持✅ 优秀✅ 优秀
精度⚠️ 中等(1-50km)✅ 高(<100m)
响应速度✅ 快(<200ms)⚠️ 慢(1-3s)
VPN/代理影响❌ 严重影响✅ 不受影响

二、核心实现方案

2.1 三层架构设计

为了确保可靠性和准确性,我们采用三层架构:

用户访问
    ↓
获取IP地址
    ↓
主API定位(ipapi.co)
    ↓ 失败 → 备用API定位(ip-api.com)
    ↓ 失败 → 浏览器语言推测
    ↓
获取经纬度坐标
    ↓
逆地理编码(OpenStreetMap)
    ↓
详细地址信息
    ↓
可视化展示

2.2 关键技术组件

1.IP地址获取

// 多源IP获取,提高成功率
async function getUserIP() {
    const apis = [
        'https://api.ipify.org?format=json',
        'https://api.ip.sb/ip',
        'https://icanhazip.com'
    ];
    
    for (const api of apis) {
        try {
            const response = await fetch(api);
            const text = await response.text();
            return text.trim();
        } catch (error) {
            continue;
        }
    }
    throw new Error('无法获取IP地址');
}

2.IP到地理位置转换

async function getIPLocation(ip) {
    // 使用ipapi.co API
    const response = await fetch(`https://ipapi.co/${ip}/json/`);
    const data = await response.json();
    
    return {
        latitude: parseFloat(data.latitude),
        longitude: parseFloat(data.longitude),
        country: data.country_name,
        city: data.city,
        region: data.region,
        // ... 其他信息
    };
}

3.逆地理编码(坐标→地址)

async function reverseGeocode(lat, lon) {
    const response = await fetch(
        `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lon}&zoom=18&accept-language=zh`
    );
    const data = await response.json();
    
    // 解析详细地址信息
    const address = data.address || {};
    return {
        display_name: data.display_name,
        full_address: [
            address.road,
            address.neighbourhood,
            address.city,
            address.county,
            address.state,
            address.country
        ].filter(Boolean).join(', ')
    };
}

2.3 精度优化策略

1.多API验证

// 同时使用多个API,选择最一致的结果
async function multiAPIVerification(ip) {
    const results = await Promise.allSettled([
        fetch('https://ipapi.co/json/'),
        fetch('http://ip-api.com/json/' + ip),
        fetch('https://api.ipgeolocation.io/ipgeo')
    ]);
    
    // 分析结果的一致性
    return analyzeConsistency(results);
}

2.网络延迟推测

// 通过延迟推测距离(简单实现)
function estimateDistanceByLatency(apiEndpoint) {
    const start = performance.now();
    return fetch(apiEndpoint).then(() => {
        const latency = performance.now() - start;
        // 简单模型:延迟越高,距离可能越远
        return Math.min(latency * 100, 50000); // 最大50公里
    });
}

3.浏览器信号增强

function enhanceWithBrowserSignals(ipLocation) {
    return {
        ...ipLocation,
        browserTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
        browserLanguage: navigator.language,
        userAgentCountry: getUserAgentCountry(),
        // 时区一致性检查
        timezoneMatch: checkTimezoneConsistency(ipLocation.timezone),
        // 语言一致性检查
        languageMatch: checkLanguageConsistency(ipLocation.country_code)
    };
}

三、完整实现代码

下面是一个可直接运行的完整实现,包含可视化界面:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>IP地址定位测试工具</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            line-height: 1.6;
            color: #333;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
        }

        .container {
            max-width: 1000px;
            margin: 0 auto;
            background: white;
            border-radius: 20px;
            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
            overflow: hidden;
        }

        header {
            background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
            color: white;
            padding: 40px 30px;
            text-align: center;
        }

        h1 {
            font-size: 2.5rem;
            margin-bottom: 10px;
            text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
        }

        .subtitle {
            font-size: 1.1rem;
            opacity: 0.9;
            max-width: 600px;
            margin: 0 auto;
        }

        .main-content {
            padding: 30px;
        }

        .card {
            background: #f8f9fa;
            border-radius: 15px;
            padding: 25px;
            margin-bottom: 25px;
            border: 1px solid #e9ecef;
            transition: transform 0.3s ease, box-shadow 0.3s ease;
        }

        .card:hover {
            transform: translateY(-5px);
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
        }

        .card h3 {
            color: #4facfe;
            margin-bottom: 15px;
            display: flex;
            align-items: center;
            gap: 10px;
        }

        .card h3 i {
            font-size: 1.2rem;
        }

        .data-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
            gap: 20px;
            margin-top: 15px;
        }

        .data-item {
            background: white;
            padding: 15px;
            border-radius: 10px;
            border-left: 4px solid #4facfe;
        }

        .data-item label {
            display: block;
            font-size: 0.85rem;
            color: #6c757d;
            margin-bottom: 5px;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }

        .data-item .value {
            font-size: 1.1rem;
            color: #212529;
            font-weight: 500;
            word-break: break-all;
        }

        .value.coordinates {
            font-family: 'Courier New', monospace;
            color: #e83e8c;
        }

        .value.ip {
            color: #28a745;
            font-weight: bold;
        }

        .map-container {
            height: 300px;
            background: #e9ecef;
            border-radius: 10px;
            overflow: hidden;
            margin-top: 15px;
            position: relative;
        }

        #map {
            width: 100%;
            height: 100%;
        }

        .map-placeholder {
            display: flex;
            align-items: center;
            justify-content: center;
            height: 100%;
            color: #6c757d;
            font-size: 1.1rem;
        }

        .buttons {
            display: flex;
            gap: 15px;
            margin-top: 20px;
            flex-wrap: wrap;
        }

        button {
            padding: 14px 28px;
            border: none;
            border-radius: 50px;
            font-size: 1rem;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 10px;
            min-width: 180px;
        }

        .primary-btn {
            background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
            color: white;
        }

        .primary-btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 10px 20px rgba(79, 172, 254, 0.3);
        }

        .secondary-btn {
            background: #6c757d;
            color: white;
        }

        .secondary-btn:hover {
            background: #5a6268;
            transform: translateY(-2px);
        }

        .danger-btn {
            background: #dc3545;
            color: white;
        }

        .danger-btn:hover {
            background: #c82333;
            transform: translateY(-2px);
        }

        .accuracy-meter {
            margin-top: 15px;
            padding: 15px;
            background: #fff3cd;
            border-radius: 10px;
            border-left: 4px solid #ffc107;
        }

        .accuracy-label {
            display: flex;
            justify-content: space-between;
            margin-bottom: 10px;
        }

        .meter-bar {
            height: 10px;
            background: #e9ecef;
            border-radius: 5px;
            overflow: hidden;
        }

        .meter-fill {
            height: 100%;
            background: linear-gradient(90deg, #20c997, #28a745);
            width: 0%;
            transition: width 1.5s ease;
        }

        .status {
            padding: 20px;
            text-align: center;
            font-size: 1.1rem;
            border-radius: 10px;
            margin-bottom: 20px;
            display: none;
        }

        .status.loading {
            background: #cfe2ff;
            color: #084298;
            display: block;
        }

        .status.error {
            background: #f8d7da;
            color: #721c24;
            display: block;
        }

        .status.success {
            background: #d1e7dd;
            color: #0f5132;
            display: block;
        }

        .footer {
            text-align: center;
            padding: 20px;
            color: #6c757d;
            font-size: 0.9rem;
            border-top: 1px solid #e9ecef;
            background: #f8f9fa;
        }

        .loading-spinner {
            display: inline-block;
            width: 20px;
            height: 20px;
            border: 3px solid rgba(255, 255, 255, 0.3);
            border-radius: 50%;
            border-top-color: white;
            animation: spin 1s ease-in-out infinite;
        }

        @keyframes spin {
            to { transform: rotate(360deg); }
        }

        @media (max-width: 768px) {
            .container {
                border-radius: 10px;
            }
            
            header {
                padding: 30px 20px;
            }
            
            h1 {
                font-size: 2rem;
            }
            
            .main-content {
                padding: 20px;
            }
            
            button {
                width: 100%;
            }
            
            .data-grid {
                grid-template-columns: 1fr;
            }
        }

        /* 图标样式 */
        .icon {
            display: inline-block;
            width: 24px;
            height: 24px;
            stroke-width: 0;
            stroke: currentColor;
            fill: currentColor;
        }

        /* 隐私提示 */
        .privacy-notice {
            background: #e7f3ff;
            border-radius: 10px;
            padding: 20px;
            margin-bottom: 25px;
            border-left: 4px solid #4facfe;
        }

        .privacy-notice h4 {
            color: #4facfe;
            margin-bottom: 10px;
            display: flex;
            align-items: center;
            gap: 10px;
        }
    </style>
    <!-- Leaflet CSS -->
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" rel="external nofollow"  />
    <!-- Leaflet JS -->
    <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
</head>
<body>
    <div class="container">
        <header>
            <h1>🌍 IP地址精确定位工具</h1>
            <p class="subtitle">通过IP地址获取用户的经纬度坐标、详细地址和网络信息</p>
        </header>

        <div class="main-content">
            <!-- 隐私提示 -->
            <div class="privacy-notice">
                <h4>🔒 隐私提示</h4>
                <p>本工具会获取您的IP地址和大致地理位置信息。所有处理均在您的浏览器中完成,数据不会被保存到服务器。</p>
            </div>

            <!-- 状态显示 -->
            <div id="status" class="status"></div>

            <!-- IP信息卡片 -->
            <div class="card">
                <h3>📍 基本信息</h3>
                <div class="data-grid">
                    <div class="data-item">
                        <label>IP 地址</label>
                        <div id="ip" class="value ip">正在获取...</div>
                    </div>
                    <div class="data-item">
                        <label>网络提供商</label>
                        <div id="isp" class="value">正在获取...</div>
                    </div>
                    <div class="data-item">
                        <label>定位方式</label>
                        <div id="method" class="value">IP地址定位</div>
                    </div>
                    <div class="data-item">
                        <label>数据来源</label>
                        <div id="source" class="value">ipapi.co + 逆地理编码</div>
                    </div>
                </div>
            </div>

            <!-- 位置信息卡片 -->
            <div class="card">
                <h3>🗺️ 地理位置</h3>
                <div class="data-grid">
                    <div class="data-item">
                        <label>国家/地区</label>
                        <div id="country" class="value">正在获取...</div>
                    </div>
                    <div class="data-item">
                        <label>省/州</label>
                        <div id="region" class="value">正在获取...</div>
                    </div>
                    <div class="data-item">
                        <label>城市</label>
                        <div id="city" class="value">正在获取...</div>
                    </div>
                    <div class="data-item">
                        <label>邮政编码</label>
                        <div id="zipcode" class="value">正在获取...</div>
                    </div>
                </div>
            </div>

            <!-- 经纬度卡片 -->
            <div class="card">
                <h3>📡 坐标信息</h3>
                <div class="data-grid">
                    <div class="data-item">
                        <label>纬度</label>
                        <div id="latitude" class="value coordinates">正在获取...</div>
                    </div>
                    <div class="data-item">
                        <label>经度</label>
                        <div id="longitude" class="value coordinates">正在获取...</div>
                    </div>
                    <div class="data-item">
                        <label>时区</label>
                        <div id="timezone" class="value">正在获取...</div>
                    </div>
                    <div class="data-item">
                        <label>货币</label>
                        <div id="currency" class="value">正在获取...</div>
                    </div>
                </div>
                
                <!-- 精度指示器 -->
                <div class="accuracy-meter">
                    <div class="accuracy-label">
                        <span>定位精度</span>
                        <span id="accuracy-text">未知</span>
                    </div>
                    <div class="meter-bar">
                        <div id="accuracy-meter" class="meter-fill"></div>
                    </div>
                    <small style="color: #6c757d; display: block; margin-top: 5px;">
                        注:IP定位精度通常为1-50公里,受网络类型和V*P*N影响
                    </small>
                </div>
            </div>

            <!-- 详细地址卡片 -->
            <div class="card">
                <h3>🏠 详细地址</h3>
                <div id="detailed-address" style="font-size: 1.1rem; line-height: 1.6; padding: 15px; background: white; border-radius: 8px; min-height: 60px;">
                    正在获取详细地址信息...
                </div>
            </div>

            <!-- 地图容器 -->
            <div class="card">
                <h3>🗺️ 位置地图</h3>
                <div class="map-container">
                    <div id="map"></div>
                    <div id="map-placeholder" class="map-placeholder">
                        获取位置后,将在此显示地图
                    </div>
                </div>
            </div>

            <!-- 操作按钮 -->
            <div class="buttons">
                <button id="locate-btn" class="primary-btn" onclick="startLocationDetection()">
                    <span class="loading-spinner" style="display: none;"></span>
                    <span id="btn-text">🚀 开始定位检测</span>
                </button>
                <button class="secondary-btn" onclick="copyLocationData()">
                    📋 复制位置数据
                </button>
                <button class="secondary-btn" onclick="refreshLocation()">
                    🔄 重新检测
                </button>
               
            </div>
        </div>

        <div class="footer">
            <p>⚠️ 注意:此工具仅供学习和测试使用。IP定位精度有限,不适用于需要精确定位的场景。</p>
            <p>📊 最后更新: <span id="update-time"></span></p>
        </div>
    </div>

    <script>
        // 全局变量
        let map = null;
        let marker = null;
        let currentLocation = null;

        // 页面加载完成后初始化
        document.addEventListener('DOMContentLoaded', function() {
            // 显示当前时间
            document.getElementById('update-time').textContent = new Date().toLocaleString('zh-CN');
            
            // 开始自动检测
            setTimeout(() => {
                startLocationDetection();
            }, 1000);
        });

        // 主函数:开始位置检测
        async function startLocationDetection() {
            const btn = document.getElementById('locate-btn');
            const btnText = document.getElementById('btn-text');
            const spinner = btn.querySelector('.loading-spinner');
            
            // 更新按钮状态
            btnText.textContent = '正在定位...';
            spinner.style.display = 'inline-block';
            btn.disabled = true;
            
            // 显示加载状态
            showStatus('正在通过IP地址获取您的位置信息...', 'loading');
            
            try {
                // 1. 获取IP地址
                const ip = await getUserIP();
                document.getElementById('ip').textContent = ip;
                
                // 2. 通过IP获取基础位置信息
                const ipLocation = await getIPLocation(ip);
                
                // 3. 进行逆地理编码获取详细地址
                const detailedAddress = await reverseGeocode(
                    ipLocation.latitude, 
                    ipLocation.longitude
                );
                
                // 4. 合并位置数据
                currentLocation = {
                    ip: ip,
                    ...ipLocation,
                    detailedAddress: detailedAddress,
                    timestamp: new Date().toISOString()
                };
                
                // 5. 更新UI
                updateUI(currentLocation);
                
                // 6. 在地图上标记位置
                updateMap(currentLocation.latitude, currentLocation.longitude);
                
                // 7. 更新精度指示器
                updateAccuracyIndicator(ipLocation.accuracy || 'medium');
                
                showStatus('位置信息获取成功!', 'success');
                
            } catch (error) {
                console.error('定位失败:', error);
                showStatus(`定位失败: ${error.message}`, 'error');
                
                // 使用默认位置(上海)作为演示
                useDemoLocation();
            } finally {
                // 恢复按钮状态
                btnText.textContent = '🚀 重新检测';
                spinner.style.display = 'none';
                btn.disabled = false;
            }
        }

        // 获取用户公网IP
        async function getUserIP() {
            try {
                // 方法1: 使用 ipify.org
                const response = await fetch('https://api.ipify.org?format=json');
                const data = await response.json();
                return data.ip;
            } catch (error) {
                // 方法2: 使用多个备选API
                const backupAPIs = [
                    'https://api.ipify.org?format=json',
                    'https://api.ip.sb/ip',
                    'https://icanhazip.com'
                ];
                
                for (const api of backupAPIs) {
                    try {
                        const response = await fetch(api);
                        const text = await response.text();
                        return text.trim();
                    } catch (e) {
                        continue;
                    }
                }
                
                throw new Error('无法获取IP地址');
            }
        }

        // 通过IP获取地理位置
        async function getIPLocation(ip) {
            // 尝试多个API以提高成功率
            const apis = [
                `https://ipapi.co/${ip}/json/`,  // 主API
                `https://ipapi.co/json/`,         // 备选(自动检测IP)
                'https://api.ipgeolocation.io/ipgeo?apiKey=demo'  // 演示API
            ];
            
            for (const apiUrl of apis) {
                try {
                    console.log(`尝试API: ${apiUrl}`);
                    const response = await fetch(apiUrl, {
                        headers: {
                            'Accept': 'application/json'
                        }
                    });
                    
                    if (!response.ok) continue;
                    
                    const data = await response.json();
                    
                    // 检查是否有经纬度数据
                    if (data.latitude && data.longitude) {
                        return {
                            latitude: parseFloat(data.latitude),
                            longitude: parseFloat(data.longitude),
                            country: data.country_name || data.country,
                            country_code: data.country_code || data.countryCode,
                            region: data.region || data.regionName || data.state_prov,
                            city: data.city || data.cityName,
                            postal: data.postal || data.zip || data.zipcode,
                            timezone: data.timezone || data.time_zone,
                            currency: data.currency || data.currency_code,
                            isp: data.isp || data.org || data.asn,
                            accuracy: data.accuracy || (data.accuracy_radius ? `${data.accuracy_radius}km` : 'medium')
                        };
                    }
                } catch (error) {
                    console.warn(`API ${apiUrl} 失败:`, error);
                    continue;
                }
            }
            
            throw new Error('所有IP定位API都失败了');
        }

        // 逆地理编码:坐标 -> 详细地址
        async function reverseGeocode(latitude, longitude) {
            try {
                // 使用Nominatim(OpenStreetMap)进行逆地理编码
                const response = await fetch(
                    `https://nominatim.openstreetmap.org/reverse?` +
                    `format=json&lat=${latitude}&lon=${longitude}` +
                    `&addressdetails=1&zoom=18&accept-language=zh`
                );
                
                if (!response.ok) {
                    throw new Error('逆地理编码服务不可用');
                }
                
                const data = await response.json();
                
                if (data.error) {
                    throw new Error(data.error);
                }
                
                const address = data.address || {};
                
                return {
                    display_name: data.display_name || '未知地址',
                    road: address.road || address.street || '',
                    neighborhood: address.neighbourhood || address.suburb || '',
                    city: address.city || address.town || address.village || '',
                    county: address.county || '',
                    state: address.state || address.region || '',
                    country: address.country || '',
                    postcode: address.postcode || '',
                    country_code: address.country_code || '',
                    full_address: [
                        address.road,
                        address.neighbourhood,
                        address.city || address.town,
                        address.county,
                        address.state,
                        address.country
                    ].filter(Boolean).join(', ')
                };
                
            } catch (error) {
                console.warn('逆地理编码失败:', error);
                
                // 返回简化地址
                return {
                    display_name: '无法获取详细地址',
                    full_address: '逆地理编码服务暂时不可用',
                    is_fallback: true
                };
            }
        }

        // 更新UI显示
        function updateUI(location) {
            // 基本信息
            document.getElementById('isp').textContent = location.isp || '未知';
            
            // 地理位置
            document.getElementById('country').textContent = location.country || '未知';
            document.getElementById('region').textContent = location.region || '未知';
            document.getElementById('city').textContent = location.city || '未知';
            document.getElementById('zipcode').textContent = location.postal || '未知';
            
            // 坐标信息
            document.getElementById('latitude').textContent = location.latitude.toFixed(6);
            document.getElementById('longitude').textContent = location.longitude.toFixed(6);
            document.getElementById('timezone').textContent = location.timezone || '未知';
            document.getElementById('currency').textContent = location.currency || '未知';
            
            // 详细地址
            const addressElement = document.getElementById('detailed-address');
            if (location.detailedAddress && location.detailedAddress.full_address) {
                addressElement.innerHTML = `
                    <strong>${location.detailedAddress.display_name}</strong>
                    <div style="margin-top: 10px; color: #666;">
                        解析地址: ${location.detailedAddress.full_address}
                        ${location.detailedAddress.is_fallback ? '
<small style="color: #dc3545;">(这是简化地址,详细地址获取失败)</small>' : ''}
                    </div>
                `;
            } else {
                addressElement.innerHTML = '<span style="color: #dc3545;">无法获取详细地址信息</span>';
            }
        }

        // 更新地图显示
        function updateMap(latitude, longitude) {
            const mapContainer = document.getElementById('map');
            const mapPlaceholder = document.getElementById('map-placeholder');
            
            // 隐藏占位符
            mapPlaceholder.style.display = 'none';
            mapContainer.style.display = 'block';
            
            if (!map) {
                // 初始化地图
                map = L.map('map').setView([latitude, longitude], 12);
                
                // 添加地图图层
                L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
                    attribution: '© <a href="https://www.openstreetmap.org/copyright" rel="external nofollow" >OpenStreetMap</a> contributors',
                    maxZoom: 18
                }).addTo(map);
            } else {
                // 更新地图中心
                map.setView([latitude, longitude], 12);
            }
            
            // 移除旧标记
            if (marker) {
                map.removeLayer(marker);
            }
            
            // 添加新标记
            marker = L.marker([latitude, longitude]).addTo(map);
            
            // 添加弹出信息
            marker.bindPopup(`
                <b>📍 检测到的位置</b>

                纬度: ${latitude.toFixed(6)}

                经度: ${longitude.toFixed(6)}

                <small>IP定位,精度有限</small>
            `).openPopup();
            
            // 添加精度圆圈(假设精度为5公里)
            L.circle([latitude, longitude], {
                color: '#4facfe',
                fillColor: '#4facfe',
                fillOpacity: 0.1,
                radius: 5000 // 5公里
            }).addTo(map);
        }

        // 更新精度指示器
        function updateAccuracyIndicator(accuracy) {
            const accuracyText = document.getElementById('accuracy-text');
            const accuracyMeter = document.getElementById('accuracy-meter');
            
            let percentage = 50; // 默认中等精度
            
            if (typeof accuracy === 'string') {
                if (accuracy.includes('high') || accuracy.includes('高')) {
                    percentage = 80;
                    accuracyText.textContent = '高精度 (1-5公里)';
                } else if (accuracy.includes('low') || accuracy.includes('低')) {
                    percentage = 30;
                    accuracyText.textContent = '低精度 (50+公里)';
                } else if (accuracy.includes('km')) {
                    const km = parseInt(accuracy);
                    if (km <= 5) {
                        percentage = 80;
                        accuracyText.textContent = `高精度 (${km}公里)`;
                    } else if (km <= 20) {
                        percentage = 60;
                        accuracyText.textContent = `中精度 (${km}公里)`;
                    } else {
                        percentage = 40;
                        accuracyText.textContent = `低精度 (${km}公里)`;
                    }
                } else {
                    accuracyText.textContent = '中等精度 (5-20公里)';
                }
            } else {
                accuracyText.textContent = '中等精度';
            }
            
            // 动画效果显示进度条
            setTimeout(() => {
                accuracyMeter.style.width = `${percentage}%`;
            }, 100);
        }

        // 显示状态信息
        function showStatus(message, type = 'info') {
            const statusElement = document.getElementById('status');
            
            // 清除旧状态
            statusElement.className = 'status';
            
            // 设置新状态
            statusElement.textContent = message;
            statusElement.classList.add(type);
            statusElement.style.display = 'block';
            
            // 3秒后自动隐藏成功/信息状态
            if (type === 'success' || type === 'info') {
                setTimeout(() => {
                    statusElement.style.display = 'none';
                }, 3000);
            }
        }

        // 使用演示位置(上海)
        function useDemoLocation() {
            const demoLocation = {
                ip: '116.228.111.118',
                latitude: 31.2304,
                longitude: 121.4737,
                country: '中国',
                country_code: 'CN',
                region: '上海',
                city: '上海市',
                postal: '200000',
                timezone: 'Asia/Shanghai',
                currency: 'CNY',
                isp: 'China Telecom',
                accuracy: 'high',
                detailedAddress: {
                    display_name: '上海市, 中国',
                    full_address: '上海市, 中国',
                    is_fallback: true
                }
            };
            
            currentLocation = demoLocation;
            updateUI(demoLocation);
            updateMap(demoLocation.latitude, demoLocation.longitude);
            updateAccuracyIndicator('high');
            
            showStatus('正在使用演示数据(上海)', 'info');
        }

        // 复制位置数据到剪贴板
        function copyLocationData() {
            if (!currentLocation) {
                showStatus('请先获取位置数据', 'error');
                return;
            }
            
            const data = {
                时间: new Date().toLocaleString('zh-CN'),
                IP地址: currentLocation.ip,
                网络提供商: currentLocation.isp,
                国家: currentLocation.country,
                省份: currentLocation.region,
                城市: currentLocation.city,
                邮政编码: currentLocation.postal,
                纬度: currentLocation.latitude,
                经度: currentLocation.longitude,
                时区: currentLocation.timezone,
                货币: currentLocation.currency,
                详细地址: currentLocation.detailedAddress?.full_address || '未知',
                定位方式: 'IP地址定位',
                精度: document.getElementById('accuracy-text').textContent
            };
            
            const text = Object.entries(data)
                .map(([key, value]) => `${key}: ${value}`)
                .join('\n');
            
            navigator.clipboard.writeText(text)
                .then(() => {
                    showStatus('位置数据已复制到剪贴板!', 'success');
                })
                .catch(err => {
                    console.error('复制失败:', err);
                    showStatus('复制失败,请手动复制', 'error');
                });
        }

        // 重新检测位置
        function refreshLocation() {
            // 清空地图
            if (marker) {
                map.removeLayer(marker);
                marker = null;
            }
            
            // 重置显示
            document.getElementById('ip').textContent = '正在获取...';
            document.getElementById('isp').textContent = '正在获取...';
            document.getElementById('country').textContent = '正在获取...';
            document.getElementById('region').textContent = '正在获取...';
            document.getElementById('city').textContent = '正在获取...';
            document.getElementById('zipcode').textContent = '正在获取...';
            document.getElementById('latitude').textContent = '正在获取...';
            document.getElementById('longitude').textContent = '正在获取...';
            document.getElementById('timezone').textContent = '正在获取...';
            document.getElementById('currency').textContent = '正在获取...';
            document.getElementById('detailed-address').textContent = '正在获取详细地址信息...';
            
            // 显示地图占位符
            document.getElementById('map-placeholder').style.display = 'flex';
            document.getElementById('map').style.display = 'none';
            
            // 重置精度指示器
            document.getElementById('accuracy-meter').style.width = '0%';
            document.getElementById('accuracy-text').textContent = '未知';
            
            // 开始新的检测
            startLocationDetection();
        }

     
    </script>
</body>
</html>

立即尝试:将完整代码保存为HTML文件,用浏览器打开即可体验纯前端的IP地理位置检测功能!

以上就是JavaScript通过IP地址获取用户精确位置的代码实现的详细内容,更多关于JavaScript IP地址获取用户位置的资料请关注脚本之家其它相关文章!

相关文章

  • JS实现的自定义map方法示例

    JS实现的自定义map方法示例

    这篇文章主要介绍了JS实现的自定义map方法,结合实例形式分析了javascript自定义map相关的json数组定义、遍历、添加、删除、读取等相关操作技巧,需要的朋友可以参考下
    2019-05-05
  • 关于JavaScript对象的动态选择及遍历对象

    关于JavaScript对象的动态选择及遍历对象

    本文为大家介绍下JavaScript对象的两点:动态选择方法及属性、遍历对象属性和方法,需要的朋友可以参考下
    2014-03-03
  • 详解基于webpack2.x的vue2.x的多页面站点

    详解基于webpack2.x的vue2.x的多页面站点

    本篇文章主要主要介绍了基于webpack2.x的vue2.x的多页面站点 ,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2017-08-08
  • Javascript 拖拽雏形(逐行分析代码,让你轻松了拖拽的原理)

    Javascript 拖拽雏形(逐行分析代码,让你轻松了拖拽的原理)

    这篇文章主要介绍了Javascript 拖拽雏形(逐行分析代码,让你轻松了拖拽的原理),需要的朋友可以参考下
    2015-01-01
  • 浅谈JavaScript节流与防抖

    浅谈JavaScript节流与防抖

    这篇文章主要为大家介绍了JavaScript的节流与防抖,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2021-11-11
  • javascript操作table(insertRow,deleteRow,insertCell,deleteCell方法详解)

    javascript操作table(insertRow,deleteRow,insertCell,deleteCell方

    本篇文章主要介绍了javascript操作table(insertRow,deleteRow,insertCell,deleteCell方法)需要的朋友可以过来参考下,希望对大家有所帮助
    2013-12-12
  • javascript中IIFE立即执行函数表达式来龙去脉解析

    javascript中IIFE立即执行函数表达式来龙去脉解析

    Ben Alman于2010年提出IIFE立即执行函数表达式,取代旧称Self-ExecutingAnonymousFunction,通过括号包裹函数声明转为表达式,实现立即执行,常见形式为()包裹,其他变种如new、void等不推荐使用,感兴趣的朋友一起看看吧
    2025-07-07
  • 前端冒泡排序算法详解及实战案例

    前端冒泡排序算法详解及实战案例

    这篇文章主要介绍了前端冒泡排序算法的相关资料,冒泡排序是一种简单的排序算法,通过比较相邻元素并交换位置,实现元素排序,该算法的时间复杂度为O(n^2),空间复杂度为O(1),具有稳定性,适用于小规模数据集和对稳定性要求高的场景,需要的朋友可以参考下
    2024-10-10
  • 一起来了解JavaScript的变量作用域

    一起来了解JavaScript的变量作用域

    这篇文章主要为大家详细介绍了JavaScript变量作用域,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-03-03
  • Three.js实现3D机房效果

    Three.js实现3D机房效果

    这篇文章主要为大家详细介绍了Three.js实现3D机房效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-12-12

最新评论