DarthSoluke commited on
Commit
5a92a29
·
verified ·
1 Parent(s): b7a040d

切换图片和标注时显示叠加图加载状态

Browse files

Show loading beside the annotation preview until both current images finish loading. Hide stale annotations during loading and keep the latest selection when switching quickly. Publish source, built page, documentation, and checksums together.

Files changed (4) hide show
  1. README.md +1 -1
  2. deployment_manifest.json +6 -6
  3. index.html +26 -7
  4. src/page.html +26 -7
README.md CHANGED
@@ -25,7 +25,7 @@ pinned: false
25
  - `data/videos.json`:视频与标注来源,区分官方标注和预测。
26
  - `data/preview_sources.json`:素材仓库、固定版本与来源清单。
27
 
28
- 查看器支持样例与标注切换、透明度、同步放大和手部区域定位。每张图保留出处,原作者图使用本地无损 WebP。
29
 
30
  ## 页面结构
31
 
 
25
  - `data/videos.json`:视频与标注来源,区分官方标注和预测。
26
  - `data/preview_sources.json`:素材仓库、固定版本与来源清单。
27
 
28
+ 查看器支持样例与标注切换、透明度、同步放大和手部区域定位。切换时叠加栏显示“加载中”,新图与标注加载完成后一起显示;快速连续切换以最后一次选择为准。每张图保留出处,原作者图使用本地无损 WebP。
29
 
30
  ## 页面结构
31
 
deployment_manifest.json CHANGED
@@ -22,8 +22,8 @@
22
  },
23
  {
24
  "path": "README.md",
25
- "bytes": 4038,
26
- "sha256": "59cfbf449ae524744cc762d8311da421ffa287f78bc2c8396e272e8d270d8bc8"
27
  },
28
  {
29
  "path": "data/datasets.json",
@@ -62,8 +62,8 @@
62
  },
63
  {
64
  "path": "index.html",
65
- "bytes": 49125,
66
- "sha256": "cb82e81762aabba470cba008b40d1f7ae6a5c738b844e89f232e16fa9bdd21df"
67
  },
68
  {
69
  "path": "media/.gitkeep",
@@ -1402,8 +1402,8 @@
1402
  },
1403
  {
1404
  "path": "src/page.html",
1405
- "bytes": 48776,
1406
- "sha256": "24aba32afa4b8407b0066bf92c1bab9df6f76fb2998b02ad94aa6c92d428f7e6"
1407
  },
1408
  {
1409
  "path": "tools/add_preview.py",
 
22
  },
23
  {
24
  "path": "README.md",
25
+ "bytes": 4173,
26
+ "sha256": "f70d18ce15b54bf1370a7475196ead7f786bf850c524912da20ef0dd7d822a4b"
27
  },
28
  {
29
  "path": "data/datasets.json",
 
62
  },
63
  {
64
  "path": "index.html",
65
+ "bytes": 50508,
66
+ "sha256": "4ad135b296611f952e1972dfd1dfa26419fdb9a8aea71e6d9da9ccedc30e4bb4"
67
  },
68
  {
69
  "path": "media/.gitkeep",
 
1402
  },
1403
  {
1404
  "path": "src/page.html",
1405
+ "bytes": 50159,
1406
+ "sha256": "6e06556d6df3aaa014035458c5cca596537c4768d748b28db5a09cdcb4f86505"
1407
  },
