<!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: Georgia, 'Times New Roman', serif;
line-height: 1.6;
color: #333;
background-color: #fff;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.container {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 40px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
max-width: 800px;
width: 100%;
}
h1 {
text-align: center;
color: #333;
margin-bottom: 20px;
font-size: 2em;
}
.author-link {
text-align: center;
margin-bottom: 25px;
}
.author-link a {
color: #667eea;
text-decoration: none;
font-size: 0.9em;
padding: 6px 12px;
border: 1px solid #667eea;
border-radius: 20px;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 5px;
}
.author-link a:hover {
background: #667eea;
color: white;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
}
.ip-list {
display: grid;
gap: 15px;
margin-bottom: 30px;
}
.ip-item {
background: #f8f9fa;
padding: 20px;
border-radius: 12px;
display: flex;
justify-content: space-between;
align-items: center;
transition: all 0.3s ease;
border: 2px solid transparent;
}
.ip-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.ip-item.fastest {
background: #f0f8ff;
border-color: #0000EE;
}
@keyframes glow {
0%, 100% {
box-shadow: 0 0 5px rgba(76, 175, 80, 0.5);
}
50% {
box-shadow: 0 0 20px rgba(76, 175, 80, 0.8);
}
}
.ip-address {
font-weight: 600;
color: #333;
font-size: 1.1em;
font-family: 'Courier New', monospace;
}
.latency {
display: flex;
align-items: center;
gap: 10px;
}
.latency-value {
font-weight: bold;
font-size: 1.2em;
min-width: 100px;
text-align: right;
}
.latency-good {
color: #006633;
}
.latency-medium {
color: #FF9800;
}
.latency-bad {
color: #f44336;
}
.latency-testing {
color: #2196F3;
}
.latency-error {
color: #9e9e9e;
}
.status {
width: 12px;
height: 12px;
border-radius: 50%;
}
.status-good {
background: #4CAF50;
animation: pulse 2s infinite;
}
.status-medium {
background: #FF9800;
animation: pulse 2s infinite;
}
.status-bad {
background: #f44336;
animation: pulse 2s infinite;
}
.status-testing {
background: #2196F3;
animation: spin 1s linear infinite;
}
.status-error {
background: #9e9e9e;
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.best-ip-section {
background: #f9f9f9;
border: 1px solid #ddd;
padding: 25px;
border-radius: 12px;
text-align: center;
}
.best-ip-label {
color: #333;
font-size: 1.1em;
margin-bottom: 15px;
font-weight: 500;
}
.best-ip-box {
background: white;
padding: 20px;
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
transition: all 0.3s ease;
min-width: 350px;
}
.best-ip-box.disabled {
opacity: 0.6;
}
.best-ip-value {
font-size: 1.2em;
font-weight: bold;
color: #11276e;
font-family: 'Courier New', monospace;
word-break: break-all;
text-align: center;
}
.copy-icon {
color: #11276e;
font-size: 1.5em;
}
.copied-toast {
position: fixed;
top: 20px;
right: 20px;
background: #f0f0f0;
color: #333;
padding: 15px 25px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
display: none;
animation: slideIn 0.3s ease;
z-index: 1000;
}
.copied-toast.show {
display: block;
}
@keyframes slideIn {
from {
transform: translateX(400px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.test-button {
width: 100%;
padding: 15px;
background: #f0f0f0;
color: #333;
border: 2px solid #333;
border-radius: 8px;
font-size: 1.1em;
font-weight: 600;
cursor: pointer;
margin-bottom: 20px;
transition: all 0.3s ease;
}
.test-button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(17, 39, 110, 0.2);
}
.test-button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.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 linear infinite;
margin-right: 10px;
}
.info-text {
text-align: center;
color: #666;
font-size: 0.9em;
margin-top: 15px;
}
.update-info {
text-align: center;
color: #999;
font-size: 0.85em;
margin-bottom: 20px;
padding: 8px;
background: rgba(0, 0, 0, 0.05);
border-radius: 6px;
}
.usage-guide {
background: #f9f9f9;
border: 1px solid #ddd;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
}
.usage-guide h3 {
color: #333;
margin-bottom: 15px;
font-size: 1.1em;
display: flex;
align-items: center;
gap: 8px;
}
.usage-steps {
color: #555;
font-size: 0.9em;
line-height: 1.6;
}
.usage-steps ol {
margin-left: 20px;
}
.usage-steps li {
margin-bottom: 8px;
}
.usage-steps code {
background: #f5f5f5;
padding: 2px 6px;
border-radius: 3px;
font-family: 'Courier New', monospace;
color: #333;
}
.copy-button {
background: #f0f0f0;
color: #333;
border: 2px solid #333;
padding: 12px 20px;
border-radius: 6px;
font-size: 1em;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
margin-left: 15px;
}
.copy-button:hover:not(:disabled) {
background: #45a049;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(76, 175, 80, 0.3);
}
.copy-button:disabled {
background: #ccc;
cursor: not-allowed;
transform: none;
}
.copy-hint {
color: white;
font-size: 0.85em;
margin-top: 10px;
font-style: italic;
}
.https-warning {
background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%);
border: none;
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
color: white;
text-align: center;
}
.https-warning.error-mode {
background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%);
}
.https-warning h3 {
margin: 0 0 15px 0;
font-size: 1.2em;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.https-warning p {
margin: 10px 0;
line-height: 1.5;
font-size: 0.95em;
}
.switch-to-http-btn {
background: white;
color: #ff6b6b;
border: none;
padding: 12px 24px;
border-radius: 8px;
font-size: 1em;
font-weight: 600;
cursor: pointer;
margin-top: 10px;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 8px;
}
.switch-to-http-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.limited-functionality {
opacity: 0.6;
pointer-events: none;
}
</style>
</head>
<body>
<div class="container">
<h1>🌐 IP延迟检测器</h1>
<div class="author-link">
<a href="https://xiangyu.cv" target="_blank" rel="noopener noreferrer">
<span>✨</span>
<span>访问我的个人主页</span>
<span>→</span>
</a>
</div>
<!-- HTTPS警告框 - 默认隐藏 -->
<div class="https-warning" id="httpsWarning" style="display: none;">
<h3>⚠️ 安全模式限制</h3>
<p>当前您通过HTTPS安全协议访问本页面,由于浏览器安全策略,无法进行HTTP直连测试。</p>
<p>为了获得最佳的IP延迟测试体验,建议切换到HTTP版本。</p>
<button class="switch-to-http-btn" onclick="switchToHttpVersion()">
<span>🚀</span>
<span>切换到HTTP版本</span>
</button>
</div>
<button class="test-button" id="testBtn" onclick="testLatency()">
<span id="btnText">开始测试延迟</span>
</button>
<div class="update-info" id="updateInfo" style="display: none;">
IP列表更新于 <span id="updateTime"></span>
</div>
<div class="ip-list" id="ipList"></div>
<div class="best-ip-section">
<div class="best-ip-label">✨ 最佳GitHub IP地址(延迟最低)</div>
<div style="display: flex; align-items: center; justify-content: center;">
<div class="best-ip-box" id="bestIpBox">
<div class="best-ip-value" id="bestIp">等待测试出最佳GitHub hosts条目...</div>
</div>
<button class="copy-button" onclick="copyToClipboard()" id="copyBtn" disabled>
<span>📋</span>
<span>复制hosts条目</span>
</button>
</div>
<div class="copy-hint">复制内容格式:IP地址 github.com</div>
</div>
<div class="usage-guide">
<h3>📖 使用说明</h3>
<div class="usage-steps">
<ol>
<li>点击上方"复制hosts条目"按钮,复制最快的GitHub IP</li>
<li>打开hosts文件:
<br><code>Windows: C:\Windows\System32\drivers\etc\hosts</code>
<br><code>Mac/Linux: /etc/hosts</code>
</li>
<li>将复制的内容粘贴到hosts文件末尾</li>
<li>保存文件即可享受更快的GitHub访问速度</li>
</ol>
</div>
</div>
</div>
<div class="copied-toast" id="toast">✅ 已复制到剪贴板!</div>
<script>
// IP地址列表 - 从远程URL获取
let ipAddresses = [];
let latencyResults = {};
let isTesting = false;
let ipListLoaded = false;
let ipListUpdateTime = null;
// JSONP回调函数 - 支持两种名称
function ipCallback(data) {
handleJSONPResponse(data);
}
function callback(data) {
handleJSONPResponse(data);
}
// 统一处理JSONP响应
function handleJSONPResponse(data) {
const btnText = document.getElementById('btnText');
console.log('JSONP响应数据:', data);
try {
if (data.status === 'success' && data.ips && Array.isArray(data.ips)) {
// 过滤出有效的IP地址
ipAddresses = data.ips.filter(ip =>
typeof ip === 'string' && /^(\d{1,3}\.){3}\d{1,3}$/.test(ip.trim())
);
// 处理更新时间
if (data.updated) {
ipListUpdateTime = data.updated;
displayUpdateTime();
}
console.log(`成功获取 ${ipAddresses.length} 个IP地址,更新时间: ${data.updated}`);
} else {
throw new Error('数据格式错误');
}
} catch (error) {
console.error('JSONP数据解析失败:', error);
useDefaultIPList();
}
ipListLoaded = true;
// 立即显示所有IP为"测试中"状态
displayAllIPs();
btnText.textContent = '开始测试延迟';
}
// 显示更新时间
function displayUpdateTime() {
if (!ipListUpdateTime) return;
const updateInfo = document.getElementById('updateInfo');
const updateTimeElement = document.getElementById('updateTime');
try {
// 尝试解析ISO时间格式
const date = new Date(ipListUpdateTime);
if (!isNaN(date.getTime())) {
// 格式化为本地时间
updateTimeElement.textContent = date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
updateInfo.style.display = 'block';
} else {
// 如果不是有效时间格式,直接显示原始字符串
updateTimeElement.textContent = ipListUpdateTime;
updateInfo.style.display = 'block';
}
} catch (error) {
updateTimeElement.textContent = ipListUpdateTime;
updateInfo.style.display = 'block';
}
}
// 检测是否为本地文件协议
function isLocalFile() {
return window.location.protocol === 'file:';
}
// 使用默认IP列表
function useDefaultIPList() {
console.log('使用默认IP列表');
ipAddresses = [
'140.82.116.4',
'140.82.121.3',
'140.82.121.4',
'20.205.243.166',
'20.26.156.215',
'20.27.177.113'
];
// 隐藏更新时间信息
document.getElementById('updateInfo').style.display = 'none';
// 立即显示所有IP为"测试中"状态
if (ipListLoaded) {
displayAllIPs();
}
}
// 使用JSONP获取IP列表
function fetchIPList() {
const btnText = document.getElementById('btnText');
btnText.innerHTML = '<span class="loading-spinner"></span>获取IP列表...';
// 先尝试不带参数的请求
tryJSONPRequest('https://xiangyu.cv/github_hosts.txt');
// 如果3秒内没有成功,再尝试带callback参数的请求
setTimeout(() => {
if (!ipListLoaded) {
console.log('尝试带callback参数的JSONP请求');
const timestamp = Date.now();
const random = Math.random().toString(36).substring(7);
tryJSONPRequest(`https://xiangyu.cv/github_hosts.txt?callback=callback&t=${timestamp}&r=${random}`);
}
}, 3000);
// 总超时时间
setTimeout(() => {
if (!ipListLoaded) {
console.error('所有JSONP请求都超时,使用默认IP列表');
useDefaultIPList();
ipListLoaded = true;
displayAllIPs();
btnText.textContent = '开始测试延迟';
}
}, 10000);
}
// 尝试JSONP请求的通用函数
function tryJSONPRequest(url) {
// 清理之前的JSONP脚本
const existingScript = document.getElementById('jsonp-script');
if (existingScript) {
existingScript.remove();
}
// 创建JSONP脚本
const script = document.createElement('script');
script.id = 'jsonp-script';
script.src = url;
console.log('发起JSONP请求:', url);
script.onerror = function() {
console.error('JSONP脚本加载失败:', url);
// 不立即使用默认列表,让其他尝试继续
};
script.onload = function() {
console.log('JSONP脚本加载完成:', url);
// 如果3秒内还没有收到回调,可能回调函数名不匹配
setTimeout(() => {
if (!ipListLoaded) {
console.warn('JSONP脚本已加载但未收到回调,可能是回调函数名不匹配');
}
}, 3000);
};
document.head.appendChild(script);
}
// 显示所有IP(初始状态)- 保持原始顺序
function displayAllIPs() {
const ipList = document.getElementById('ipList');
ipList.innerHTML = '';
// 按原始顺序显示所有IP
ipAddresses.forEach(ip => {
const ipItem = document.createElement('div');
ipItem.className = 'ip-item';
ipItem.setAttribute('data-ip', ip);
ipItem.innerHTML = `
<div class="ip-address">${ip}</div>
<div class="latency">
<div class="latency-value latency-testing">测试中...</div>
<div class="status status-testing"></div>
</div>
`;
ipList.appendChild(ipItem);
});
}
// 测试单个IP的延迟 - 类似 nc -w5 -z -vv IP 80
async function testSingleIP(ip) {
const testCount = 3; // 测试3次取平均值
let successfulTests = [];
// 第一次测试时立即显示第一次结果
let firstResultShown = false;
for (let i = 0; i < testCount; i++) {
try {
const startTime = performance.now();
// 使用Image对象来测试连接,避免CORS和HTTPS跳转问题
const testConnection = new Promise((resolve, reject) => {
const img = new Image();
// 任何响应(包括404、302等)都算连接成功
img.onload = () => resolve(true);
img.onerror = () => resolve(true); // 即使图片加载失败,说明服务器有响应
// 超时控制
const timeout = setTimeout(() => {
img.src = ''; // 停止加载
reject(new Error('timeout'));
}, 5000);
// 清理函数
const cleanup = () => clearTimeout(timeout);
img.onload = () => { cleanup(); resolve(true); };
img.onerror = () => { cleanup(); resolve(true); };
// 发起请求,添加随机参数避免缓存
img.src = `http://${ip}:80/?t=${Date.now()}&r=${Math.random()}`;
});
await testConnection;
const endTime = performance.now();
const latency = Math.round(endTime - startTime);
successfulTests.push(latency);
// 如果是第一次成功的测试,立即显示结果
if (!firstResultShown && successfulTests.length > 0) {
firstResultShown = true;
updateIPItem(ip, latency);
// 立即更新最佳IP(可能是最快的)
updateBestIP();
}
} catch (error) {
// 超时或其他错误,不计入成功测试
console.log(`IP ${ip} 第${i + 1}次测试失败`);
}
// 每次测试间隔100ms
if (i < testCount - 1) {
await new Promise(resolve => setTimeout(resolve, 100));
}
}
// 所有测试完成后,返回最终结果
if (successfulTests.length > 0) {
const finalLatency = Math.round(successfulTests.reduce((a, b) => a + b) / successfulTests.length);
return finalLatency;
}
return null; // 所有测试都失败(无法连接)
}
function getLatencyClass(latency) {
if (latency === null) return 'error';
if (latency < 100) return 'good';
if (latency < 300) return 'medium';
return 'bad';
}
function updateIPItem(ip, latency) {
const existingItem = document.querySelector(`[data-ip="${ip}"]`);
if (!existingItem) return;
const latencyClass = getLatencyClass(latency);
const latencyText = latency === null ? '超时' : latency === 'testing' ? '测试中...' : `${latency}ms`;
// 只更新延迟显示,不重新创建元素,保持原始顺序
const latencyElement = existingItem.querySelector('.latency');
if (latencyElement) {
latencyElement.innerHTML = `
<div class="latency-value latency-${latencyClass}">${latencyText}</div>
<div class="status status-${latencyClass}"></div>
`;
}
}
function updateBestIP() {
// 找出所有已完成的测试结果
const validResults = Object.entries(latencyResults).filter(([_, latency]) =>
latency !== null && latency !== 'testing' && typeof latency === 'number'
);
if (validResults.length === 0) {
return;
}
// 找出延迟最低的IP
const sortedResults = validResults.sort((a, b) => a[1] - b[1]);
const fastestIP = sortedResults[0][0];
const fastestLatency = sortedResults[0][1];
// 更新最佳IP显示,显示完整的hosts格式
document.getElementById('bestIp').textContent = `${fastestIP} github.com`;
document.getElementById('bestIpBox').classList.remove('disabled');
document.getElementById('copyBtn').disabled = false;
// 移除所有 fastest 样式
document.querySelectorAll('.ip-item.fastest').forEach(item => {
item.classList.remove('fastest');
});
// 给最快IP添加 fastest 样式
const fastestItem = document.querySelector(`[data-ip="${fastestIP}"]`);
if (fastestItem) {
fastestItem.classList.add('fastest');
}
console.log(`当前最快IP: ${fastestIP} (${fastestLatency}ms)`);
}
async function testLatency() {
if (isTesting || !ipListLoaded) return;
isTesting = true;
const testBtn = document.getElementById('testBtn');
const btnText = document.getElementById('btnText');
testBtn.disabled = true;
btnText.innerHTML = '<span class="loading-spinner"></span>测试中...';
latencyResults = {};
document.getElementById('bestIp').textContent = '正在测试最佳GitHub hosts条目...';
document.getElementById('bestIpBox').classList.add('disabled');
document.getElementById('copyBtn').disabled = true;
// 确保所有IP都显示在列表中
displayAllIPs();
// 设置所有IP为测试中状态
ipAddresses.forEach(ip => {
latencyResults[ip] = 'testing';
updateIPItem(ip, 'testing');
});
// 并发测试所有IP
const testPromises = ipAddresses.map(async ip => {
const latency = await testSingleIP(ip);
latencyResults[ip] = latency;
// 更新最终结果
updateIPItem(ip, latency);
// 实时更新最佳IP
updateBestIP();
});
await Promise.all(testPromises);
// 所有测试完成后,最后更新一次最佳IP
updateBestIP();
testBtn.disabled = false;
btnText.textContent = '重新测试延迟';
isTesting = false;
}
function copyToClipboard() {
const bestIpContent = document.getElementById('bestIp').textContent;
if (bestIpContent.includes('等待测试') || bestIpContent.includes('正在测试') || bestIpContent === '无可用IP') {
return;
}
// 直接复制显示的内容(已经是hosts格式)
const contentToCopy = bestIpContent;
// 使用现代的 Clipboard API
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(contentToCopy).then(function() {
showSuccessMessage();
}).catch(function(err) {
// 如果 Clipboard API 失败,使用传统方法
fallbackCopy(contentToCopy);
});
} else {
// 浏览器不支持 Clipboard API,使用传统方法
fallbackCopy(contentToCopy);
}
}
function fallbackCopy(text) {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.left = '-9999px';
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
showSuccessMessage();
} catch (err) {
alert('复制失败,请手动复制以下内容:\n' + text);
}
document.body.removeChild(textArea);
}
function showSuccessMessage() {
const toast = document.getElementById('toast');
toast.textContent = '✅ 已复制hosts条目到剪贴板!';
toast.classList.add('show');
// 3秒后隐藏成功消息
setTimeout(() => {
toast.classList.remove('show');
toast.textContent = '✅ 已复制到剪贴板!'; // 恢复原始文本
}, 3000);
}
// 检测当前协议并显示相应提示
function detectProtocolAndShowWarning() {
const isHttps = window.location.protocol === 'https:';
const isLocal = isLocalFile();
const httpsWarning = document.getElementById('httpsWarning');
const testBtn = document.getElementById('testBtn');
const ipList = document.getElementById('ipList');
const bestIpSection = document.querySelector('.best-ip-section');
const usageGuide = document.querySelector('.usage-guide');
if (isHttps) {
// HTTPS模式:显示无法工作的提示
httpsWarning.style.display = 'block';
httpsWarning.classList.add('error-mode');
httpsWarning.querySelector('h3').innerHTML = '❌ 无法正常工作';
httpsWarning.querySelector('p:nth-child(2)').textContent = '当前您通过HTTPS安全协议访问本页面,由于浏览器安全策略阻止HTTP请求,IP延迟测试功能无法正常工作。';
httpsWarning.querySelector('p:nth-child(3)').textContent = '请使用HTTP协议访问本页面,或下载到本地HTML文件使用。';
// 修改按钮为禁用状态
const switchBtn = httpsWarning.querySelector('.switch-to-http-btn');
if (switchBtn) {
switchBtn.innerHTML = '<span>🚫</span><span>HTTPS模式不支持</span>';
switchBtn.disabled = true;
switchBtn.style.opacity = '0.5';
switchBtn.style.cursor = 'not-allowed';
}
testBtn.classList.add('limited-functionality');
testBtn.disabled = true;
document.getElementById('btnText').textContent = '无法测试';
if (ipList) ipList.classList.add('limited-functionality');
if (bestIpSection) bestIpSection.classList.add('limited-functionality');
if (usageGuide) usageGuide.classList.add('limited-functionality');
console.log('检测到HTTPS访问,显示无法工作提示');
} else if (isLocal) {
// 本地文件模式:显示本地提示,但启用功能
httpsWarning.style.display = 'block';
httpsWarning.querySelector('h3').innerHTML = '🏠 本地文件模式';
httpsWarning.querySelector('p:nth-child(2)').textContent = '当前您正在本地打开本页面,某些功能可能受限。建议部署到Web服务器或使用本地HTTP服务器。';
httpsWarning.querySelector('p:nth-child(3)').textContent = 'IP测试功能可以正常使用,但无法获取远程IP列表更新。';
// 隐藏切换按钮,改为提示信息
const switchBtn = httpsWarning.querySelector('.switch-to-http-btn');
if (switchBtn) {
switchBtn.style.display = 'none';
}
// 启用测试功能
testBtn.classList.remove('limited-functionality');
testBtn.disabled = false;
document.getElementById('btnText').textContent = '开始测试延迟';
if (ipList) ipList.classList.remove('limited-functionality');
if (bestIpSection) bestIpSection.classList.remove('limited-functionality');
if (usageGuide) usageGuide.classList.remove('limited-functionality');
console.log('检测到本地文件访问,启用本地模式');
} else {
// HTTP模式:正常功能
httpsWarning.style.display = 'none';
testBtn.classList.remove('limited-functionality');
testBtn.disabled = false;
document.getElementById('btnText').textContent = '开始测试延迟';
if (ipList) ipList.classList.remove('limited-functionality');
if (bestIpSection) bestIpSection.classList.remove('limited-functionality');
if (usageGuide) usageGuide.classList.remove('limited-functionality');
console.log('检测到HTTP访问,启用完整功能');
}
}
// 切换到HTTP版本
function switchToHttpVersion() {
const currentUrl = window.location.href;
const httpUrl = currentUrl.replace('https://', 'http://');
// 在当前窗口打开HTTP版本
window.open(httpUrl, '_self');
}
// 页面加载时先检测协议,然后获取IP列表,最后开始测试
window.onload = async () => {
// 检测协议并显示相应提示
detectProtocolAndShowWarning();
const isLocal = isLocalFile();
const isHttp = window.location.protocol === 'http:';
// HTTP模式或本地模式:执行测试流程
if (isHttp || isLocal) {
if (isLocal) {
// 本地模式:直接使用默认IP列表,跳过JSONP请求
console.log('本地文件模式:跳过JSONP请求,直接使用默认IP列表');
ipListLoaded = true;
useDefaultIPList();
setTimeout(() => {
testLatency();
}, 500);
} else {
// HTTP模式:正常获取IP列表
await fetchIPList();
setTimeout(() => {
testLatency();
}, 500);
}
}
};
</script>
</body>
</html>
版权属于:
Curry
作品采用:
《
署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)
》许可协议授权
评论 (0)