JavaScript通過IP地址獲取用戶精確位置的代碼實(shí)現(xiàn)
引言
無需服務(wù)器,純前端技術(shù)即可通過IP地址獲取用戶的經(jīng)緯度坐標(biāo)和詳細(xì)地址信息。
在Web開發(fā)中,獲取用戶地理位置是常見的需求。傳統(tǒng)的HTML5 Geolocation API雖然精確,但需要用戶授權(quán),且移動(dòng)端支持較好而桌面端較差。本文將介紹一種無需用戶授權(quán)的替代方案:通過IP地址獲取用戶地理位置,并附上完整的可直接運(yùn)行的代碼。


注:有大約5公里-50公里的誤差
一、技術(shù)原理與可行性分析
1.1 IP定位的基本原理
IP地址定位基于龐大的地理位置數(shù)據(jù)庫。每個(gè)IP地址段都被互聯(lián)網(wǎng)服務(wù)提供商(ISP)分配,而這些IP段與物理位置有映射關(guān)系:
- ISP分配記錄:每個(gè)ISP在特定區(qū)域分配IP段
- 路由表信息:網(wǎng)絡(luò)路由包含地理位置線索
- Whois數(shù)據(jù)庫:IP注冊(cè)信息常包含地理位置
- 眾包數(shù)據(jù):用戶反饋的位置數(shù)據(jù)不斷校準(zhǔn)數(shù)據(jù)庫
1.2 不同級(jí)別的定位精度
| 定位級(jí)別 | 準(zhǔn)確率 | 典型精度 | 適用場(chǎng)景 |
|---|---|---|---|
| 國家級(jí)別 | 99%+ | 全國范圍 | 內(nèi)容本地化、廣告定向 |
| 省級(jí)/州級(jí) | 85-95% | 省級(jí)范圍 | 地區(qū)性 服務(wù)、物流預(yù)估 |
| 城市級(jí)別 | 70-85% | 5-50公里 | 本地新聞、天氣預(yù)報(bào) |
| 經(jīng)緯度坐標(biāo) | 60-80% | 1-50公里 | 大致位置標(biāo)記、地理圍欄 |
1.3 與傳統(tǒng)Geolocation對(duì)比
| 特性 | IP定位 | HTML5 Geolocation |
|---|---|---|
| 需要用戶授權(quán) | ? 不需要 | ? 需要 |
| 桌面端支持 | ? 優(yōu)秀 | ?? 一般 |
| 移動(dòng)端支持 | ? 優(yōu)秀 | ? 優(yōu)秀 |
| 精度 | ?? 中等(1-50km) | ? 高(<100m) |
| 響應(yīng)速度 | ? 快(<200ms) | ?? 慢(1-3s) |
| VPN/代理影響 | ? 嚴(yán)重影響 | ? 不受影響 |
二、核心實(shí)現(xiàn)方案
2.1 三層架構(gòu)設(shè)計(jì)
為了確??煽啃院蜏?zhǔn)確性,我們采用三層架構(gòu):
用戶訪問
↓
獲取IP地址
↓
主API定位(ipapi.co)
↓ 失敗 → 備用API定位(ip-api.com)
↓ 失敗 → 瀏覽器語言推測(cè)
↓
獲取經(jīng)緯度坐標(biāo)
↓
逆地理編碼(OpenStreetMap)
↓
詳細(xì)地址信息
↓
可視化展示
2.2 關(guān)鍵技術(shù)組件
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到地理位置轉(zhuǎn)換
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.逆地理編碼(坐標(biāo)→地址)
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();
// 解析詳細(xì)地址信息
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 精度優(yōu)化策略
1.多API驗(yàn)證
// 同時(shí)使用多個(gè)API,選擇最一致的結(jié)果
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')
]);
// 分析結(jié)果的一致性
return analyzeConsistency(results);
}
2.網(wǎng)絡(luò)延遲推測(cè)
// 通過延遲推測(cè)距離(簡單實(shí)現(xiàn))
function estimateDistanceByLatency(apiEndpoint) {
const start = performance.now();
return fetch(apiEndpoint).then(() => {
const latency = performance.now() - start;
// 簡單模型:延遲越高,距離可能越遠(yuǎn)
return Math.min(latency * 100, 50000); // 最大50公里
});
}
3.瀏覽器信號(hào)增強(qiáng)
function enhanceWithBrowserSignals(ipLocation) {
return {
...ipLocation,
browserTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
browserLanguage: navigator.language,
userAgentCountry: getUserAgentCountry(),
// 時(shí)區(qū)一致性檢查
timezoneMatch: checkTimezoneConsistency(ipLocation.timezone),
// 語言一致性檢查
languageMatch: checkLanguageConsistency(ipLocation.country_code)
};
}
三、完整實(shí)現(xiàn)代碼
下面是一個(gè)可直接運(yùn)行的完整實(shí)現(xiàn),包含可視化界面:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IP地址定位測(cè)試工具</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;
}
}
/* 圖標(biāo)樣式 */
.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" 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地址獲取用戶的經(jīng)緯度坐標(biāo)、詳細(xì)地址和網(wǎng)絡(luò)信息</p>
</header>
<div class="main-content">
<!-- 隱私提示 -->
<div class="privacy-notice">
<h4>?? 隱私提示</h4>
<p>本工具會(huì)獲取您的IP地址和大致地理位置信息。所有處理均在您的瀏覽器中完成,數(shù)據(jù)不會(huì)被保存到服務(wù)器。</p>
</div>
<!-- 狀態(tài)顯示 -->
<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>網(wǎng)絡(luò)提供商</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>數(shù)據(jù)來源</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>國家/地區(qū)</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>
<!-- 經(jīng)緯度卡片 -->
<div class="card">
<h3>?? 坐標(biāo)信息</h3>
<div class="data-grid">
<div class="data-item">
<label>緯度</label>
<div id="latitude" class="value coordinates">正在獲取...</div>
</div>
<div class="data-item">
<label>經(jīng)度</label>
<div id="longitude" class="value coordinates">正在獲取...</div>
</div>
<div class="data-item">
<label>時(shí)區(qū)</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公里,受網(wǎng)絡(luò)類型和V*P*N影響
</small>
</div>
</div>
<!-- 詳細(xì)地址卡片 -->
<div class="card">
<h3>?? 詳細(xì)地址</h3>
<div id="detailed-address" style="font-size: 1.1rem; line-height: 1.6; padding: 15px; background: white; border-radius: 8px; min-height: 60px;">
正在獲取詳細(xì)地址信息...
</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">?? 開始定位檢測(cè)</span>
</button>
<button class="secondary-btn" onclick="copyLocationData()">
?? 復(fù)制位置數(shù)據(jù)
</button>
<button class="secondary-btn" onclick="refreshLocation()">
?? 重新檢測(cè)
</button>
</div>
</div>
<div class="footer">
<p>?? 注意:此工具僅供學(xué)習(xí)和測(cè)試使用。IP定位精度有限,不適用于需要精確定位的場(chǎng)景。</p>
<p>?? 最后更新: <span id="update-time"></span></p>
</div>
</div>
<script>
// 全局變量
let map = null;
let marker = null;
let currentLocation = null;
// 頁面加載完成后初始化
document.addEventListener('DOMContentLoaded', function() {
// 顯示當(dāng)前時(shí)間
document.getElementById('update-time').textContent = new Date().toLocaleString('zh-CN');
// 開始自動(dòng)檢測(cè)
setTimeout(() => {
startLocationDetection();
}, 1000);
});
// 主函數(shù):開始位置檢測(cè)
async function startLocationDetection() {
const btn = document.getElementById('locate-btn');
const btnText = document.getElementById('btn-text');
const spinner = btn.querySelector('.loading-spinner');
// 更新按鈕狀態(tài)
btnText.textContent = '正在定位...';
spinner.style.display = 'inline-block';
btn.disabled = true;
// 顯示加載狀態(tài)
showStatus('正在通過IP地址獲取您的位置信息...', 'loading');
try {
// 1. 獲取IP地址
const ip = await getUserIP();
document.getElementById('ip').textContent = ip;
// 2. 通過IP獲取基礎(chǔ)位置信息
const ipLocation = await getIPLocation(ip);
// 3. 進(jìn)行逆地理編碼獲取詳細(xì)地址
const detailedAddress = await reverseGeocode(
ipLocation.latitude,
ipLocation.longitude
);
// 4. 合并位置數(shù)據(jù)
currentLocation = {
ip: ip,
...ipLocation,
detailedAddress: detailedAddress,
timestamp: new Date().toISOString()
};
// 5. 更新UI
updateUI(currentLocation);
// 6. 在地圖上標(biāo)記位置
updateMap(currentLocation.latitude, currentLocation.longitude);
// 7. 更新精度指示器
updateAccuracyIndicator(ipLocation.accuracy || 'medium');
showStatus('位置信息獲取成功!', 'success');
} catch (error) {
console.error('定位失敗:', error);
showStatus(`定位失敗: ${error.message}`, 'error');
// 使用默認(rèn)位置(上海)作為演示
useDemoLocation();
} finally {
// 恢復(fù)按鈕狀態(tài)
btnText.textContent = '?? 重新檢測(cè)';
spinner.style.display = 'none';
btn.disabled = false;
}
}
// 獲取用戶公網(wǎng)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: 使用多個(gè)備選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) {
// 嘗試多個(gè)API以提高成功率
const apis = [
`https://ipapi.co/${ip}/json/`, // 主API
`https://ipapi.co/json/`, // 備選(自動(dòng)檢測(cè)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();
// 檢查是否有經(jīng)緯度數(shù)據(jù)
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都失敗了');
}
// 逆地理編碼:坐標(biāo) -> 詳細(xì)地址
async function reverseGeocode(latitude, longitude) {
try {
// 使用Nominatim(OpenStreetMap)進(jìn)行逆地理編碼
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('逆地理編碼服務(wù)不可用');
}
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: '無法獲取詳細(xì)地址',
full_address: '逆地理編碼服務(wù)暫時(shí)不可用',
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 || '未知';
// 坐標(biāo)信息
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 || '未知';
// 詳細(xì)地址
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;">(這是簡化地址,詳細(xì)地址獲取失敗)</small>' : ''}
</div>
`;
} else {
addressElement.innerHTML = '<span style="color: #dc3545;">無法獲取詳細(xì)地址信息</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 rel="external nofollow" >OpenStreetMap</a> contributors',
maxZoom: 18
}).addTo(map);
} else {
// 更新地圖中心
map.setView([latitude, longitude], 12);
}
// 移除舊標(biāo)記
if (marker) {
map.removeLayer(marker);
}
// 添加新標(biāo)記
marker = L.marker([latitude, longitude]).addTo(map);
// 添加彈出信息
marker.bindPopup(`
<b>?? 檢測(cè)到的位置</b>
緯度: ${latitude.toFixed(6)}
經(jīng)度: ${longitude.toFixed(6)}
<small>IP定位,精度有限</small>
`).openPopup();
// 添加精度圓圈(假設(shè)精度為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; // 默認(rèn)中等精度
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 = '中等精度';
}
// 動(dòng)畫效果顯示進(jìn)度條
setTimeout(() => {
accuracyMeter.style.width = `${percentage}%`;
}, 100);
}
// 顯示狀態(tài)信息
function showStatus(message, type = 'info') {
const statusElement = document.getElementById('status');
// 清除舊狀態(tài)
statusElement.className = 'status';
// 設(shè)置新狀態(tài)
statusElement.textContent = message;
statusElement.classList.add(type);
statusElement.style.display = 'block';
// 3秒后自動(dòng)隱藏成功/信息狀態(tài)
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('正在使用演示數(shù)據(jù)(上海)', 'info');
}
// 復(fù)制位置數(shù)據(jù)到剪貼板
function copyLocationData() {
if (!currentLocation) {
showStatus('請(qǐng)先獲取位置數(shù)據(jù)', 'error');
return;
}
const data = {
時(shí)間: new Date().toLocaleString('zh-CN'),
IP地址: currentLocation.ip,
網(wǎng)絡(luò)提供商: currentLocation.isp,
國家: currentLocation.country,
省份: currentLocation.region,
城市: currentLocation.city,
郵政編碼: currentLocation.postal,
緯度: currentLocation.latitude,
經(jīng)度: currentLocation.longitude,
時(shí)區(qū): currentLocation.timezone,
貨幣: currentLocation.currency,
詳細(xì)地址: 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('位置數(shù)據(jù)已復(fù)制到剪貼板!', 'success');
})
.catch(err => {
console.error('復(fù)制失敗:', err);
showStatus('復(fù)制失敗,請(qǐng)手動(dòng)復(fù)制', 'error');
});
}
// 重新檢測(cè)位置
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 = '正在獲取詳細(xì)地址信息...';
// 顯示地圖占位符
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 = '未知';
// 開始新的檢測(cè)
startLocationDetection();
}
</script>
</body>
</html>
立即嘗試:將完整代碼保存為HTML文件,用瀏覽器打開即可體驗(yàn)純前端的IP地理位置檢測(cè)功能!
以上就是JavaScript通過IP地址獲取用戶精確位置的代碼實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于JavaScript IP地址獲取用戶位置的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
2019 年編寫現(xiàn)代 JavaScript 代碼的5個(gè)小技巧(小結(jié))
這篇文章主要介紹了2019 年編寫現(xiàn)代 JavaScript 代碼的5個(gè)小技巧,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-01-01
微信小程序基于canvas漸變實(shí)現(xiàn)的彩虹效果示例
這篇文章主要介紹了微信小程序基于canvas漸變實(shí)現(xiàn)的彩虹效果,結(jié)合實(shí)例形式分析了微信小程序線性漸變及圓形漸變的相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2019-05-05
Microsoft Ajax Minifier 壓縮javascript的方法
使用Microsoft AJAX 庫 (第六個(gè)預(yù)覽版) 其中有一個(gè) ajaxmin.exe 可以壓縮Js文件可以在dos 命令下 /? 查看其參數(shù)說明。2010-03-03
Bootstrap編寫一個(gè)在當(dāng)前網(wǎng)頁彈出可關(guān)閉的對(duì)話框 非彈窗
這篇文章主要介紹了Bootstrap編寫一個(gè)在當(dāng)前網(wǎng)頁彈出可關(guān)閉的對(duì)話框,不用跳轉(zhuǎn),非彈窗,感興趣的小伙伴們可以參考一下2016-06-06
js實(shí)現(xiàn)點(diǎn)擊彈窗彈出登錄框
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)點(diǎn)擊彈窗彈出登錄框,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-04-04
原生javascript實(shí)現(xiàn)DIV拖拽并計(jì)算重復(fù)面積
這篇文章主要介紹了使用原生javascript實(shí)現(xiàn)DIV拖拽并計(jì)算重復(fù)面積的方法及示例代碼分享,效果十分漂亮,需要的朋友可以參考下2015-01-01

