- // ==UserScript==
- // @name 移动端网络监测套件(极简版)
- // @namespace http://tampermonkey.net/
- // @version 4.0.0
- // @description 轻量级网络监测工具
- // @license MIT
- // @match http://*/*
- // @match https://*/*
- // @run-at document-start
- // @grant none
- // ==/UserScript==
-
- (function() {
- 'use strict';
-
- const createShadowContainer = () => {
- const host = document.createElement('div');
- const shadowRoot = host.attachShadow({ mode: 'closed' });
- document.documentElement.appendChild(host);
-
- const container = document.createElement("div");
- Object.assign(container.style, {
- position: "fixed",
- right: "10px",
- bottom: "10px",
- display: "flex",
- gap: "8px",
- zIndex: "999998",
- pointerEvents: "none",
- flexWrap: "wrap"
- });
-
- shadowRoot.appendChild(container);
- return container;
- };
-
- const container = createShadowContainer();
- let resourceObserver, updateTimer, scrollTimer;
-
- const panelStyle = {
- padding: "5px 10px",
- background: "rgba(40, 40, 40, 0.9)",
- color: "#f0f0f0",
- borderRadius: "8px",
- fontSize: "12px",
- fontFamily: "system-ui, sans-serif",
- backdropFilter: "blur(4px)",
- boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
- whiteSpace: "nowrap",
- transition: "opacity 0.2s",
- flexShrink: 0
- };
-
- const [trafficPanel, latencyPanel] = ['流量: 0 B', '延迟: ...'].map(text => {
- const panel = document.createElement("div");
- panel.textContent = text;
- Object.assign(panel.style, panelStyle);
- return panel;
- });
- container.append(trafficPanel, latencyPanel);
-
- let totalBytes = 0;
- const formatSize = bytes => {
- if (bytes < 1024) return `${bytes} B`;
- return bytes < 1048576 ?
- `${(bytes/1024).toFixed(1)} KB` :
- `${(bytes/1048576).toFixed(2)} MB`;
- };
-
- const initResourceObserver = () => {
- resourceObserver = new PerformanceObserver(list => {
- list.getEntries().forEach(entry => {
- const size = entry.transferSize || entry.decodedBodySize || 0;
- if (size > 0) totalBytes += size;
- });
- trafficPanel.textContent = `流量: ${formatSize(totalBytes)}`;
- });
- resourceObserver.observe({ type: 'resource', buffered: true });
- };
-
- const getLatencyColor = latency => {
- if (latency < 100) return '#4caf50';
- if (latency < 300) return '#ffb300';
- return latency < 500 ? '#ff9800' : '#f44336';
- };
-
- const measureLatency = () => {
- const controller = new AbortController();
- const timeoutId = setTimeout(() => controller.abort(), 5000);
- const start = performance.now();
-
- fetch(`${location.origin}/?t=${start}`, {
- method: 'HEAD',
- mode: 'no-cors',
- cache: 'no-store',
- signal: controller.signal
- }).then(() => {
- const latency = performance.now() - start;
- latencyPanel.textContent = `延迟: ${latency.toFixed(1)}ms`;
- latencyPanel.style.color = getLatencyColor(latency);
- }).catch(e => {
- latencyPanel.textContent = e.name === 'AbortError' ? '延迟: 超时' : '延迟: 断开';
- latencyPanel.style.color = '#f44336';
- }).finally(() => clearTimeout(timeoutId));
- };
-
- const syncUpdate = () => {
- measureLatency();
- updateTimer = setTimeout(syncUpdate, 2000);
- };
-
- const handleVisibilityChange = () => {
- clearTimeout(updateTimer);
- if (document.hidden) {
- resourceObserver?.disconnect();
- } else {
- initResourceObserver();
- syncUpdate();
- }
- };
-
- window.addEventListener('scroll', () => {
- container.style.opacity = '0.7';
- clearTimeout(scrollTimer);
- scrollTimer = setTimeout(() => container.style.opacity = '1', 800);
- }, { passive: true });
-
- document.addEventListener('visibilitychange', handleVisibilityChange);
- window.addEventListener('beforeunload', () => {
- resourceObserver?.disconnect();
- clearTimeout(updateTimer);
- clearTimeout(scrollTimer);
- });
-
- initResourceObserver();
- syncUpdate();
- })();