优化缩略图

This commit is contained in:
Yeomai committed 2026-09-20 02:31:07 +08:00
1 parent fe4997f913
commit c28adf9fae
21 files changed
+74 -24

No files matched your search

+1 -1
View File
@@ -37,7 +37,7 @@
"animatedWebpUnavailable": "Diese Umgebung kann WebP nicht kodieren. Bitte APNG auswählen.",
"animatedCannotFit": "Die Animation kann nicht auf {target} KB reduziert werden (mindestens etwa {actual} KB). Erhöhen Sie das Limit oder verkürzen Sie den Framebereich.",
"animatedPickTitle": "Miniaturbild auswählen",
"animatedPickHint": "Alle Ergebnisse behalten eine 400×400-Arbeitsfläche; das Label zeigt Detailauflösung@fps/Qualität/Dateigröße. Wählen Sie eines und drücken Sie Bestätigen, um es zu übernehmen, oder brechen Sie ab, um alle zu verwerfen.",
"animatedPickHint": "Alle Ergebnisse behalten eine {size}-Arbeitsfläche; das Label zeigt Detailauflösung@fps/Qualität/Dateigröße. Wählen Sie eines und drücken Sie Bestätigen, um es zu übernehmen, oder brechen Sie ab, um alle zu verwerfen.",
"animatedPickConfirm": "Bestätigen",
"animatedPriority": "Priorität",
"animatedCanvas": "Ausgabegröße",
+1 -1
View File
@@ -37,7 +37,7 @@
"animatedWebpUnavailable": "This environment cannot encode WebP. Choose APNG instead.",
"animatedCannotFit": "The animation cannot be reduced to {target} KB (minimum about {actual} KB). Increase the limit or shorten the frame range.",
"animatedPickTitle": "Pick a thumbnail",
"animatedPickHint": "Every result keeps a 400×400 canvas; the label shows detail resolution@fps/quality/file size. Select one and press Confirm to replace the thumbnail, or cancel to discard them all.",
"animatedPickHint": "Every result keeps a {size} canvas; the label shows detail resolution@fps/quality/file size. Select one and press Confirm to replace the thumbnail, or cancel to discard them all.",
"animatedPickConfirm": "Confirm",
"animatedPriority": "Priority",
"animatedCanvas": "Canvas size",
+1 -1
View File
@@ -37,7 +37,7 @@
"animatedWebpUnavailable": "Este entorno no puede codificar WebP. Selecciona APNG.",
"animatedCannotFit": "No se puede reducir la animación a {target} KB (mínimo aproximado: {actual} KB). Aumenta el límite o acorta el rango.",
"animatedPickTitle": "Elige una miniatura",
"animatedPickHint": "Todos los resultados mantienen un lienzo de 400×400; la etiqueta indica la resolución de detalle@fps/calidad/tamaño de archivo. Elige uno y pulsa Confirmar para aplicarlo, o cancela para descartarlos todos.",
"animatedPickHint": "Todos los resultados mantienen un lienzo de {size}; la etiqueta indica la resolución de detalle@fps/calidad/tamaño de archivo. Elige uno y pulsa Confirmar para aplicarlo, o cancela para descartarlos todos.",
"animatedPickConfirm": "Confirmar",
"animatedPriority": "Prioridad",
"animatedCanvas": "Tamaño del lienzo",
+1 -1
View File
@@ -37,7 +37,7 @@
"animatedWebpUnavailable": "この環境では WebP をエンコードできません。APNG を選択してください。",
"animatedCannotFit": "アニメーションを {target} KB まで圧縮できません(最小約 {actual} KB)。上限を増やすかフレーム範囲を短くしてください。",
"animatedPickTitle": "サムネイルを選択",
"animatedPickHint": "すべての結果は 400×400 のキャンバスです。ラベルは実際のディテール解像度@fps/品質/ファイルサイズを示します。1つ選んで確認を押すと置き換え、キャンセルですべて破棄します。",
"animatedPickHint": "すべての結果は {size} のキャンバスです。ラベルは実際のディテール解像度@fps/品質/ファイルサイズを示します。1つ選んで確認を押すと置き換え、キャンセルですべて破棄します。",
"animatedPickConfirm": "確認",
"animatedPriority": "優先度",
"animatedCanvas": "出力サイズ",
+1 -1
View File
@@ -37,7 +37,7 @@
"animatedWebpUnavailable": "현재 환경에서 WebP를 인코딩할 수 없습니다. APNG를 선택하세요.",
"animatedCannotFit": "애니메이션을 {target}KB로 줄일 수 없습니다(최소 약 {actual}KB). 제한을 높이거나 프레임 범위를 줄이세요.",
"animatedPickTitle": "썸네일 선택",
"animatedPickHint": "모든 결과의 캔버스는 400×400이며, 라벨은 실제 디테일 해상도@fps/품질/파일 크기입니다. 하나를 선택하고 확인을 누르면 적용되고, 취소하면 모두 버려집니다.",
"animatedPickHint": "모든 결과의 캔버스는 {size}이며, 라벨은 실제 디테일 해상도@fps/품질/파일 크기입니다. 하나를 선택하고 확인을 누르면 적용되고, 취소하면 모두 버려집니다.",
"animatedPickConfirm": "확인",
"animatedPriority": "우선 순위",
"animatedCanvas": "출력 크기",
+1 -1
View File
@@ -37,7 +37,7 @@
"animatedWebpUnavailable": "Эта среда не поддерживает кодирование WebP. Выберите APNG.",
"animatedCannotFit": "Анимацию нельзя уменьшить до {target} КБ (минимум около {actual} КБ). Увеличьте лимит или сократите диапазон кадров.",
"animatedPickTitle": "Выберите миниатюру",
"animatedPickHint": "У всех результатов холст 400×400; в подписи указаны реальное разрешение детализации@fps/качество/размер файла. Выберите результат и нажмите «Подтвердить», чтобы применить его, или отмените, чтобы всё отбросить.",
"animatedPickHint": "У всех результатов холст {size}; в подписи указаны реальное разрешение детализации@fps/качество/размер файла. Выберите результат и нажмите «Подтвердить», чтобы применить его, или отмените, чтобы всё отбросить.",
"animatedPickConfirm": "Подтвердить",
"animatedPriority": "Приоритет",
"animatedCanvas": "Размер холста",
+1 -1
View File
@@ -37,7 +37,7 @@
"animatedWebpUnavailable": "当前环境不支持 WebP 编码,请选择 APNG",
"animatedCannotFit": "无法在保留动画的前提下压缩到 {target} KB(最小约 {actual} KB),请提高上限或缩短帧范围",
"animatedPickTitle": "选择一个缩略图",
"animatedPickHint": "所有结果画布都是 400×400,标签为实际细节分辨率@帧率/质量/文件大小。点选一个结果后按确认替换缩略图,取消则全部放弃。",
"animatedPickHint": "所有结果画布都是 {size},标签为实际细节分辨率@帧率/质量/文件大小。点选一个结果后按确认替换缩略图,取消则全部放弃。",
"animatedPickConfirm": "确认",
"animatedPriority": "优先级别",
"animatedCanvas": "输出尺寸",
+1 -1
View File
@@ -37,7 +37,7 @@
"animatedWebpUnavailable": "目前環境不支援 WebP 編碼,請選擇 APNG",
"animatedCannotFit": "無法在保留動畫的情況下壓縮到 {target} KB(最小約 {actual} KB),請提高上限或縮短影格範圍",
"animatedPickTitle": "選擇一個縮圖",
"animatedPickHint": "所有結果畫布都是 400×400,標籤為實際細節解析度@影格率/品質/檔案大小。點選一個結果後按確認替換縮圖,取消則全部放棄。",
"animatedPickHint": "所有結果畫布都是 {size},標籤為實際細節解析度@影格率/品質/檔案大小。點選一個結果後按確認替換縮圖,取消則全部放棄。",
"animatedPickConfirm": "確認",
"animatedPriority": "優先級別",
"animatedCanvas": "輸出尺寸",
+15
View File
@@ -42,9 +42,21 @@ const DEFAULT_PRIORITY = 'resolution';
const ANIMATED_OUTPUT_SIZES = [200, 400];
const DEFAULT_OUTPUT_SIZE = 200;
/**
* 「缩略图大小」输入框的默认建议值:每秒动画配 25 KB,夹在 25–100 KB 之间。
* 只是打开面板时的初始值,用户随时可以手动改成 10–1024 之间的任意数。
*/
const SIZE_PER_SECOND = 25;
const SIZE_SUGGESTION_RANGE = [25, 100];
// 相邻锚点之间插多少个采样点。数值越大,越容易压在目标体积附近,但粗估阶段耗时略增。
const PATH_STEPS_PER_SEGMENT = 6;
function suggestSize(seconds) {
const [min, max] = SIZE_SUGGESTION_RANGE;
return Math.max(min, Math.min(max, Math.round((Number(seconds) || 0) * SIZE_PER_SECOND)));
}
function animatedProfiles(priority, outputSize) {
const limit = outputSize || DEFAULT_OUTPUT_SIZE;
const plan = ANIMATED_PRIORITIES[priority] || ANIMATED_PRIORITIES[DEFAULT_PRIORITY];
@@ -86,7 +98,10 @@ module.exports = {
DEFAULT_PRIORITY,
ANIMATED_OUTPUT_SIZES,
DEFAULT_OUTPUT_SIZE,
SIZE_PER_SECOND,
SIZE_SUGGESTION_RANGE,
PATH_STEPS_PER_SEGMENT,
animatedProfiles,
expandAnimatedPath,
suggestSize,
};
@@ -37,7 +37,7 @@
"animatedWebpUnavailable": "Diese Umgebung kann WebP nicht kodieren. Bitte APNG auswählen.",
"animatedCannotFit": "Die Animation kann nicht auf {target} KB reduziert werden (mindestens etwa {actual} KB). Erhöhen Sie das Limit oder verkürzen Sie den Framebereich.",
"animatedPickTitle": "Miniaturbild auswählen",
"animatedPickHint": "Alle Ergebnisse behalten eine 400×400-Arbeitsfläche; das Label zeigt Detailauflösung@fps/Qualität/Dateigröße. Wählen Sie eines und drücken Sie Bestätigen, um es zu übernehmen, oder brechen Sie ab, um alle zu verwerfen.",
"animatedPickHint": "Alle Ergebnisse behalten eine {size}-Arbeitsfläche; das Label zeigt Detailauflösung@fps/Qualität/Dateigröße. Wählen Sie eines und drücken Sie Bestätigen, um es zu übernehmen, oder brechen Sie ab, um alle zu verwerfen.",
"animatedPickConfirm": "Bestätigen",
"animatedPriority": "Priorität",
"animatedCanvas": "Ausgabegröße",
@@ -37,7 +37,7 @@
"animatedWebpUnavailable": "This environment cannot encode WebP. Choose APNG instead.",
"animatedCannotFit": "The animation cannot be reduced to {target} KB (minimum about {actual} KB). Increase the limit or shorten the frame range.",
"animatedPickTitle": "Pick a thumbnail",
"animatedPickHint": "Every result keeps a 400×400 canvas; the label shows detail resolution@fps/quality/file size. Select one and press Confirm to replace the thumbnail, or cancel to discard them all.",
"animatedPickHint": "Every result keeps a {size} canvas; the label shows detail resolution@fps/quality/file size. Select one and press Confirm to replace the thumbnail, or cancel to discard them all.",
"animatedPickConfirm": "Confirm",
"animatedPriority": "Priority",
"animatedCanvas": "Canvas size",
@@ -37,7 +37,7 @@
"animatedWebpUnavailable": "Este entorno no puede codificar WebP. Selecciona APNG.",
"animatedCannotFit": "No se puede reducir la animación a {target} KB (mínimo aproximado: {actual} KB). Aumenta el límite o acorta el rango.",
"animatedPickTitle": "Elige una miniatura",
"animatedPickHint": "Todos los resultados mantienen un lienzo de 400×400; la etiqueta indica la resolución de detalle@fps/calidad/tamaño de archivo. Elige uno y pulsa Confirmar para aplicarlo, o cancela para descartarlos todos.",
"animatedPickHint": "Todos los resultados mantienen un lienzo de {size}; la etiqueta indica la resolución de detalle@fps/calidad/tamaño de archivo. Elige uno y pulsa Confirmar para aplicarlo, o cancela para descartarlos todos.",
"animatedPickConfirm": "Confirmar",
"animatedPriority": "Prioridad",
"animatedCanvas": "Tamaño del lienzo",
@@ -37,7 +37,7 @@
"animatedWebpUnavailable": "この環境では WebP をエンコードできません。APNG を選択してください。",
"animatedCannotFit": "アニメーションを {target} KB まで圧縮できません(最小約 {actual} KB)。上限を増やすかフレーム範囲を短くしてください。",
"animatedPickTitle": "サムネイルを選択",
"animatedPickHint": "すべての結果は 400×400 のキャンバスです。ラベルは実際のディテール解像度@fps/品質/ファイルサイズを示します。1つ選んで確認を押すと置き換え、キャンセルですべて破棄します。",
"animatedPickHint": "すべての結果は {size} のキャンバスです。ラベルは実際のディテール解像度@fps/品質/ファイルサイズを示します。1つ選んで確認を押すと置き換え、キャンセルですべて破棄します。",
"animatedPickConfirm": "確認",
"animatedPriority": "優先度",
"animatedCanvas": "出力サイズ",
@@ -37,7 +37,7 @@
"animatedWebpUnavailable": "현재 환경에서 WebP를 인코딩할 수 없습니다. APNG를 선택하세요.",
"animatedCannotFit": "애니메이션을 {target}KB로 줄일 수 없습니다(최소 약 {actual}KB). 제한을 높이거나 프레임 범위를 줄이세요.",
"animatedPickTitle": "썸네일 선택",
"animatedPickHint": "모든 결과의 캔버스는 400×400이며, 라벨은 실제 디테일 해상도@fps/품질/파일 크기입니다. 하나를 선택하고 확인을 누르면 적용되고, 취소하면 모두 버려집니다.",
"animatedPickHint": "모든 결과의 캔버스는 {size}이며, 라벨은 실제 디테일 해상도@fps/품질/파일 크기입니다. 하나를 선택하고 확인을 누르면 적용되고, 취소하면 모두 버려집니다.",
"animatedPickConfirm": "확인",
"animatedPriority": "우선 순위",
"animatedCanvas": "출력 크기",
@@ -37,7 +37,7 @@
"animatedWebpUnavailable": "Эта среда не поддерживает кодирование WebP. Выберите APNG.",
"animatedCannotFit": "Анимацию нельзя уменьшить до {target} КБ (минимум около {actual} КБ). Увеличьте лимит или сократите диапазон кадров.",
"animatedPickTitle": "Выберите миниатюру",
"animatedPickHint": "У всех результатов холст 400×400; в подписи указаны реальное разрешение детализации@fps/качество/размер файла. Выберите результат и нажмите «Подтвердить», чтобы применить его, или отмените, чтобы всё отбросить.",
"animatedPickHint": "У всех результатов холст {size}; в подписи указаны реальное разрешение детализации@fps/качество/размер файла. Выберите результат и нажмите «Подтвердить», чтобы применить его, или отмените, чтобы всё отбросить.",
"animatedPickConfirm": "Подтвердить",
"animatedPriority": "Приоритет",
"animatedCanvas": "Размер холста",
@@ -37,7 +37,7 @@
"animatedWebpUnavailable": "当前环境不支持 WebP 编码,请选择 APNG",
"animatedCannotFit": "无法在保留动画的前提下压缩到 {target} KB(最小约 {actual} KB),请提高上限或缩短帧范围",
"animatedPickTitle": "选择一个缩略图",
"animatedPickHint": "所有结果画布都是 400×400,标签为实际细节分辨率@帧率/质量/文件大小。点选一个结果后按确认替换缩略图,取消则全部放弃。",
"animatedPickHint": "所有结果画布都是 {size},标签为实际细节分辨率@帧率/质量/文件大小。点选一个结果后按确认替换缩略图,取消则全部放弃。",
"animatedPickConfirm": "确认",
"animatedPriority": "优先级别",
"animatedCanvas": "输出尺寸",
@@ -37,7 +37,7 @@
"animatedWebpUnavailable": "目前環境不支援 WebP 編碼,請選擇 APNG",
"animatedCannotFit": "無法在保留動畫的情況下壓縮到 {target} KB(最小約 {actual} KB),請提高上限或縮短影格範圍",
"animatedPickTitle": "選擇一個縮圖",
"animatedPickHint": "所有結果畫布都是 400×400,標籤為實際細節解析度@影格率/品質/檔案大小。點選一個結果後按確認替換縮圖,取消則全部放棄。",
"animatedPickHint": "所有結果畫布都是 {size},標籤為實際細節解析度@影格率/品質/檔案大小。點選一個結果後按確認替換縮圖,取消則全部放棄。",
"animatedPickConfirm": "確認",
"animatedPriority": "優先級別",
"animatedCanvas": "輸出尺寸",
@@ -42,9 +42,21 @@ const DEFAULT_PRIORITY = 'resolution';
const ANIMATED_OUTPUT_SIZES = [200, 400];
const DEFAULT_OUTPUT_SIZE = 200;
/**
* 「缩略图大小」输入框的默认建议值:每秒动画配 25 KB,夹在 25–100 KB 之间。
* 只是打开面板时的初始值,用户随时可以手动改成 10–1024 之间的任意数。
*/
const SIZE_PER_SECOND = 25;
const SIZE_SUGGESTION_RANGE = [25, 100];
// 相邻锚点之间插多少个采样点。数值越大,越容易压在目标体积附近,但粗估阶段耗时略增。
const PATH_STEPS_PER_SEGMENT = 6;
function suggestSize(seconds) {
const [min, max] = SIZE_SUGGESTION_RANGE;
return Math.max(min, Math.min(max, Math.round((Number(seconds) || 0) * SIZE_PER_SECOND)));
}
function animatedProfiles(priority, outputSize) {
const limit = outputSize || DEFAULT_OUTPUT_SIZE;
const plan = ANIMATED_PRIORITIES[priority] || ANIMATED_PRIORITIES[DEFAULT_PRIORITY];
@@ -86,7 +98,10 @@ module.exports = {
DEFAULT_PRIORITY,
ANIMATED_OUTPUT_SIZES,
DEFAULT_OUTPUT_SIZE,
SIZE_PER_SECOND,
SIZE_SUGGESTION_RANGE,
PATH_STEPS_PER_SEGMENT,
animatedProfiles,
expandAnimatedPath,
suggestSize,
};
@@ -103,7 +103,7 @@
<span data-i18n="viewer.framesUnit">帧</span>
</div>
<label class="animated-thumb-quality"><span data-i18n="viewer.animatedMaxSize">缩略图大小</span>
<span class="animated-thumb-size"><input id="animatedThumbMaxSize" type="number" min="10" max="1024" step="10" value="30"><span>KB</span></span>
<span class="animated-thumb-size"><input id="animatedThumbMaxSize" type="number" min="10" max="1024" step="10" value="25"><span>KB</span></span>
</label>
<label class="animated-thumb-quality"><span data-i18n="viewer.animatedCanvas">输出尺寸</span>
<select id="animatedThumbCanvas">
@@ -129,7 +129,7 @@
<div id="animatedPickDialog" class="thumb-dialog hidden" role="dialog" aria-modal="true" aria-labelledby="animatedPickTitle">
<div class="thumb-dialog-card animated-pick-card">
<div id="animatedPickTitle" class="thumb-dialog-title" data-i18n="viewer.animatedPickTitle">选择一个缩略图</div>
<div class="thumb-dialog-note" data-i18n="viewer.animatedPickHint">所有结果画布都是 400×400,标签为实际细节分辨率@帧率/质量/文件大小。点击一个结果即替换缩略图,关闭则全部放弃。</div>
<div id="animatedPickHint" class="thumb-dialog-note">所有结果画布都是 400×400,标签为实际细节分辨率@帧率/质量/文件大小。点选一个结果后按确认替换缩略图,关闭则全部放弃。</div>
<div id="animatedPickGrid" class="animated-pick-grid"></div>
<div id="animatedPickNotice" class="animated-pick-notice"></div>
<div class="thumb-dialog-actions">
@@ -838,7 +838,8 @@
formatInput.value = 'webp';
priorityInput.value = animatedProfileConfig.DEFAULT_PRIORITY;
canvasInput.value = String(animatedProfileConfig.DEFAULT_OUTPUT_SIZE);
maxSizeInput.value = '30';
// 默认值跟动画时长走:每秒 25 KB,夹在 25–100 KB 之间;用户随时可以手动改。
maxSizeInput.value = String(animatedProfileConfig.suggestSize(animationLength));
errorEl.textContent = '';
dialog.classList.remove('hidden');
return new Promise((resolve) => {
@@ -1108,6 +1109,8 @@
const notice = document.getElementById('animatedPickNotice');
const cancelButton = document.getElementById('animatedPickCancel');
const confirmButton = document.getElementById('animatedPickConfirm');
// 提示里的画布尺寸跟着界面选的输出尺寸走,不写死。
document.getElementById('animatedPickHint').textContent = t('viewer.animatedPickHint', { size: size + '×' + size });
const targetBytes = options.maxKilobytes * 1024;
// 锚点插值成更密的搜索路径,候选数量仍按锚点数量给。
const searchPath = animatedProfileConfig.expandAnimatedPath(options.priority, options.canvasSize);
+16 -2
View File
@@ -95,6 +95,17 @@ assert.ok(
);
delete profilesModule.ANIMATED_PRIORITIES.__size;
// 「缩略图大小」默认值跟动画时长联动:每秒 25 KB,夹在 25–100 KB。
assert.deepEqual(profilesModule.SIZE_SUGGESTION_RANGE, [25, 100]);
assert.equal(profilesModule.suggestSize(0.5), 25, '短动画取最低值');
assert.equal(profilesModule.suggestSize(1), 25);
assert.equal(profilesModule.suggestSize(2), 50, '每秒 25 KB');
assert.equal(profilesModule.suggestSize(3.4), 85);
assert.equal(profilesModule.suggestSize(4), 100, '到顶后不再增加');
assert.equal(profilesModule.suggestSize(30), 100);
assert.equal(profilesModule.suggestSize(0), 25);
assert.equal(profilesModule.suggestSize(undefined), 25);
// 插值路径供按目标体积定位使用:比锚点更密,首尾与锚点一致。
// 配置允许分段(分辨率优先就是「400 系列 + 200 系列」),所以不要求整条路径单调。
for (const name of Object.keys(priorities)) {
@@ -124,6 +135,9 @@ for (const root of ['.', 'release/UNIVERSAL_SpineViewer']) {
assert.match(html, /id="animatedThumbCanvas"/);
assert.match(html, /<option value="200" selected>200 × 200<\/option>/);
assert.match(script, /canvasSize: Number\(canvasInput\.value\) \|\| animatedProfileConfig\.DEFAULT_OUTPUT_SIZE/);
// 画廊提示里的画布尺寸跟着输出尺寸走,不能写死。
assert.match(html, /id="animatedPickHint" class="thumb-dialog-note">/);
assert.match(script, /t\('viewer\.animatedPickHint', \{ size: size \+ '×' \+ size \}\)/);
assert.match(script, /async function renderAnimatedSourceFrames\(animation, options\)/);
assert.match(script, /async function pickAnimatedThumbnail\(sourceFrames, size, range, options\)/);
assert.match(script, /const targetBytes = options\.maxKilobytes \* 1024/);
@@ -168,11 +182,11 @@ for (const root of ['.', 'release/UNIVERSAL_SpineViewer']) {
assert.match(html, /id="animatedThumbStart" type="number" min="0" step="1"/);
assert.match(html, /id="animatedThumbEnd" type="number" min="0" step="1"/);
assert.match(html, /data-i18n="viewer\.framesUnit"/);
assert.match(html, /id="animatedThumbMaxSize" type="number" min="10" max="1024" step="10" value="30"/);
assert.match(html, /id="animatedThumbMaxSize" type="number" min="10" max="1024" step="10" value="25"/);
assert.match(html, /class="animated-thumb-danger" data-i18n="viewer\.animatedThumbnailDanger"/);
assert.match(html, /class="animated-thumb-warning-title" data-i18n="viewer\.animatedThumbnailAttention"/);
assert.match(css, /\.animated-thumb-warning-title \{ display: block; \}/);
assert.match(script, /maxSizeInput\.value = '30'/);
assert.match(script, /maxSizeInput\.value = String\(animatedProfileConfig\.suggestSize\(animationLength\)\)/);
assert.match(script, /maxKilobytes < 10 \|\| maxKilobytes > 1024/);
assert.doesNotMatch(script, /startInput\.(?:focus|select)\(\)/);
assert.match(script, /document\.activeElement\.blur\(\)/);
+6 -3
View File
@@ -103,7 +103,7 @@
<span data-i18n="viewer.framesUnit">帧</span>
</div>
<label class="animated-thumb-quality"><span data-i18n="viewer.animatedMaxSize">缩略图大小</span>
<span class="animated-thumb-size"><input id="animatedThumbMaxSize" type="number" min="10" max="1024" step="10" value="30"><span>KB</span></span>
<span class="animated-thumb-size"><input id="animatedThumbMaxSize" type="number" min="10" max="1024" step="10" value="25"><span>KB</span></span>
</label>
<label class="animated-thumb-quality"><span data-i18n="viewer.animatedCanvas">输出尺寸</span>
<select id="animatedThumbCanvas">
@@ -129,7 +129,7 @@
<div id="animatedPickDialog" class="thumb-dialog hidden" role="dialog" aria-modal="true" aria-labelledby="animatedPickTitle">
<div class="thumb-dialog-card animated-pick-card">
<div id="animatedPickTitle" class="thumb-dialog-title" data-i18n="viewer.animatedPickTitle">选择一个缩略图</div>
<div class="thumb-dialog-note" data-i18n="viewer.animatedPickHint">所有结果画布都是 400×400,标签为实际细节分辨率@帧率/质量/文件大小。点击一个结果即替换缩略图,关闭则全部放弃。</div>
<div id="animatedPickHint" class="thumb-dialog-note">所有结果画布都是 400×400,标签为实际细节分辨率@帧率/质量/文件大小。点选一个结果后按确认替换缩略图,关闭则全部放弃。</div>
<div id="animatedPickGrid" class="animated-pick-grid"></div>
<div id="animatedPickNotice" class="animated-pick-notice"></div>
<div class="thumb-dialog-actions">
@@ -838,7 +838,8 @@
formatInput.value = 'webp';
priorityInput.value = animatedProfileConfig.DEFAULT_PRIORITY;
canvasInput.value = String(animatedProfileConfig.DEFAULT_OUTPUT_SIZE);
maxSizeInput.value = '30';
// 默认值跟动画时长走:每秒 25 KB,夹在 25–100 KB 之间;用户随时可以手动改。
maxSizeInput.value = String(animatedProfileConfig.suggestSize(animationLength));
errorEl.textContent = '';
dialog.classList.remove('hidden');
return new Promise((resolve) => {
@@ -1108,6 +1109,8 @@
const notice = document.getElementById('animatedPickNotice');
const cancelButton = document.getElementById('animatedPickCancel');
const confirmButton = document.getElementById('animatedPickConfirm');
// 提示里的画布尺寸跟着界面选的输出尺寸走,不写死。
document.getElementById('animatedPickHint').textContent = t('viewer.animatedPickHint', { size: size + '×' + size });
const targetBytes = options.maxKilobytes * 1024;
// 锚点插值成更密的搜索路径,候选数量仍按锚点数量给。
const searchPath = animatedProfileConfig.expandAnimatedPath(options.priority, options.canvasSize);