[HTML] 纯文本查看 复制代码
<!DOCTYPE html>
<html lang="zh">
<head>
<meta name="google-adsense-account" content="ca-pub-5360103615621276">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>在线照片测量 – 图像比例与尺寸工具</title>
<meta name="description" content="使用已知大小的物体作为比例,从图像中获取实际测量值。快速、简单且私密 – 您的文件在浏览器中处理。">
<meta name="keywords" content="照片测量, 图像比例, 在线尺子, 蓝图测量, 尺寸测量, 技术图纸">
<link rel="canonical" href="https://measureonimage.com/zh/">
<link rel="icon" type="image/svg+xml" href="https://measureonimage.com/logo.svg">
<link rel="apple-touch-icon" href="https://measureonimage.com/logo.svg">
<link rel="manifest" href="https://measureonimage.com/manifest.json">
<meta name="theme-color" content="#1e1e1e">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta property="og:type" content="website">
<meta property="og:url" content="https://measureonimage.com/zh/">
<meta property="og:title" content="在线照片测量 – 图像比例与尺寸工具">
<meta property="og:description" content="使用已知大小的物体作为比例,从图像中获取实际测量值。快速、简单且私密 – 您的文件在浏览器中处理。">
<meta property="og:image" content="https://measureonimage.com/social-share.jpg">
<meta property="twitter:card" content="summary_large_image">
<meta property="twitter:url" content="https://measureonimage.com/zh/">
<meta property="twitter:title" content="在线照片测量 – 图像比例与尺寸工具">
<meta property="twitter:description" content="使用已知大小的物体作为比例,从图像中获取实际测量值。快速、简单且私密 – 您的文件在浏览器中处理。">
<meta property="twitter:image" content="https://measureonimage.com/social-share.jpg">
<link rel="alternate" hreflang="en" href="https://measureonimage.com/" />
<link rel="alternate" hreflang="x-default" href="https://measureonimage.com/" />
<link rel="alternate" hreflang="es" href="https://measureonimage.com/es/" />
<link rel="alternate" hreflang="fr" href="https://measureonimage.com/fr/" />
<link rel="alternate" hreflang="de" href="https://measureonimage.com/de/" />
<link rel="alternate" hreflang="pt" href="https://measureonimage.com/pt/" />
<link rel="alternate" hreflang="it" href="https://measureonimage.com/it/" />
<link rel="alternate" hreflang="zh" href="https://measureonimage.com/zh/" />
<link rel="alternate" hreflang="lt" href="https://measureonimage.com/lt/" />
<link rel="alternate" hreflang="pl" href="https://measureonimage.com/pl/" />
<link rel="alternate" hreflang="ru" href="https://measureonimage.com/ru/" />
<link rel="alternate" hreflang="ja" href="https://measureonimage.com/ja/" />
<link rel="alternate" hreflang="ar" href="https://measureonimage.com/ar/" />
<link rel="alternate" hreflang="hi" href="https://measureonimage.com/hi/" />
<link rel="alternate" hreflang="ko" href="https://measureonimage.com/ko/" />
<link rel="alternate" hreflang="vi" href="https://measureonimage.com/vi/" />
<link rel="alternate" hreflang="tr" href="https://measureonimage.com/tr/" />
<link rel="alternate" hreflang="nl" href="https://measureonimage.com/nl/" />
<link rel="alternate" hreflang="sv" href="https://measureonimage.com/sv/" />
<link rel="alternate" hreflang="id" href="https://measureonimage.com/id/" />
<link rel="alternate" hreflang="th" href="https://measureonimage.com/th/" />
<link rel="alternate" hreflang="ro" href="https://measureonimage.com/ro/" />
<link rel="alternate" hreflang="hu" href="https://measureonimage.com/hu/" />
<link rel="alternate" hreflang="fi" href="https://measureonimage.com/fi/" />
<link rel="alternate" hreflang="bn" href="https://measureonimage.com/bn/" />
<link rel="alternate" hreflang="ur" href="https://measureonimage.com/ur/" />
<link rel="alternate" hreflang="fa" href="https://measureonimage.com/fa/" />
<link rel="alternate" hreflang="sw" href="https://measureonimage.com/sw/" />
<link rel="alternate" hreflang="uk" href="https://measureonimage.com/uk/" />
<link rel="alternate" hreflang="cs" href="https://measureonimage.com/cs/" />
<link rel="alternate" hreflang="el" href="https://measureonimage.com/el/" />
<link rel="alternate" hreflang="da" href="https://measureonimage.com/da/" />
<link rel="alternate" hreflang="no" href="https://measureonimage.com/no/" />
<link rel="alternate" hreflang="bg" href="https://measureonimage.com/bg/" />
<link rel="alternate" hreflang="mr" href="https://measureonimage.com/mr/" />
<link rel="alternate" hreflang="te" href="https://measureonimage.com/te/" />
<link rel="alternate" hreflang="ta" href="https://measureonimage.com/ta/" />
<link rel="alternate" hreflang="tl" href="https://measureonimage.com/tl/" />
<link rel="alternate" hreflang="jv" href="https://measureonimage.com/jv/" />
<link rel="alternate" hreflang="pa" href="https://measureonimage.com/pa/" />
<link rel="alternate" hreflang="kn" href="https://measureonimage.com/kn/" />
<link rel="alternate" hreflang="gu" href="https://measureonimage.com/gu/" />
<link rel="alternate" hreflang="my" href="https://measureonimage.com/my/" />
<link rel="alternate" hreflang="ha" href="https://measureonimage.com/ha/" />
<link rel="alternate" hreflang="eo" href="https://measureonimage.com/eo/" />
<base href="https://measureonimage.com/">
<script async src="https://www.googletagmanager.com/gtag/js?id=G-ZQP523QG3F" type="4ec5c5409a4e93a82f20310d-text/javascript"></script>
<script type="4ec5c5409a4e93a82f20310d-text/javascript">
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-ZQP523QG3F');
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js" type="4ec5c5409a4e93a82f20310d-text/javascript"></script>
<style>
:root {
--bg-color: #121212;
--panel-bg: #1e1e1e;
--item-bg: #2d2d2d;
--accent: #007aff;
--success: #28cd41;
--danger: #ff3b30;
--text: #ffffff;
--text-dim: #a0a0a0;
--warning: #f1c40f;
--border: #444;
}
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: var(--bg-color);
color: var(--text);
margin: 0;
}
#site-header {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 60%, #0f3460 100%);
border-bottom: 1px solid #007aff44;
padding: 16px 20px;
margin-bottom: 10px;
}
#site-header-inner {
max-width: 1400px;
margin: 0 auto;
display: flex;
align-items: center;
gap: 24px;
}
#header-text { flex: 1; min-width: 0; text-align: center}
#site-header h1 {
font-size: 1.2rem;
font-weight: 700;
color: #ffffff;
margin: 0 0 5px;
line-height: 1.3;
}
#site-header p {
font-size: 0.82rem;
color: #a0b8d0;
margin: 0 0 8px;
line-height: 1.5;
}
#header-badges { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center;}
#header-badges span {
background: rgba(0,122,255,0.15);
border: 1px solid rgba(0,122,255,0.35);
color: #80b8ff;
font-size: 11px;
font-weight: 600;
padding: 3px 10px;
border-radius: 20px;
white-space: nowrap;
}
#header-ad {
flex-shrink: 0;
width: 728px;
min-height: 90px;
display: flex;
align-items: center;
justify-content: center;
}
[url=home.php?mod=space&uid=945662]@media[/url] (max-width: 900px) {
#header-ad { width: 100%; min-height: 60px; }
}
@media (max-width: 768px) {
#site-header { padding: 10px 14px; }
#site-header-inner { flex-direction: column; gap: 8px; align-items: flex-start; }
#site-header h1 { font-size: 1rem; }
#site-header p { font-size: 0.78rem; }
}
#main-app-container {
display: flex;
flex-direction: column;
height: calc(100vh - 112px);
height: calc(100dvh - 112px);
}
#mobile-hint {
display: none;
background: #1a2a3a;
border-bottom: 1px solid #007aff44;
color: #a0c8ff;
font-size: 12px;
padding: 7px 14px;
text-align: center;
line-height: 1.4;
margin-bottom: 20px;
}
@media (max-width: 768px) {
#mobile-hint { display: block; }
#main-app-container { height: auto; }
#canvas-container {
height: 65vw !important;
min-height: 260px !important;
max-height: 60vh !important;
flex: none !important;
}
}
.seo-h1 { position: absolute; left: -9999px; }
#toolbar {
background: var(--panel-bg); padding: 10px; border-bottom: 1px solid #333; align-items: center;
box-shadow: 0 4px 20px rgba(0,0,0,0.5); z-index: 100; flex-wrap: nowrap; flex-shrink: 0;
display: grid; grid-auto-flow: column; grid-template-rows: repeat(2, auto); gap: 5px;
overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch;
}
#toolbar::-webkit-scrollbar { height: 4px; }
#toolbar::-webkit-scrollbar-thumb { background: #555; border-radius: 4px; }
@media (min-width: 1001px) {
#toolbar { flex-wrap: wrap; overflow-x: visible; justify-content: center; height: auto; }
}
.group {
display: flex; flex-direction: column; background: var(--item-bg); padding: 8px 12px;
border-radius: 12px; gap: 6px; border: 1px solid var(--border); flex-shrink: 0;
justify-content: center; height: 73px;
}
label {
font-size: 10px; text-transform: uppercase; color: var(--text-dim); font-weight: 700;
letter-spacing: 0.5px; white-space: nowrap;
}
.row { display: flex; align-items: center; gap: 8px; }
.v-separator { width: 1px; height: 24px; background: rgba(255, 255, 255, 0.15); margin: 0 4px; }
button {
background: var(--accent); color: white; border: none; padding: 0 14px; height: 36px;
border-radius: 8px; cursor: pointer; font-weight: 600; font-size: 14px;
display: flex; align-items: center; justify-content: center; gap: 6px;
transition: transform 0.1s, filter 0.2s; white-space: nowrap;
}
button:active { transform: scale(0.96); }
button.secondary { background: #444; }
button.active { background: var(--success); box-shadow: 0 0 10px rgba(40, 205, 65, 0.4); }
button.delete-btn { background: var(--warning); color: #000; }
button.danger { background: var(--danger); }
button:disabled { opacity: 0.3; cursor: not-allowed; transform: none; }
input[type="number"], input[type="text"], select {
background: #151515; border: 1px solid #555; color: white; padding: 0 8px;
height: 36px; border-radius: 8px; font-size: 16px; outline: none;
}
#imageLoader { font-size: 12px; color: var(--text-dim); width: 180px; }
#imageLoader::-webkit-file-upload-button {
padding: 6px 12px; border-radius: 6px; border: none; background: var(--accent);
color: white; font-weight: 600; cursor: pointer; margin-right: 10px; font-size: 12px;
}
.color-wrapper {
position: relative; display: flex; align-items: center; justify-content: center;
width: 36px; height: 36px; background: #000; border: 2px solid #fff;
border-radius: 8px; overflow: hidden;
}
.color-wrapper input[type="color"] {
position: absolute; top: -50%; left: -50%; width: 200%; height: 200%;
border: none; padding: 0; margin: 0; cursor: pointer;
}
#canvas-container {
flex: 1; overflow: hidden; position: relative; background: #151515;
display: flex; align-items: center; justify-content: center;
background-image: radial-gradient(#333 1px, transparent 1px); background-size: 20px 20px;
}
#imageCanvas { position: absolute; top: 0; left: 0; touch-action: none; }
#drop-zone {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
display: flex; flex-direction: column; align-items: center; justify-content: center;
z-index: 50; pointer-events: none; transition: opacity 0.2s, background 0.2s;
}
#drop-zone.active { background: rgba(0, 122, 255, 0.2); pointer-events: all; }
.drop-box {
width: 80%; max-width: 400px; padding: 40px; border: 3px dashed rgba(255, 255, 255, 0.3);
border-radius: 20px; background: rgba(30, 30, 30, 0.7); text-align: center;
box-shadow: 0 10px 30px rgba(0,0,0,0.5); pointer-events: auto; cursor: pointer;
backdrop-filter: blur(2px);
}
.drop-box:hover { border-color: var(--accent); background: rgba(30, 30, 30, 0.9); }
.drop-box .icon { font-size: 40px; margin-bottom: 15px; }
#tempTextInput {
position: absolute; background: rgba(0, 0, 0, 0.8); color: #fff; border: 2px solid var(--accent);
padding: 8px; border-radius: 6px; z-index: 1000; min-width: 100px;
box-shadow: 0 4px 15px rgba(0,0,0,0.5); transform: translate(-50%, -100%);
}
#helpModal {
display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.8); z-index: 2000; justify-content: center; align-items: center;
padding: 20px; backdrop-filter: blur(5px);
}
.modal-content {
background: var(--panel-bg); padding: 25px; border-radius: 16px; max-width: 600px;
width: 100%; border: 1px solid #444; position: relative; box-shadow: 0 10px 40px rgba(0,0,0,0.5);
}
.close-modal {
position: absolute; top: 15px; right: 15px; background: #333; width: 30px; height: 30px;
border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer;
}
#mode-indicator {
position: absolute; top: 10px; left: 50%; transform: translateX(-50%);
background: rgba(0,0,0,0.7); color: white; padding: 6px 12px; border-radius: 20px;
font-size: 13px; pointer-events: none; z-index: 50; opacity: 0; transition: opacity 0.3s;
white-space: nowrap; border: 1px solid rgba(255,255,255,0.2);
}
#mode-indicator.visible { opacity: 1; }
@media (max-width: 600px) {
#toolbar { padding: 8px; gap: 8px; }
.group { padding: 6px 10px; }
button span { display: none; }
input[type="number"] { width: 60px !important; }
input[type="text"] { width: 60px !important; }
.drop-box {
width: auto !important; max-width: 200px !important; padding: 15px !important;
background: rgba(30, 30, 30, 0.6) !important; border: 2px dashed rgba(255,255,255,0.2) !important;
}
.drop-box .icon { font-size: 24px !important; margin-bottom: 5px !important; }
.drop-box h2 { font-size: 13px !important; margin: 0; }
.drop-box p { display: none; }
}
.seo-content {
padding: 40px 20px; max-width: 900px; margin: 0 auto; line-height: 1.8; font-size: 18px;
}
.seo-content h2 {
font-size: 2.5em; color: var(--accent); border-bottom: 2px solid var(--border);
padding-bottom: 10px; margin-top: 40px; margin-bottom: 20px;
}
.seo-content h3 { font-size: 1.8em; color: var(--text); margin-top: 30px; }
.seo-content p, .seo-content li { color: var(--text-dim); }
.seo-content ul { list-style-type: '✓ '; padding-left: 20px; }
.seo-content strong { color: var(--text); font-weight: 600; }
.ads-placeholder {
width: 100%; min-height: 100px; background: #222; border: 1px dashed var(--border);
display: flex; align-items: center; justify-content: center; margin: 40px 0; color: var(--text-dim);
}
footer {
background: var(--panel-bg); text-align: center; padding: 20px; margin-top: 40px;
border-top: 1px solid var(--border);
}
footer a { color: var(--accent); text-decoration: none; margin: 0 15px; font-size: 14px; }
footer a:hover { text-decoration: underline; }
</style>
</head>
<body>
<header id="site-header">
<div id="site-header-inner">
<div id="header-text">
<a href="https://measureonimage.com/" title="Home" style="display: inline-flex; align-items: center; gap: 10px; margin-bottom: 15px; text-decoration: none; transition: transform 0.2s;" onmouseover="if (!window.__cfRLUnblockHandlers) return false; this.style.transform='scale(1.02)'" onmouseout="if (!window.__cfRLUnblockHandlers) return false; this.style.transform='scale(1)'" data-cf-modified-4ec5c5409a4e93a82f20310d-="">
<img src="logo.svg" alt="Measure On Image Logo" style="height: 40px; width: auto;">
<span style="color: var(--accent, #007aff); font-size: 24px; font-weight: 800; letter-spacing: -0.5px;">MeasureOnImage.com</span>
</a>
<div style="height: 1px; background: var(--border, #444); width: 100%; margin-bottom: 15px; opacity: 0.5;"></div>
<h1>专业在线图像测量工具</h1>
<p>使用已知大小的物体作为比例,从图像中获取实际测量值。快速、简单且私密 – 您的文件在浏览器中处理。</p>
<div id="header-badges">
<span>✅ 免费</span><span>🔒 无需上传</span><span>📱 支持手机</span> </div>
</div>
<!--<div id="header-ad"></div>-->
</div>
</header>
<div id="main-app-container">
<h1 class="seo-h1">专业在线图像测量工具</h1>
<div id="mobile-hint">
📐 在已知距离上画一条红色比例线进行校准。 | 手机: 单指绘制。双指 移动和缩放。 </div>
<div id="toolbar">
<div class="group">
<label>📁 上传图像</label>
<input type="file" id="imageLoader" accept="image/*">
</div>
<div class="group">
<label>📐 比例与单位</label>
<div class="row">
<input type="number" id="knownValue" value="1.0" step="0.1" style="width: 70px;">
<input type="text" id="unitName" value="m" style="width: 50px;">
</div>
</div>
<div class="group">
<label>↔️ 线条样式/宽度 | 文字颜色/大小 | 类型</label>
<div class="row">
<div class="color-wrapper" title="线条颜色"><input type="color" id="lineColor" value="#000000"></div>
<select id="lineWidth" style="width: 50px;" title="线条宽度">
<option value="1">1</option>
<option value="2" selected>2</option>
<option value="4">4</option>
<option value="8">8</option>
<option value="12">12</option>
</select>
<button id="customLineBtn" class="secondary" onclick="if (!window.__cfRLUnblockHandlers) return false; toggleCustomLineMode()" title="绘制一条没有测量文本的线" data-cf-modified-4ec5c5409a4e93a82f20310d-="">
简单线条 </button>
<div class="v-separator"></div>
<div class="color-wrapper" title="线条文字颜色"><input type="color" id="lineTextColor" value="#ffffff"></div>
<select id="lineTextSize" style="width: 55px;" title="线条上方文字大小">
<option value="8">XS</option>
<option value="16">S</option>
<option value="24" selected>M</option>
<option value="36">L</option>
<option value="48">XL</option>
</select>
<div class="v-separator"></div>
<select id="capType" style="width: 50px;">
<option value="none">─</option>
<option value="arrow">↔</option>
<option value="tshape" selected>┫┣</option>
<option value="circle">●</option>
</select>
</div>
</div>
<div class="group">
<label>✍️ 添加自定义文本</label>
<div class="row">
<div class="color-wrapper" title="自定义文本颜色"><input type="color" id="textColor" value="#ffff00"></div>
<select id="textSize" style="width: 60px;" title="自定义文本大小">
<option value="10">XS</option>
<option value="20">S</option>
<option value="32" selected>M</option>
<option value="48">L</option>
<option value="72">XL</option>
</select>
<button id="addTextBtn" class="secondary" onclick="if (!window.__cfRLUnblockHandlers) return false; toggleTextMode()" data-cf-modified-4ec5c5409a4e93a82f20310d-="">添加文本</button>
</div>
</div>
<div class="group">
<label>🗑️ 删除</label>
<button id="deleteBtn" class="delete-btn" onclick="if (!window.__cfRLUnblockHandlers) return false; deleteSelected()" title="选择一个元素并按此处或 DEL 键" disabled data-cf-modified-4ec5c5409a4e93a82f20310d-="">🗑️ 删除元素</button>
</div>
<div class="group">
<label>🔍 视图</label>
<div class="row">
<button class="secondary" onclick="if (!window.__cfRLUnblockHandlers) return false; changeZoom(1.2)" data-cf-modified-4ec5c5409a4e93a82f20310d-="">+</button>
<button class="secondary" onclick="if (!window.__cfRLUnblockHandlers) return false; changeZoom(0.8)" data-cf-modified-4ec5c5409a4e93a82f20310d-="">-</button>
<button class="secondary" onclick="if (!window.__cfRLUnblockHandlers) return false; setZoomOriginal()" title="重置图像为原始大小 (1:1)" data-cf-modified-4ec5c5409a4e93a82f20310d-="">1:1</button>
<button onclick="if (!window.__cfRLUnblockHandlers) return false; fitToScreen()" data-cf-modified-4ec5c5409a4e93a82f20310d-="">Fit</button>
</div>
</div>
<div class="group">
<label>↩️ 历史</label>
<div class="row">
<button id="undoBtn" class="secondary" onclick="if (!window.__cfRLUnblockHandlers) return false; performUndo()" disabled data-cf-modified-4ec5c5409a4e93a82f20310d-="">↩</button>
<button id="redoBtn" class="secondary" onclick="if (!window.__cfRLUnblockHandlers) return false; performRedo()" disabled data-cf-modified-4ec5c5409a4e93a82f20310d-="">↪</button>
<button class="danger" onclick="if (!window.__cfRLUnblockHandlers) return false; resetAll()" data-cf-modified-4ec5c5409a4e93a82f20310d-=""> 重置 </button>
</div>
</div>
<div class="group">
<label>💾 导出</label>
<div class="row">
<button class="success" onclick="if (!window.__cfRLUnblockHandlers) return false; exportFile('pdf')" data-cf-modified-4ec5c5409a4e93a82f20310d-="">PDF</button>
<button class="success" onclick="if (!window.__cfRLUnblockHandlers) return false; exportFile('png')" data-cf-modified-4ec5c5409a4e93a82f20310d-="">PNG</button>
<button class="success" onclick="if (!window.__cfRLUnblockHandlers) return false; exportFile('jpg')" data-cf-modified-4ec5c5409a4e93a82f20310d-="">JPG</button>
</div>
</div>
<div class="group">
<label>语言</label>
<select onchange="if (!window.__cfRLUnblockHandlers) return false; location.href='https://measureonimage.com/' + (this.value === 'en' ? '' : this.value + '/')" style="min-width: 80px; padding: 5px;" data-cf-modified-4ec5c5409a4e93a82f20310d-="">
<option value="en" >English</option>
<option value="es" >Español</option>
<option value="fr" >Français</option>
<option value="de" >Deutsch</option>
<option value="pt" >Português</option>
<option value="it" >Italiano</option>
<option value="zh" selected>中文 (Chinese)</option>
<option value="lt" >Lietuvių</option>
<option value="pl" >Polski</option>
<option value="ru" >Русский</option>
<option value="ja" >日本語</option>
<option value="ar" >العربية</option>
<option value="hi" >हिन्दी</option>
<option value="ko" >한국어</option>
<option value="vi" >Tiếng Việt</option>
<option value="tr" >Türkçe</option>
<option value="nl" >Nederlands</option>
<option value="sv" >Svenska</option>
<option value="id" >Bahasa Indonesia</option>
<option value="th" >ไทย</option>
<option value="ro" >Română</option>
<option value="hu" >Magyar</option>
<option value="fi" >Suomi</option>
<option value="bn" >বাংলা</option>
<option value="ur" >اردو</option>
<option value="fa" >فارسی</option>
<option value="sw" >Kiswahili</option>
<option value="uk" >Українська</option>
<option value="cs" >Čeština</option>
<option value="el" >Ελληνικά</option>
<option value="da" >Dansk</option>
<option value="no" >Norsk</option>
<option value="bg" >Български</option>
<option value="mr" >मराठी</option>
<option value="te" >తెలుగు</option>
<option value="ta" >தமிழ்</option>
<option value="tl" >Filipino</option>
<option value="jv" >Basa Jawa</option>
<option value="pa" >ਪੰਜਾਬੀ</option>
<option value="kn" >ಕನ್ನಡ</option>
<option value="gu" >ગુજરાતી</option>
<option value="my" >မြန်မာ</option>
<option value="ha" >Hausa</option>
<option value="eo" >Esperanto</option>
</select>
</div>
<div class="group">
<label>帮助</label>
<button id="helpBtn" class="secondary" onclick="if (!window.__cfRLUnblockHandlers) return false; toggleHelp()" data-cf-modified-4ec5c5409a4e93a82f20310d-="">?</button>
</div>
</div>
<div id="canvas-container">
<div id="mode-indicator">1 Finger: Draw • 2 Fingers: Pan/Zoom</div>
<div id="drop-zone">
<div class="drop-box" onclick="if (!window.__cfRLUnblockHandlers) return false; document.getElementById('imageLoader').click()" data-cf-modified-4ec5c5409a4e93a82f20310d-="">
<div class="icon">📁</div>
<h2>上传您的图像</h2>
<p>将文件拖到此处或使用上面的按钮</p>
</div>
</div>
<canvas id="imageCanvas"></canvas>
</div>
<div id="helpModal">
<div class="modal-content">
<div class="close-modal" onclick="if (!window.__cfRLUnblockHandlers) return false; toggleHelp()" data-cf-modified-4ec5c5409a4e93a82f20310d-="">✕</div>
<h2>📐 使用说明</h2>
<ul style="line-height: 1.6; padding-left: 20px;">
<li>在已知距离上画一条红色比例线进行校准。</li>
<li>在物体上画其他线条 - 系统将计算长度。</li>
<li>双击任何标签以编辑其值。</li>
<li>选择一个元素并按 DEL 键将其删除。</li>
<div style="text-align: center">- - - - - - - - - -</div>
<li>桌面: 左键绘制。空格 + 拖动 (或右键) 移动。</li>
<li>手机: 单指绘制。双指 移动和缩放。</li>
</ul>
</div>
</div>
</div>
<script type="4ec5c5409a4e93a82f20310d-text/javascript">
const translations = {
svgTitle: "图像测量",
svgSub: "(上传蓝图、平面图或照片)",
svgInstr: "开始使用:",
instr1: "在已知距离上画一条红色比例线进行校准。",
instr2: "在物体上画其他线条 - 系统将计算长度。",
instr3: "双击任何标签以编辑其值。",
instr4: "选择一个元素并按 DEL 键将其删除。",
instr5: "桌面: 左键绘制。空格 + 拖动 (或右键) 移动。",
instr6: "手机: 单指绘制。双指 移动和缩放。",
};
const canvas = document.getElementById('imageCanvas');
const ctx = canvas.getContext('2d', { alpha: false });
const container = document.getElementById('canvas-container');
const dropZone = document.getElementById('drop-zone');
const modeIndicator = document.getElementById('mode-indicator');
let img = new Image();
let lines = [];
let customTexts = [];
let selectedElement = null;
let undoStack = [], redoStack = [];
let scale = 1, offsetX = 0, offsetY = 0;
let isDrawing = false, isPanning = false, textMode = false, spacePressed = false;
let customLineMode = false;
let pointers = [];
let prevDiff = -1;
let lastCenter = null;
let draggedPoint = null, draggedText = null, draggedWholeLine = null;
let pointerStartX, pointerStartY, originalElementState = null;
const SNAP_THRESHOLD = 20;
const HIT_RADIUS = 20;
document.addEventListener('DOMContentLoaded', () => {
resizeCanvas();
loadInitialSVG();
initHistory();
window.addEventListener('resize', () => { resizeCanvas(); requestRedraw(); });
});
function loadInitialSVG() {
const svgString = `<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="800" viewBox="0 0 1000 800"><rect width="100%" height="100%" fill="#f8f9fa"/><defs><pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse"><path d="M 40 0 L 0 0 0 40" fill="none" stroke="#e0e4e8" stroke-width="1"/></pattern></defs><rect width="100%" height="100%" fill="url(#grid)" /><rect x="50" y="50" width="900" height="700" fill="none" stroke="#cfd8dc" stroke-width="2" rx="10"/><path d="M200 200 h600 v300 h-600 z M200 350 h250 M450 200 v300" fill="none" stroke="#546e7a" stroke-width="3" stroke-linejoin="round"/><text x="500" y="100" font-family="Segoe UI, Arial" font-size="28" font-weight="600" text-anchor="middle" fill="#2c3e50">${translations.svgTitle}</text><path d="M205 205 l15 15 M795 205 l-15 15" stroke="#d32f2f" stroke-width="2" stroke-linecap="round"/><text x="500" y="130" font-family="Arial" font-size="14" text-anchor="middle" fill="#90a4ae">${translations.svgSub}</text><g transform="translate(100, 540)"><rect width="800" height="180" fill="white" fill-opacity="0.6" rx="8" stroke="#eceff1"/><text x="400" y="30" font-family="Segoe UI, Arial" font-size="18" font-weight="bold" text-anchor="middle" fill="#455a64">${translations.svgInstr}</text><text x="400" y="60" font-family="Segoe UI, Arial" font-size="15" text-anchor="middle" fill="#d32f2f">1. ${translations.instr1}</text><text x="400" y="80" font-family="Segoe UI, Arial" font-size="15" text-anchor="middle" fill="#546e7a">2. ${translations.instr2}</text><text x="400" y="110" font-family="Segoe UI, Arial" font-size="12" text-anchor="middle" fill="#78909c" font-style="italic">3. ${translations.instr3}</text><text x="400" y="125" font-family="Segoe UI, Arial" font-size="12" text-anchor="middle" fill="#78909c" font-style="italic">4. ${translations.instr4}</text><text x="400" y="140" font-family="Segoe UI, Arial" font-size="12" text-anchor="middle" fill="#78909c" font-style="italic">5. ${translations.instr5}</text><text x="400" y="155" font-family="Segoe UI, Arial" font-size="12" text-anchor="middle" fill="#78909c" font-style="italic">6. ${translations.instr6}</text></g></svg>`;
loadSource("data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svgString))), true, false);
}
canvas.addEventListener('pointerdown', handlePointerDown);
canvas.addEventListener('pointermove', handlePointerMove);
canvas.addEventListener('pointerup', handlePointerUp);
canvas.addEventListener('pointercancel', handlePointerUp);
canvas.addEventListener('pointerout', handlePointerUp);
window.addEventListener('keydown', (e) => {
if (e.code === 'Space') {
spacePressed = true;
container.style.cursor = 'grab';
e.preventDefault();
}
if (e.key === 'Delete' || e.key === 'Backspace') deleteSelected();
if ((e.ctrlKey || e.metaKey) && e.key === 'z') performUndo();
if ((e.ctrlKey || e.metaKey) && e.key === 'y') performRedo();
});
window.addEventListener('keyup', (e) => {
if (e.code === 'Space') {
spacePressed = false;
container.style.cursor = textMode ? 'text' : 'crosshair';
}
});
container.addEventListener('wheel', (e) => {
e.preventDefault();
const zoomIntensity = 0.1;
const delta = e.deltaY > 0 ? (1 - zoomIntensity) : (1 + zoomIntensity);
zoomToPoint(e.clientX, e.clientY, delta);
}, { passive: false });
document.getElementById('imageLoader').onchange = (e) => handleFile(e.target.files[0]);
window.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('active');
});
window.addEventListener('dragleave', (e) => {
e.preventDefault();
if (e.relatedTarget === null) dropZone.classList.remove('active');
});
window.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('active');
if (e.dataTransfer.files[0]) handleFile(e.dataTransfer.files[0]);
});
function handlePointerDown(e) {
canvas.setPointerCapture(e.pointerId);
pointers.push(e);
if (pointers.length === 2) {
showModeIndicator(true);
if (isDrawing) { lines.pop(); isDrawing = false; }
draggedPoint = null; draggedText = null; draggedWholeLine = null;
const p1 = pointers[0]; const p2 = pointers[1];
prevDiff = Math.hypot(p1.clientX - p2.clientX, p1.clientY - p2.clientY);
lastCenter = { x: (p1.clientX + p2.clientX)/2, y: (p1.clientY + p2.clientY)/2 };
return;
}
const pt = getCanvasCoordinates(e.clientX, e.clientY);
if (pointers.length === 1) {
if (spacePressed || e.button === 2) {
isPanning = true;
container.style.cursor = 'grabbing';
return;
}
if (textMode) { createTextInput(e.clientX, e.clientY, pt.x, pt.y); return; }
for (let i = customTexts.length - 1; i >= 0; i--) {
if (isHitText(customTexts[i], pt.x, pt.y)) {
selectedElement = { type: 'text', index: i }; draggedText = i;
pointerStartX = pt.x; pointerStartY = pt.y;
requestRedraw(); updateUI(); return;
}
}
for (let i = 0; i < lines.length; i++) {
if (dist(lines[i].x1, lines[i].y1, pt.x, pt.y) < HIT_RADIUS / scale) { draggedPoint = { idx: i, type: 'p1' }; return; }
if (dist(lines[i].x2, lines[i].y2, pt.x, pt.y) < HIT_RADIUS / scale) { draggedPoint = { idx: i, type: 'p2' }; return; }
}
for (let i = 0; i < lines.length; i++) {
const center = { x: (lines[i].x1 + lines[i].x2)/2, y: (lines[i].y1 + lines[i].y2)/2 };
if (dist(center.x, center.y, pt.x, pt.y) < HIT_RADIUS / scale) {
selectedElement = { type: 'line', index: i }; draggedWholeLine = i;
pointerStartX = pt.x; pointerStartY = pt.y; originalElementState = { ...lines[i] };
requestRedraw(); updateUI(); return;
}
}
selectedElement = null; updateUI();
const snap = getSnappedPos(pt, -1);
isDrawing = true;
lines.push({
x1: snap.x, y1: snap.y, x2: snap.x, y2: snap.y,
lColor: document.getElementById('lineColor').value,
lWidth: parseInt(document.getElementById('lineWidth').value),
tColor: document.getElementById('lineTextColor').value,
tSize: parseInt(document.getElementById('lineTextSize').value),
cap: document.getElementById('capType').value,
isCustom: customLineMode
});
requestRedraw();
}
}
function handlePointerMove(e) {
const index = pointers.findIndex(p => p.pointerId === e.pointerId);
if (index !== -1) pointers[index] = e;
if (pointers.length === 2) {
const p1 = pointers[0]; const p2 = pointers[1];
const curCenter = { x: (p1.clientX + p2.clientX)/2, y: (p1.clientY + p2.clientY)/2 };
const curDiff = Math.hypot(p1.clientX - p2.clientX, p1.clientY - p2.clientY);
if (prevDiff > 0 && lastCenter) {
const dx = curCenter.x - lastCenter.x;
const dy = curCenter.y - lastCenter.y;
offsetX += dx; offsetY += dy;
const zoomFactor = curDiff / prevDiff;
const oldScale = scale;
scale *= zoomFactor;
if (scale < 0.05) scale = 0.05; if (scale > 50) scale = 50;
offsetX = curCenter.x - (curCenter.x - offsetX) * (scale / oldScale);
offsetY = curCenter.y - (curCenter.y - offsetY) * (scale / oldScale);
requestRedraw();
}
prevDiff = curDiff; lastCenter = curCenter;
return;
}
if (pointers.length === 1) {
const pt = getCanvasCoordinates(e.clientX, e.clientY);
if (isPanning) { offsetX += e.movementX; offsetY += e.movementY; requestRedraw(); return; }
if (isDrawing) {
const snap = getSnappedPos(pt, lines.length - 1);
lines[lines.length - 1].x2 = snap.x; lines[lines.length - 1].y2 = snap.y;
requestRedraw();
} else if (draggedPoint) {
const snap = getSnappedPos(pt, draggedPoint.idx);
if (draggedPoint.type === 'p1') { lines[draggedPoint.idx].x1 = snap.x; lines[draggedPoint.idx].y1 = snap.y; }
else { lines[draggedPoint.idx].x2 = snap.x; lines[draggedPoint.idx].y2 = snap.y; }
requestRedraw();
} else if (draggedText !== null) {
customTexts[draggedText].x = pt.x; customTexts[draggedText].y = pt.y;
requestRedraw();
} else if (draggedWholeLine !== null) {
const baseDx = pt.x - pointerStartX; const baseDy = pt.y - pointerStartY;
lines[draggedWholeLine].x1 = originalElementState.x1 + baseDx; lines[draggedWholeLine].y1 = originalElementState.y1 + baseDy;
lines[draggedWholeLine].x2 = originalElementState.x2 + baseDx; lines[draggedWholeLine].y2 = originalElementState.y2 + baseDy;
requestRedraw();
}
}
}
function handlePointerUp(e) {
pointers = pointers.filter(p => p.pointerId !== e.pointerId);
if (pointers.length < 2) { prevDiff = -1; lastCenter = null; showModeIndicator(false); }
if (isPanning && pointers.length === 0) {
isPanning = false;
container.style.cursor = spacePressed ? 'grab' : (textMode ? 'text' : 'crosshair');
}
if (isDrawing) {
const l = lines[lines.length - 1];
if (Math.hypot(l.x2 - l.x1, l.y2 - l.y1) < 5 / scale) { lines.pop(); } else { saveState(); }
} else if (draggedPoint || draggedText !== null || draggedWholeLine !== null) {
saveState();
}
isDrawing = false; draggedPoint = null; draggedText = null; draggedWholeLine = null; originalElementState = null;
requestRedraw();
}
function getCanvasCoordinates(clientX, clientY) {
const rect = canvas.getBoundingClientRect();
return { x: (clientX - rect.left - offsetX) / scale, y: (clientY - rect.top - offsetY) / scale };
}
function zoomToPoint(cx, cy, factor) {
const oldScale = scale;
scale *= factor;
if (scale < 0.05) scale = 0.05; if (scale > 50) scale = 50;
offsetX = cx - (cx - offsetX) * (scale / oldScale);
offsetY = cy - (cy - offsetY) * (scale / oldScale);
requestRedraw();
}
function changeZoom(factor) {
const rect = container.getBoundingClientRect();
zoomToPoint(rect.left + rect.width/2, rect.top + rect.height/2, factor);
}
function fitToScreen() {
if (!img.complete || img.naturalWidth === 0) return;
const padding = 20;
const scaleX = (canvas.width - padding*2) / img.naturalWidth;
const scaleY = (canvas.height - padding*2) / img.naturalHeight;
scale = Math.min(scaleX, scaleY);
offsetX = (canvas.width - img.naturalWidth * scale) / 2;
offsetY = (canvas.height - img.naturalHeight * scale) / 2;
requestRedraw();
}
function setZoomOriginal() {
scale = 1;
offsetX = (canvas.width - img.naturalWidth) / 2;
offsetY = (canvas.height - img.naturalHeight) / 2;
requestRedraw();
}
function getSnappedPos(pos, currentLineIdx) {
let bestDist = SNAP_THRESHOLD / scale;
let bestPos = pos;
lines.forEach((l, i) => {
if (i === currentLineIdx) return;
const d1 = dist(l.x1, l.y1, pos.x, pos.y); const d2 = dist(l.x2, l.y2, pos.x, pos.y);
if (d1 < bestDist) { bestDist = d1; bestPos = {x: l.x1, y: l.y1}; }
else if (d2 < bestDist) { bestDist = d2; bestPos = {x: l.x2, y: l.y2}; }
});
return bestPos;
}
function isHitText(t, x, y) {
const w = t.text.length * (t.size * 0.6); const h = t.size;
return (x > t.x - w/2 && x < t.x + w/2 && y > t.y - h && y < t.y + h/2);
}
function dist(x1, y1, x2, y2) { return Math.hypot(x2 - x1, y2 - y1); }
let animationFrameId;
function requestRedraw() { if (!animationFrameId) { animationFrameId = requestAnimationFrame(() => { draw(); animationFrameId = null; }); } }
function draw() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (!img.complete || img.naturalWidth === 0) return;
ctx.translate(offsetX, offsetY); ctx.scale(scale, scale); ctx.drawImage(img, 0, 0);
let pixelsPerUnit = 0;
if (lines[0]) {
const pxLen = dist(lines[0].x1, lines[0].y1, lines[0].x2, lines[0].y2);
if (pxLen > 0) pixelsPerUnit = pxLen / parseFloat(document.getElementById('knownValue').value);
}
const unitName = document.getElementById('unitName').value;
lines.forEach((l, i) => {
const isRef = (i === 0);
const isSel = (selectedElement && selectedElement.type === 'line' && selectedElement.index === i);
ctx.beginPath(); ctx.lineWidth = l.lWidth;
ctx.strokeStyle = isRef ? "#ff3b30" : (isSel ? "#f1c40f" : l.lColor);
ctx.moveTo(l.x1, l.y1); ctx.lineTo(l.x2, l.y2); ctx.stroke();
const angle = Math.atan2(l.y2 - l.y1, l.x2 - l.x1);
drawCap(ctx, l.x1, l.y1, angle + Math.PI, l.cap, ctx.strokeStyle, l.lWidth);
drawCap(ctx, l.x2, l.y2, angle, l.cap, ctx.strokeStyle, l.lWidth);
if (!l.isCustom || isRef) {
let valText = isRef ? parseFloat(document.getElementById('knownValue').value).toString() : (dist(l.x1,l.y1,l.x2,l.y2)/pixelsPerUnit).toFixed(2);
drawTextWithBg(valText + " " + unitName, (l.x1 + l.x2)/2, (l.y1 + l.y2)/2 - 10/scale, l.tSize, l.tColor, isSel);
}
});
customTexts.forEach((t, i) => {
const isSel = (selectedElement && selectedElement.type === 'text' && selectedElement.index === i);
drawTextWithBg(t.text, t.x, t.y, t.size, isSel ? "#f1c40f" : t.color, isSel);
});
}
function drawCap(ctx, x, y, angle, type, color, w) {
if (type === 'none') return;
ctx.save(); ctx.translate(x, y); ctx.rotate(angle);
ctx.fillStyle = color; ctx.strokeStyle = color; ctx.lineWidth = w;
ctx.beginPath();
if (type === 'arrow') { const s = w * 3; ctx.moveTo(0, 0); ctx.lineTo(-s, -s/2); ctx.lineTo(-s, s/2); ctx.fill(); }
else if (type === 'tshape') { const s = w * 4; ctx.moveTo(0, -s/2); ctx.lineTo(0, s/2); ctx.stroke(); }
else if (type === 'circle') { ctx.arc(0, 0, w * 1.5, 0, Math.PI * 2); ctx.fill(); }
ctx.restore();
}
function drawTextWithBg(txt, x, y, size, color, isSel) {
ctx.save();
ctx.font = `bold ${size}px sans-serif`; ctx.textAlign = "center"; ctx.textBaseline = "middle";
ctx.strokeStyle = "rgba(0,0,0,0.8)"; ctx.lineWidth = size / 8; ctx.strokeText(txt, x, y);
ctx.fillStyle = color; ctx.fillText(txt, x, y);
ctx.restore();
}
function handleFile(file) {
const reader = new FileReader();
reader.onload = (ev) => loadSource(ev.target.result, true, true);
reader.readAsDataURL(file);
}
function loadSource(src, resetZoom, hideDropZone) {
img = new Image();
img.onload = () => {
if (hideDropZone) dropZone.style.display = 'none';
if (resetZoom) {
lines = [];
customTexts = [];
fitToScreen();
initHistory();
}
requestRedraw();
};
img.src = src;
}
function resizeCanvas() { canvas.width = container.clientWidth; canvas.height = container.clientHeight; requestRedraw(); }
function toggleTextMode() { textMode = !textMode; customLineMode = false; document.getElementById('addTextBtn').classList.toggle('active', textMode); document.getElementById('customLineBtn').classList.remove('active'); }
function toggleCustomLineMode() { customLineMode = !customLineMode; textMode = false; document.getElementById('customLineBtn').classList.toggle('active', customLineMode); document.getElementById('addTextBtn').classList.remove('active'); }
function deleteSelected() { if (!selectedElement) return; if (selectedElement.type === 'line') lines.splice(selectedElement.index, 1); else customTexts.splice(selectedElement.index, 1); selectedElement = null; saveState(); updateUI(); requestRedraw(); }
function resetAll() {
if (confirm("Clear all?")) {
lines = [];
customTexts = [];
selectedElement = null;
saveState();
requestRedraw();
}
}
function createTextInput(screenX, screenY, canvasX, canvasY) {
spawnInput(screenX, screenY, "", (val) => {
if (val) { customTexts.push({ x: canvasX, y: canvasY, text: val, color: document.getElementById('textColor').value, size: parseInt(document.getElementById('textSize').value) }); saveState(); }
});
}
function spawnInput(x, y, initialVal, callback) {
const input = document.createElement('input');
input.id = 'tempTextInput'; input.type = 'text'; input.value = initialVal;
input.style.left = x + 'px'; input.style.top = y + 'px';
document.body.appendChild(input);
setTimeout(() => { input.focus(); }, 50);
const finish = () => { callback(input.value.trim()); if (input.parentNode) input.parentNode.removeChild(input); textMode = false; document.getElementById('addTextBtn').classList.remove('active'); requestRedraw(); };
input.addEventListener('keydown', (e) => { if (e.key === 'Enter') finish(); });
input.addEventListener('blur', finish);
}
// --- UNDO / REDO ---
function initHistory() {
undoStack = [JSON.stringify({ l: [], t: [] })];
redoStack = [];
updateUI();
}
function saveState() {
redoStack = [];
undoStack.push(JSON.stringify({ l: lines, t: customTexts }));
updateUI();
}
function performUndo() {
if (undoStack.length <= 1) return;
const currentState = undoStack.pop();
redoStack.push(currentState);
const prevState = JSON.parse(undoStack[undoStack.length - 1]);
lines = prevState.l.map(obj => ({...obj}));
customTexts = prevState.t.map(obj => ({...obj}));
selectedElement = null;
requestRedraw();
updateUI();
}
function performRedo() {
if (redoStack.length === 0) return;
const nextStateStr = redoStack.pop();
const nextState = JSON.parse(nextStateStr);
undoStack.push(nextStateStr);
lines = nextState.l.map(obj => ({...obj}));
customTexts = nextState.t.map(obj => ({...obj}));
selectedElement = null;
requestRedraw();
updateUI();
}
function updateUI() {
document.getElementById('deleteBtn').disabled = !selectedElement;
document.getElementById('undoBtn').disabled = undoStack.length <= 1;
document.getElementById('redoBtn').disabled = redoStack.length === 0;
}
// --- UNDO / REDO ---
function showModeIndicator(show) { modeIndicator.classList.toggle('visible', show); }
function toggleHelp() { document.getElementById('helpModal').style.display = (document.getElementById('helpModal').style.display === 'flex') ? 'none' : 'flex'; }
function exportFile(type) {
if (!img.complete || img.naturalWidth === 0) return;
const tempCanvas = document.createElement('canvas'); tempCanvas.width = img.naturalWidth; tempCanvas.height = img.naturalHeight; const tCtx = tempCanvas.getContext('2d'); tCtx.drawImage(img, 0, 0);
let pixelsPerUnit = 0; if (lines[0]) { pixelsPerUnit = dist(lines[0].x1, lines[0].y1, lines[0].x2, lines[0].y2) / parseFloat(document.getElementById('knownValue').value); }
const unitName = document.getElementById('unitName').value;
lines.forEach((l, i) => {
tCtx.lineWidth = l.lWidth; tCtx.strokeStyle = (i===0) ? "#ff3b30" : l.lColor;
tCtx.beginPath(); tCtx.moveTo(l.x1, l.y1); tCtx.lineTo(l.x2, l.y2); tCtx.stroke();
if (!l.isCustom || i===0) {
let txt = (i===0) ? document.getElementById('knownValue').value : (dist(l.x1,l.y1,l.x2,l.y2)/pixelsPerUnit).toFixed(2);
tCtx.font = `bold ${l.tSize}px sans-serif`; tCtx.textAlign = "center"; tCtx.fillStyle = l.tColor;
tCtx.strokeText(txt+" "+unitName, (l.x1+l.x2)/2, (l.y1+l.y2)/2-10); tCtx.fillText(txt+" "+unitName, (l.x1+l.x2)/2, (l.y1+l.y2)/2-10);
}
});
customTexts.forEach(t => { tCtx.font = `bold ${t.size}px sans-serif`; tCtx.textAlign = "center"; tCtx.fillStyle = t.color; tCtx.strokeText(t.text, t.x, t.y); tCtx.fillText(t.text, t.x, t.y); });
const link = document.createElement('a'); link.download = 'measurement.'+type; link.href = tempCanvas.toDataURL('image/'+(type==='png'?'png':'jpeg')); link.click();
}
</script>
<style>
.blog-teasers {
max-width: 900px;
margin: 40px auto 0;
padding: 0 20px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.blog-teasers h2 {
font-size: 1.6rem;
color: #ffffff;
border-bottom: 2px solid var(--accent, #007aff);
padding-bottom: 10px;
margin-bottom: 20px;
}
.teaser-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
}
.teaser-card {
background: var(--item-bg, #2d2d2d);
border: 1px solid var(--border, #444);
border-radius: 12px;
padding: 20px;
text-decoration: none;
transition: transform 0.2s, border-color 0.2s;
display: flex;
flex-direction: column;
}
.teaser-card:hover {
transform: translateY(-5px);
border-color: var(--accent, #007aff);
}
.teaser-category {
font-size: 0.8rem;
color: var(--accent, #007aff);
text-transform: uppercase;
font-weight: 700;
margin-bottom: 8px;
}
.teaser-title {
color: #ffffff;
font-size: 1.15rem;
font-weight: 600;
margin: 0 0 10px 0;
line-height: 1.4;
}
.teaser-desc {
color: #a0a0a0;
font-size: 0.9rem;
line-height: 1.5;
margin: 0;
flex-grow: 1;
}
.read-more {
margin-top: 15px;
font-size: 0.9rem;
color: #fff;
font-weight: 600;
}
</style>
<div class="blog-teasers">
<h2>Guides & Tutorials</h2>
<div class="teaser-grid">
<a href="https://measureonimage.com/blog#photo-measurement" class="teaser-card">
<span class="teaser-category">Tutorial</span>
<h3 class="teaser-title">Measuring from Photos</h3>
<p class="teaser-desc">Learn how to accurately extract real-world dimensions and room sizes using just a single digital photograph.</p>
<span class="read-more">Read article →</span>
</a>
<a href="https://measureonimage.com/blog#blueprint-guide" class="teaser-card">
<span class="teaser-category">Guide</span>
<h3 class="teaser-title">Reading Blueprints</h3>
<p class="teaser-desc">A beginner's guide to understanding architect floor plans, symbols, and how to scale them digitally.</p>
<span class="read-more">Read article →</span>
</a>
<a href="https://measureonimage.com/blog#diy-renovation" class="teaser-card">
<span class="teaser-category">DIY & Planning</span>
<h3 class="teaser-title">Home Renovation Tips</h3>
<p class="teaser-desc">Top planning strategies to save money and time during your next DIY home improvement project.</p>
<span class="read-more">Read article →</span>
</a>
</div>
</div>
<style>
.content-sections {
max-width: 900px;
margin: 0 auto;
padding: 40px 20px 20px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
color: #e0e0e0;
direction: ltr;
}
.content-sections h2 {
font-size: 1.6rem;
color: #ffffff;
border-bottom: 2px solid #007aff;
padding-bottom: 10px;
margin-top: 50px;
margin-bottom: 20px;
}
.content-sections h3 {
font-size: 1.1rem;
color: #ffffff;
margin-bottom: 6px;
}
.content-sections p {
line-height: 1.7;
color: #c0c0c0;
margin-bottom: 14px;
}
.how-steps {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 20px;
}
.how-step {
background: #1e1e1e;
border: 1px solid #333;
border-radius: 10px;
padding: 16px;
}
.how-step h3 {
color: #007aff;
font-size: 0.95rem;
margin-bottom: 8px;
}
.how-step p {
font-size: 0.9rem;
margin: 0;
}
.faq-item {
background: #1e1e1e;
border: 1px solid #333;
border-radius: 10px;
padding: 16px 20px;
margin-bottom: 12px;
}
.faq-item h3 {
font-size: 1rem;
color: #ffffff;
margin-bottom: 8px;
}
.faq-item p {
font-size: 0.9rem;
margin: 0;
color: #aaa;
}
.usecases-list {
list-style: none;
padding: 0;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 10px;
margin-bottom: 20px;
}
.usecases-list li {
background: #1e1e1e;
border: 1px solid #333;
border-radius: 8px;
padding: 12px 16px;
font-size: 0.9rem;
color: #c0c0c0;
line-height: 1.5;
}
.usecases-list li::before {
content: "✓ ";
color: #28cd41;
font-weight: bold;
}
@media (max-width: 600px) {
.how-steps, .usecases-list {
grid-template-columns: 1fr;
}
.content-sections h2 {
font-size: 1.3rem;
}
}
</style>
<div class="content-sections">
<h2>关于 MeasureOnImage.com</h2>
<p>MeasureOnImage.com 是一款免费的、基于浏览器的工具,可直接在任何图像上测量距离和尺寸——平面图、蓝图、地图、技术图纸或照片。无需安装,无需上传至服务器。</p>
<h2>使用方法</h2>
<div class="how-steps">
<div class="how-step">
<h3>1. 上传图像</h3>
<p>点击"上传图像"或将照片、平面图或地图拖放到画布上。支持 JPG、PNG 格式。</p>
</div>
<div class="how-step">
<h3>2. 绘制比例线</h3>
<p>在已知尺寸的对象上绘制一条红色比例线——例如门(2米)、桌子(1.5米)或尺子。输入已知值和单位。</p>
</div>
<div class="how-step">
<h3>3. 测量任何对象</h3>
<p>在需要测量的对象上绘制其他线条,系统将自动根据比例计算实际长度。</p>
</div>
<div class="how-step">
<h3>4. 导出结果</h3>
<p>将标注图像下载为 PNG、JPG 或 PDF。</p>
</div>
</div>
<h2>常见使用场景</h2>
<ul class="usecases-list">
<li>平面图测量 — 从房产或建筑图纸估算房间尺寸。</li>
<li>蓝图缩放 — 验证建筑或工程图纸上的尺寸。</li>
<li>地图测距 — 根据已知比例计算地图上的实际距离。</li>
<li>家具规划 — 从照片测量家具和空间。</li>
<li>产品尺寸估计 — 从目录图像确定大致的产品尺寸。</li>
<li>土地分析 — 从卫星或地籍地图图像测量地块面积。</li>
</ul>
<h2>常见问题</h2>
<div class="faq-item">
<h3>MeasureOnImage.com 是免费的吗?</h3>
<p>是的,该工具完全免费,无需注册。</p>
</div>
<div class="faq-item">
<h3>我的图像会上传到服务器吗?</h3>
<p>不会。图像完全在您的浏览器中处理,永远不会离开您的设备。</p>
</div>
<div class="faq-item">
<h3>可以测量哪些类型的图像?</h3>
<p>平面图、蓝图、地图、建筑图纸、产品照片等。</p>
</div>
<div class="faq-item">
<h3>测量有多准确?</h3>
<p>准确度取决于您设置的比例参考。如果您使用已知对象设置比例,测量结果将按比例准确。</p>
</div>
<div class="faq-item">
<h3>该工具支持手机使用吗?</h3>
<p>支持。单指绘制线条,双指移动和缩放。</p>
</div>
<div class="faq-item">
<h3>可以使用哪些单位?</h3>
<p>米、厘米、毫米、英寸、英尺或任何自定义单位。</p>
</div>
<div class="faq-item">
<h3>我可以保存测量结果吗?</h3>
<p>您可以将标注后的图像导出为 PNG、JPG 或 PDF 以保存您的工作。</p>
</div>
<!-- FAQ JSON-LD Schema for Google -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "MeasureOnImage.com \u662f\u514d\u8d39\u7684\u5417\uff1f",
"acceptedAnswer": {
"@type": "Answer",
"text": "\u662f\u7684\uff0c\u8be5\u5de5\u5177\u5b8c\u5168\u514d\u8d39\uff0c\u65e0\u9700\u6ce8\u518c\u3002" }
}, {
"@type": "Question",
"name": "\u6211\u7684\u56fe\u50cf\u4f1a\u4e0a\u4f20\u5230\u670d\u52a1\u5668\u5417\uff1f",
"acceptedAnswer": {
"@type": "Answer",
"text": "\u4e0d\u4f1a\u3002\u56fe\u50cf\u5b8c\u5168\u5728\u60a8\u7684\u6d4f\u89c8\u5668\u4e2d\u5904\u7406\uff0c\u6c38\u8fdc\u4e0d\u4f1a\u79bb\u5f00\u60a8\u7684\u8bbe\u5907\u3002" }
}, {
"@type": "Question",
"name": "\u53ef\u4ee5\u6d4b\u91cf\u54ea\u4e9b\u7c7b\u578b\u7684\u56fe\u50cf\uff1f",
"acceptedAnswer": {
"@type": "Answer",
"text": "\u5e73\u9762\u56fe\u3001\u84dd\u56fe\u3001\u5730\u56fe\u3001\u5efa\u7b51\u56fe\u7eb8\u3001\u4ea7\u54c1\u7167\u7247\u7b49\u3002" }
}, {
"@type": "Question",
"name": "\u6d4b\u91cf\u6709\u591a\u51c6\u786e\uff1f",
"acceptedAnswer": {
"@type": "Answer",
"text": "\u51c6\u786e\u5ea6\u53d6\u51b3\u4e8e\u60a8\u8bbe\u7f6e\u7684\u6bd4\u4f8b\u53c2\u8003\u3002\u5982\u679c\u60a8\u4f7f\u7528\u5df2\u77e5\u5bf9\u8c61\u8bbe\u7f6e\u6bd4\u4f8b\uff0c\u6d4b\u91cf\u7ed3\u679c\u5c06\u6309\u6bd4\u4f8b\u51c6\u786e\u3002" }
}, {
"@type": "Question",
"name": "\u8be5\u5de5\u5177\u652f\u6301\u624b\u673a\u4f7f\u7528\u5417\uff1f",
"acceptedAnswer": {
"@type": "Answer",
"text": "\u652f\u6301\u3002\u5355\u6307\u7ed8\u5236\u7ebf\u6761\uff0c\u53cc\u6307\u79fb\u52a8\u548c\u7f29\u653e\u3002" }
}, {
"@type": "Question",
"name": "\u53ef\u4ee5\u4f7f\u7528\u54ea\u4e9b\u5355\u4f4d\uff1f",
"acceptedAnswer": {
"@type": "Answer",
"text": "\u7c73\u3001\u5398\u7c73\u3001\u6beb\u7c73\u3001\u82f1\u5bf8\u3001\u82f1\u5c3a\u6216\u4efb\u4f55\u81ea\u5b9a\u4e49\u5355\u4f4d\u3002" }
}, {
"@type": "Question",
"name": "\u6211\u53ef\u4ee5\u4fdd\u5b58\u6d4b\u91cf\u7ed3\u679c\u5417\uff1f",
"acceptedAnswer": {
"@type": "Answer",
"text": "\u60a8\u53ef\u4ee5\u5c06\u6807\u6ce8\u540e\u7684\u56fe\u50cf\u5bfc\u51fa\u4e3a PNG\u3001JPG \u6216 PDF \u4ee5\u4fdd\u5b58\u60a8\u7684\u5de5\u4f5c\u3002" }
} ]
}
</script>
</div>
<style>
.site-footer {
background-color: var(--panel-bg, #1e1e1e);
border-top: 1px solid var(--border, #444);
padding: 30px 20px;
text-align: center;
margin-top: auto; /* Prispaudžia footerį prie apačios, jei puslapis trumpas */
width: 100%;
box-sizing: border-box;
}
.site-footer .footer-links {
display: flex;
flex-wrap: wrap; /* Leidžia nuorodoms gražiai persikelti į kitą eilutę mobiliuosiuose */
justify-content: center;
align-items: center;
gap: 15px 25px; /* Tarpai: 15px vertikaliai, 25px horizontaliai */
margin-bottom: 15px;
}
.site-footer a {
color: var(--text-dim, #a0a0a0);
text-decoration: none;
font-size: 14px;
font-weight: 500;
transition: color 0.2s ease;
white-space: nowrap; /* Neleidžia vienos nuorodos žodžiams lūžti per dvi eilutes */
}
.site-footer a:hover {
color: var(--accent, #007aff);
}
.site-footer p {
color: var(--text-dim, #a0a0a0);
font-size: 12px;
margin: 0;
opacity: 0.7;
}
</style>
<footer class="site-footer">
<div class="footer-links">
<a href="/zh/privacy-policy">隐私政策</a>
<a href="/zh/terms-of-service">服务条款</a>
<a href="/zh/about-us">关于我们</a>
<a href="/zh/contact">联系我们</a>
<a href="/zh/blog">Blog & Guides</a>
</div>
<p>© 2026 MeasureOnImage.com. All rights reserved.</p>
</footer>
<script src="/cdn-cgi/scripts/7d0fa10a/cloudflare-static/rocket-loader.min.js" data-cf-settings="4ec5c5409a4e93a82f20310d-|49" defer></script><script defer src="https://static.cloudflareinsights.com/beacon.min.js/v8c78df7c7c0f484497ecbca7046644da1771523124516" integrity="sha512-8DS7rgIrAmghBFwoOTujcf6D9rXvH8xm8JQ1Ja01h9QX8EzXldiszufYa4IFfKdLUKTTrnSFXLDkUEOTrZQ8Qg==" data-cf-beacon='{"version":"2024.11.0","token":"2e3197faebd747898c47de4a68adef7a","r":1,"server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body>
</html>