1408
  {
1409
  "path": "tools/add_preview.py",
index.html CHANGED
@@ -246,7 +246,8 @@ footer { font-size: 0.8rem; color: var(--muted); border-top: 1px solid var(--lin
246
  .preview-tools button { padding:6px 10px; border:1px solid var(--line); color:var(--ink); background:var(--surface); border-radius:4px; cursor:pointer; font:inherit; }
247
  .preview-pair { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:10px; }
248
  .preview-pair figure { margin:0; border:1px solid var(--line); border-radius:6px; overflow:hidden; background:var(--surface); }
249
- .preview-pair figcaption { padding:8px 10px; font-size:.8rem; min-height:2.8em; }
 
250
  .preview-pair svg { display:block; width:100%; height:min(46vw,480px); background:#10161c; }
251
  .preview-thumbs { display:flex; gap:8px; overflow-x:auto; padding:3px 0 7px; }
252
  .preview-thumbs button { flex:0 0 86px; position:relative; padding:0; border:2px solid transparent; border-radius:4px; cursor:pointer; overflow:hidden; background:var(--sunk); }
@@ -331,13 +332,13 @@ footer { font-size: 0.8rem; color: var(--muted); border-top: 1px solid var(--lin
331
  function previewHTML(d) {
332
  var pv = PREV[d.id];
333
  return '<div class="preview"><div class="preview-tools"><button id="pv-prev" aria-label="上一个样例">←</button><label>样例<select id="pv-select" aria-label="样例">' + pv.map(function (p, i) { return '<option value="' + i + '">' + (i + 1) + ' · ' + esc(p.caption) + '</option>'; }).join('') + '</select></label><button id="pv-next" aria-label="下一个样例">→</button><label>标注<select id="pv-mode" aria-label="标注"></select></label></div>' +
334
- '<div class="preview-pair"><figure><figcaption id="pv-rgb-label"></figcaption><svg id="pv-left" role="img" aria-label="原始图像" preserveAspectRatio="xMidYMid meet"><image id="pv-rgb"/></svg></figure><figure><figcaption id="pv-gt-label"></figcaption><svg id="pv-right" role="img" aria-label="同帧图像与来源标注叠加" preserveAspectRatio="xMidYMid meet"><image id="pv-base"/><image id="pv-overlay"/></svg></figure></div>' +
335
  '<div class="preview-tools"><label>叠加透明度<input id="pv-alpha" aria-label="叠加透明度" type="range" min="0" max="100" value="100"><output id="pv-alpha-value">100%</output></label><label>同步放大<input id="pv-zoom" aria-label="同步放大" type="range" min="1" max="8" step="0.1" value="1"><output id="pv-zoom-value">1×</output></label><button id="pv-hand">手部区域</button><button id="pv-reset">全图</button></div>' +
336
  '<p id="pv-caption" class="small"></p><div id="pv-strata" class="chips"></div><div id="pv-source" class="source-note"></div><div class="preview-thumbs">' + pv.map(function (p, i) { return '<button data-preview="' + i + '" aria-label="样例 ' + (i + 1) + '"><img loading="lazy" src="' + esc(p.rgb) + '" alt=""><span>' + (i + 1) + '</span></button>'; }).join('') + '</div></div>' + samplingHTML(d.id);
337
  }
338
 
339
  function bindPreview(id) {
340
- var pv = PREV[id], index = 0, box;
341
  function el(name) { return document.getElementById('pv-' + name); }
342
  function viewport() {
343
  var zoom = Number(el('zoom').value), w = box[2] / zoom, h = box[3] / zoom;
@@ -346,14 +347,32 @@ footer { font-size: 0.8rem; color: var(--muted); border-top: 1px solid var(--lin
346
  el('zoom-value').textContent = zoom.toFixed(1).replace('.0', '') + '×';
347
  }
348
  function annotation() {
349
- var mode = pv[index].modes[Number(el('mode').value)];
350
- el('overlay').setAttribute('href', mode.src); el('gt-label').textContent = '标注叠加 · ' + mode.label;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
  }
352
  function show() {
353
  var p = pv[index];
354
  el('select').value = index;
355
- ['rgb', 'base', 'overlay'].forEach(function (name) { el(name).setAttribute('width', p.width); el(name).setAttribute('height', p.height); });
356
- el('rgb').setAttribute('href', p.rgb); el('base').setAttribute('href', p.rgb);
357
  el('rgb-label').textContent = p.rgb_label;
358
  el('mode').innerHTML = p.modes.map(function (m, i) { return '<option value="' + i + '">' + esc(m.label) + '</option>'; }).join('');
359
  annotation(); box = [0, 0, p.width, p.height]; el('zoom').value = 1; viewport();
 
246
  .preview-tools button { padding:6px 10px; border:1px solid var(--line); color:var(--ink); background:var(--surface); border-radius:4px; cursor:pointer; font:inherit; }
247
  .preview-pair { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:10px; }
248
  .preview-pair figure { margin:0; border:1px solid var(--line); border-radius:6px; overflow:hidden; background:var(--surface); }
249
+ .preview-pair figcaption { padding:8px 10px; font-size:.8rem; min-height:2.8em; display:flex; flex-wrap:wrap; gap:6px 12px; align-items:center; justify-content:space-between; }
250
+ .loading-status { color:var(--accent); white-space:nowrap; }
251
  .preview-pair svg { display:block; width:100%; height:min(46vw,480px); background:#10161c; }
252
  .preview-thumbs { display:flex; gap:8px; overflow-x:auto; padding:3px 0 7px; }
253
  .preview-thumbs button { flex:0 0 86px; position:relative; padding:0; border:2px solid transparent; border-radius:4px; cursor:pointer; overflow:hidden; background:var(--sunk); }
 
332
  function previewHTML(d) {
333
  var pv = PREV[d.id];
334
  return '<div class="preview"><div class="preview-tools"><button id="pv-prev" aria-label="上一个样例">←</button><label>样例<select id="pv-select" aria-label="样例">' + pv.map(function (p, i) { return '<option value="' + i + '">' + (i + 1) + ' · ' + esc(p.caption) + '</option>'; }).join('') + '</select></label><button id="pv-next" aria-label="下一个样例">→</button><label>标注<select id="pv-mode" aria-label="标注"></select></label></div>' +
335
+ '<div class="preview-pair"><figure><figcaption id="pv-rgb-label"></figcaption><svg id="pv-left" role="img" aria-label="原始图像" preserveAspectRatio="xMidYMid meet"><image id="pv-rgb"/></svg></figure><figure><figcaption><span id="pv-gt-label"></span><span id="pv-loading" class="loading-status" role="status" aria-live="polite">加载中…</span></figcaption><svg id="pv-right" role="img" aria-label="同帧图像与来源标注叠加" preserveAspectRatio="xMidYMid meet"><image id="pv-base"/><image id="pv-overlay"/></svg></figure></div>' +
336
  '<div class="preview-tools"><label>叠加透明度<input id="pv-alpha" aria-label="叠加透明度" type="range" min="0" max="100" value="100"><output id="pv-alpha-value">100%</output></label><label>同步放大<input id="pv-zoom" aria-label="同步放大" type="range" min="1" max="8" step="0.1" value="1"><output id="pv-zoom-value">1×</output></label><button id="pv-hand">手部区域</button><button id="pv-reset">全图</button></div>' +
337
  '<p id="pv-caption" class="small"></p><div id="pv-strata" class="chips"></div><div id="pv-source" class="source-note"></div><div class="preview-thumbs">' + pv.map(function (p, i) { return '<button data-preview="' + i + '" aria-label="样例 ' + (i + 1) + '"><img loading="lazy" src="' + esc(p.rgb) + '" alt=""><span>' + (i + 1) + '</span></button>'; }).join('') + '</div></div>' + samplingHTML(d.id);
338
  }
339
 
340
  function bindPreview(id) {
341
+ var pv = PREV[id], index = 0, box, loadVersion = 0;
342
  function el(name) { return document.getElementById('pv-' + name); }
343
  function viewport() {
344
  var zoom = Number(el('zoom').value), w = box[2] / zoom, h = box[3] / zoom;
 
347
  el('zoom-value').textContent = zoom.toFixed(1).replace('.0', '') + '×';
348
  }
349
  function annotation() {
350
+ var p = pv[index], mode = p.modes[Number(el('mode').value)], version = ++loadVersion;
351
+ var pane = el('right'), status = el('loading'), images = [];
352
+ el('gt-label').textContent = '标注叠加 · ' + mode.label;
353
+ status.hidden = false; status.textContent = '加载中…'; pane.setAttribute('aria-busy', 'true');
354
+ Promise.all([['base', p.rgb], ['overlay', mode.src]].map(function (item) {
355
+ var image = document.createElementNS('http://www.w3.org/2000/svg', 'image');
356
+ image.id = 'pv-' + item[0]; image.setAttribute('width', p.width); image.setAttribute('height', p.height);
357
+ image.style.visibility = 'hidden';
358
+ if (item[0] === 'overlay') image.style.opacity = Number(el('alpha').value) / 100;
359
+ var ready = new Promise(function (resolve, reject) { image.onload = resolve; image.onerror = reject; });
360
+ el(item[0]).replaceWith(image); images.push(image); image.setAttribute('href', item[1]);
361
+ return ready;
362
+ })).then(function () {
363
+ if (version !== loadVersion) return;
364
+ images.forEach(function (image) { image.style.visibility = 'visible'; });
365
+ status.hidden = true; pane.setAttribute('aria-busy', 'false');
366
+ }, function () {
367
+ if (version !== loadVersion) return;
368
+ status.textContent = '加载失败'; pane.setAttribute('aria-busy', 'false');
369
+ });
370
  }
371
  function show() {
372
  var p = pv[index];
373
  el('select').value = index;
374
+ el('rgb').setAttribute('width', p.width); el('rgb').setAttribute('height', p.height);
375
+ el('rgb').setAttribute('href', p.rgb);
376
  el('rgb-label').textContent = p.rgb_label;
377
  el('mode').innerHTML = p.modes.map(function (m, i) { return '<option value="' + i + '">' + esc(m.label) + '</option>'; }).join('');
378
  annotation(); box = [0, 0, p.width, p.height]; el('zoom').value = 1; viewport();
src/page.html CHANGED
@@ -240,7 +240,8 @@ footer { font-size: 0.8rem; color: var(--muted); border-top: 1px solid var(--lin
240
  .preview-tools button { padding:6px 10px; border:1px solid var(--line); color:var(--ink); background:var(--surface); border-radius:4px; cursor:pointer; font:inherit; }
241
  .preview-pair { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:10px; }
242
  .preview-pair figure { margin:0; border:1px solid var(--line); border-radius:6px; overflow:hidden; background:var(--surface); }
243
- .preview-pair figcaption { padding:8px 10px; font-size:.8rem; min-height:2.8em; }
 
244
  .preview-pair svg { display:block; width:100%; height:min(46vw,480px); background:#10161c; }
245
  .preview-thumbs { display:flex; gap:8px; overflow-x:auto; padding:3px 0 7px; }
246
  .preview-thumbs button { flex:0 0 86px; position:relative; padding:0; border:2px solid transparent; border-radius:4px; cursor:pointer; overflow:hidden; background:var(--sunk); }
@@ -322,13 +323,13 @@ footer { font-size: 0.8rem; color: var(--muted); border-top: 1px solid var(--lin
322
  function previewHTML(d) {
323
  var pv = PREV[d.id];
324
  return '<div class="preview"><div class="preview-tools"><button id="pv-prev" aria-label="上一个样例">←</button><label>样例<select id="pv-select" aria-label="样例">' + pv.map(function (p, i) { return '<option value="' + i + '">' + (i + 1) + ' · ' + esc(p.caption) + '</option>'; }).join('') + '</select></label><button id="pv-next" aria-label="下一个样例">→</button><label>标注<select id="pv-mode" aria-label="标注"></select></label></div>' +
325
- '<div class="preview-pair"><figure><figcaption id="pv-rgb-label"></figcaption><svg id="pv-left" role="img" aria-label="原始图像" preserveAspectRatio="xMidYMid meet"><image id="pv-rgb"/></svg></figure><figure><figcaption id="pv-gt-label"></figcaption><svg id="pv-right" role="img" aria-label="同帧图像与来源标注叠加" preserveAspectRatio="xMidYMid meet"><image id="pv-base"/><image id="pv-overlay"/></svg></figure></div>' +
326
  '<div class="preview-tools"><label>叠加透明度<input id="pv-alpha" aria-label="叠加透明度" type="range" min="0" max="100" value="100"><output id="pv-alpha-value">100%</output></label><label>同步放大<input id="pv-zoom" aria-label="同步放大" type="range" min="1" max="8" step="0.1" value="1"><output id="pv-zoom-value">1×</output></label><button id="pv-hand">手部区域</button><button id="pv-reset">全图</button></div>' +
327
  '<p id="pv-caption" class="small"></p><div id="pv-strata" class="chips"></div><div id="pv-source" class="source-note"></div><div class="preview-thumbs">' + pv.map(function (p, i) { return '<button data-preview="' + i + '" aria-label="样例 ' + (i + 1) + '"><img loading="lazy" src="' + esc(p.rgb) + '" alt=""><span>' + (i + 1) + '</span></button>'; }).join('') + '</div></div>' + samplingHTML(d.id);
328
  }
329
 
330
  function bindPreview(id) {
331
- var pv = PREV[id], index = 0, box;
332
  function el(name) { return document.getElementById('pv-' + name); }
333
  function viewport() {
334
  var zoom = Number(el('zoom').value), w = box[2] / zoom, h = box[3] / zoom;
@@ -337,14 +338,32 @@ footer { font-size: 0.8rem; color: var(--muted); border-top: 1px solid var(--lin
337
  el('zoom-value').textContent = zoom.toFixed(1).replace('.0', '') + '×';
338
  }
339
  function annotation() {
340
- var mode = pv[index].modes[Number(el('mode').value)];
341
- el('overlay').setAttribute('href', mode.src); el('gt-label').textContent = '标注叠加 · ' + mode.label;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
342
  }
343
  function show() {
344
  var p = pv[index];
345
  el('select').value = index;
346
- ['rgb', 'base', 'overlay'].forEach(function (name) { el(name).setAttribute('width', p.width); el(name).setAttribute('height', p.height); });
347
- el('rgb').setAttribute('href', p.rgb); el('base').setAttribute('href', p.rgb);
348
  el('rgb-label').textContent = p.rgb_label;
349
  el('mode').innerHTML = p.modes.map(function (m, i) { return '<option value="' + i + '">' + esc(m.label) + '</option>'; }).join('');
350
  annotation(); box = [0, 0, p.width, p.height]; el('zoom').value = 1; viewport();
 
240
  .preview-tools button { padding:6px 10px; border:1px solid var(--line); color:var(--ink); background:var(--surface); border-radius:4px; cursor:pointer; font:inherit; }
241
  .preview-pair { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:10px; }
242
  .preview-pair figure { margin:0; border:1px solid var(--line); border-radius:6px; overflow:hidden; background:var(--surface); }
243
+ .preview-pair figcaption { padding:8px 10px; font-size:.8rem; min-height:2.8em; display:flex; flex-wrap:wrap; gap:6px 12px; align-items:center; justify-content:space-between; }
244
+ .loading-status { color:var(--accent); white-space:nowrap; }
245
  .preview-pair svg { display:block; width:100%; height:min(46vw,480px); background:#10161c; }
246
  .preview-thumbs { display:flex; gap:8px; overflow-x:auto; padding:3px 0 7px; }
247
  .preview-thumbs button { flex:0 0 86px; position:relative; padding:0; border:2px solid transparent; border-radius:4px; cursor:pointer; overflow:hidden; background:var(--sunk); }
 
323
  function previewHTML(d) {
324
  var pv = PREV[d.id];
325
  return '<div class="preview"><div class="preview-tools"><button id="pv-prev" aria-label="上一个样例">←</button><label>样例<select id="pv-select" aria-label="样例">' + pv.map(function (p, i) { return '<option value="' + i + '">' + (i + 1) + ' · ' + esc(p.caption) + '</option>'; }).join('') + '</select></label><button id="pv-next" aria-label="下一个样例">→</button><label>标注<select id="pv-mode" aria-label="标注"></select></label></div>' +
326
+ '<div class="preview-pair"><figure><figcaption id="pv-rgb-label"></figcaption><svg id="pv-left" role="img" aria-label="原始图像" preserveAspectRatio="xMidYMid meet"><image id="pv-rgb"/></svg></figure><figure><figcaption><span id="pv-gt-label"></span><span id="pv-loading" class="loading-status" role="status" aria-live="polite">加载中…</span></figcaption><svg id="pv-right" role="img" aria-label="同帧图像与来源标注叠加" preserveAspectRatio="xMidYMid meet"><image id="pv-base"/><image id="pv-overlay"/></svg></figure></div>' +
327
  '<div class="preview-tools"><label>叠加透明度<input id="pv-alpha" aria-label="叠加透明度" type="range" min="0" max="100" value="100"><output id="pv-alpha-value">100%</output></label><label>同步放大<input id="pv-zoom" aria-label="同步放大" type="range" min="1" max="8" step="0.1" value="1"><output id="pv-zoom-value">1×</output></label><button id="pv-hand">手部区域</button><button id="pv-reset">全图</button></div>' +
328
  '<p id="pv-caption" class="small"></p><div id="pv-strata" class="chips"></div><div id="pv-source" class="source-note"></div><div class="preview-thumbs">' + pv.map(function (p, i) { return '<button data-preview="' + i + '" aria-label="样例 ' + (i + 1) + '"><img loading="lazy" src="' + esc(p.rgb) + '" alt=""><span>' + (i + 1) + '</span></button>'; }).join('') + '</div></div>' + samplingHTML(d.id);
329
  }
330
 
331
  function bindPreview(id) {
332
+ var pv = PREV[id], index = 0, box, loadVersion = 0;
333
  function el(name) { return document.getElementById('pv-' + name); }
334
  function viewport() {
335
  var zoom = Number(el('zoom').value), w = box[2] / zoom, h = box[3] / zoom;
 
338
  el('zoom-value').textContent = zoom.toFixed(1).replace('.0', '') + '×';
339
  }
340
  function annotation() {
341
+ var p = pv[index], mode = p.modes[Number(el('mode').value)], version = ++loadVersion;
342
+ var pane = el('right'), status = el('loading'), images = [];
343
+ el('gt-label').textContent = '标注叠加 · ' + mode.label;
344
+ status.hidden = false; status.textContent = '加载中…'; pane.setAttribute('aria-busy', 'true');
345
+ Promise.all([['base', p.rgb], ['overlay', mode.src]].map(function (item) {
346
+ var image = document.createElementNS('http://www.w3.org/2000/svg', 'image');
347
+ image.id = 'pv-' + item[0]; image.setAttribute('width', p.width); image.setAttribute('height', p.height);
348
+ image.style.visibility = 'hidden';
349
+ if (item[0] === 'overlay') image.style.opacity = Number(el('alpha').value) / 100;
350
+ var ready = new Promise(function (resolve, reject) { image.onload = resolve; image.onerror = reject; });
351
+ el(item[0]).replaceWith(image); images.push(image); image.setAttribute('href', item[1]);
352
+ return ready;
353
+ })).then(function () {
354
+ if (version !== loadVersion) return;
355
+ images.forEach(function (image) { image.style.visibility = 'visible'; });
356
+ status.hidden = true; pane.setAttribute('aria-busy', 'false');
357
+ }, function () {
358
+ if (version !== loadVersion) return;
359
+ status.textContent = '加载失败'; pane.setAttribute('aria-busy', 'false');
360
+ });
361
  }
362
  function show() {
363
  var p = pv[index];
364
  el('select').value = index;
365
+ el('rgb').setAttribute('width', p.width); el('rgb').setAttribute('height', p.height);
366
+ el('rgb').setAttribute('href', p.rgb);
367
  el('rgb-label').textContent = p.rgb_label;
368
  el('mode').innerHTML = p.modes.map(function (m, i) { return '<option value="' + i + '">' + esc(m.label) + '</option>'; }).join('');
369
  annotation(); box = [0, 0, p.width, p.height]; el('zoom').value = 1; viewport();