diff --git a/src/layouts/SiteLayout.vue b/src/layouts/SiteLayout.vue index 834238f..48f8daf 100644 --- a/src/layouts/SiteLayout.vue +++ b/src/layouts/SiteLayout.vue @@ -447,11 +447,14 @@ function hideResultsSoon(): void { * 会顺着所有字形轮廓连续走,只有拆成独立元素才能五个字同时起笔 */ const curtainLetters = ['G', 'V', 'S', 'D', 'S'] as const -/** 轮廓长度估算系数。getTotalLength() 只对 这类几何元素生效, 取不到 - * 真实轮廓长度,只能用字宽 getComputedTextLength() 乘经验系数估: - * 大写 D / G 的轮廓周长约为字宽的 4.0 倍、S / V 约 3.0 倍。取 4.2 保证所有笔画都能收尾 - * (取小了尾部一段永远画不满),代价是笔画少的 S / V 会比 D / G 稍早停笔 */ -const CURTAIN_DASH_FACTOR = 4.2 +/** 每个字母"轮廓周长 ÷ 字宽"的估算比(大写,含内轮廓)。 + * 带碗形内圈的 G / D 要沿**外轮廓 + 内圈反轮廓两条闭合路径**走完,约为字宽的 5.4 倍; + * V 只有两条斜画、S 是一条蛇形画,约 3.0 ~ 3.5 倍。统一再留 ~12% 余量: + * 系数给小了内圈那段永远画不到(看着就是"边缘没填实"),给大了字母会提前停笔。 + * 各字母单算而不是取一个统一系数,是为了让五个字差不多同时收笔。 */ +const CURTAIN_LETTER_RATIO: Record = { G: 6.0, V: 3.8, S: 3.6, D: 6.0 } +/** 表里没有的字母按这个兜底(单条轮廓的常规估算) */ +const CURTAIN_DASH_FALLBACK = 3.6 /** 幕布露出这么多比例即起笔 */ const CURTAIN_DRAW_AT = 0.6 @@ -478,23 +481,23 @@ let curtainGlideUntil = 0 /** * 幕布初始化(挂载后跑一次): * ① 先等字体就绪——字体没加载完就量,量到的是回退字体的字宽,字母会排错位; - * ② 逐个字母量宽:按累计宽度排 x,并按估算的轮廓周长写 --dash(虚线的长与偏移初值); + * ② 逐个字母量宽:按累计宽度排 x,并按"字宽 × 该字母轮廓比"写 --dash(虚线的长与偏移初值); * ③ 按整组实测 bbox 设 viewBox,让五个字母整体铺满这半屏幕布,四周留一点边不贴框。 */ async function setupCurtain(): Promise { const svg = curtainSvg.value - const els = letterEls.filter((el): el is SVGTextElement => el !== null) - if (!svg || els.length === 0) return + if (!svg || letterEls.every((el) => el === null)) return try { await document.fonts.ready } catch { // 字体接口不可用就按当前字体量(退到系统无衬线,位置仍自洽) } let x = 0 - els.forEach((el) => { - const advance = el.getComputedTextLength() - el.setAttribute('x', String(x)) - el.style.setProperty('--dash', String(advance * CURTAIN_DASH_FACTOR)) + curtainLetters.forEach((ch, i) => { + const el = letterEls[i] + const advance = el ? el.getComputedTextLength() : 0 + el?.setAttribute('x', String(x)) + el?.style.setProperty('--dash', String(advance * (CURTAIN_LETTER_RATIO[ch] ?? CURTAIN_DASH_FALLBACK))) x += advance }) const box = curtainGroup.value?.getBBox() @@ -754,14 +757,16 @@ watch( /* 起笔:虚线偏移归零 → 描边沿着字形轮廓把笔画画出来;随后实心补上,回到原来的实心白字。 transition 只写在 .is-drawn 里:量完写 --dash 那一次改动不能带过渡, - 否则偏移会从 0 缓到量好的值(等于提前把字画一遍) */ + 否则偏移会从 0 缓到量好的值(等于提前把字画一遍)。 + 实心延迟 1.8s 是压在五个字都收完笔那一刻(系数留了 12% 余量,约九成时长收笔), + 让它们同时变实心;太早填会把还没画到的笔画盖住 */ .brand-curtain-letter.is-drawn { stroke-opacity: 1; stroke-dashoffset: 0; fill-opacity: 1; transition: stroke-dashoffset 2s cubic-bezier(0.33, 0, 0.55, 1), - fill-opacity 0.5s ease 1.65s; + fill-opacity 0.5s ease 1.8s; } /* 末尾占位:与幕布等高(半屏,svh 避开移动端地址栏收缩导致的跳动),透明 → 露出幕布 */