最近发现手机端访问首页时,标题「半知半解,且过且学!」的打字机特效走完之后,文字右侧被截断、显示不全,而桌面端一切正常。本文记录排查过程与修复方案。
问题描述#
- 桌面端:标题完整显示,打字机动画正常。
- 手机端:动画结束后右侧文字被裁掉,最后一个字不完整。
原因分析#
问题出在 assets/css/extended/custom.css 里打字机动画的宽度设计:
1
2
3
4
5
6
| .profile .profile_inner h1 {
max-width: 100%; /* 手机窄屏下宽度被压到容器宽(约 292~347px) */
width: 14em; /* 动画终点目标宽度,14em ≈ 448px,超出容器 */
overflow: hidden; /* 超出的部分直接裁剪 */
white-space: nowrap; /* 禁止换行 */
}
|
标题「半知半解,且过且学!」是 10 个全角字符 ≈ 10em,但动画把宽度写死成了 14em(可能是按 14 个字符设计的)。三个属性叠加导致窄屏截断:
- 桌面容器约 780px > 14em,文字完整;
- 手机容器约 292~347px < 14em,
max-width: 100% 把宽度压到容器值,右侧文字被 overflow: hidden 裁掉,而 nowrap 又禁止换行,于是文字就"丢"了。
注:这里和下面的数字都按出问题时的标题「半知半解,且过且学!」(10 字)记录。后来首页标题换成了「拾光入笔,岁月留痕」(9 字),数字换了但改法一致,见文末「后续改进」。
修复方案(保留手机端打字机效果)#
将 custom.css 中 .profile .profile_inner h1 至 @media (prefers-reduced-motion...) 整段替换为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
| .profile .profile_inner h1 {
max-width: 100%;
width: 10em; /* 与标题字符数一致:"半知半解,且过且学!" = 10 字符 */
overflow: hidden;
white-space: nowrap;
border-right: 1px solid currentColor;
animation: profile-title-typing 2.4s steps(10, end), profile-title-caret 0.75s step-end infinite;
}
@keyframes profile-title-typing {
from {
width: 0;
}
to {
width: 10em;
}
}
@keyframes profile-title-caret {
from,
to {
border-color: transparent;
}
50% {
border-color: currentColor;
}
}
/* 无障碍:系统开启"减少动态效果"时直接静态显示 */
@media (prefers-reduced-motion: reduce) {
.profile .profile_inner h1 {
animation: none;
width: auto;
max-width: 100%;
white-space: normal;
border-right: 0;
}
}
/* 手机端:缩小字号,保证 10em 的动画宽度能放下 */
/* 320px 屏容器约 292px,1.75em×10 = 280px,完整放得下 */
@media (max-width: 480px) {
.profile .profile_inner h1 {
font-size: 1.75em;
}
}
|
改动对照表#
| 项 | 原来 | 现在 | 原因 |
|---|
width | 14em | 10em | 精确等于文字宽度,不再白白占 4em |
steps() | steps(14, end) | steps(10, end) | 步数 = 字符数,每步显示一个字 |
| 动画时长 | 3.2s | 2.4s | 保持每字约 0.24s 的节奏 |
| 手机端 | 无 | font-size: 1.75em | em 随字号缩放,10em 同步变小,窄屏放得下 |
原理:width: 10em 与 keyframes 里的 10em 都基于元素自身字号,媒体查询里只改 font-size,动画终点宽度等比缩放,打字机效果在手机上依然保留且完整显示。
备选方案:手机端直接关闭动画#
若认为手机端不需要打字机效果,仅追加以下媒体查询即可(标题自动换行、永不被裁):
1
2
3
4
5
6
7
8
| @media (max-width: 768px) {
.profile .profile_inner h1 {
width: auto;
white-space: normal;
border-right: 0;
animation: none;
}
}
|
后续改进:字符数改为自动生成#
上面的方案修好了截断,但留了个隐患:width 和 steps() 里的数字必须和标题字数保持一致,标题一改就得回来手动同步,很容易忘。
后来改成由模板按标题实际字数注入 CSS 变量,custom.css 里只留兜底值。
layouts/partials/extend_head.html 在首页注入:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| {{- /* 首页标题打字机:按标题实际字数生成动画参数,
避免与 custom.css 里的兜底值不同步(1 个全角字按 1em 计)。 */ -}}
{{- if .IsHome }}
{{- with site.Params.profileMode }}
{{- if .enabled }}
{{- $chars := strings.RuneCount (.title | default site.Title) }}
<style>
.profile .profile_inner h1 {
--type-chars: {{ $chars }};
--type-ease: steps({{ $chars }}, end);
}
</style>
{{- end }}
{{- end }}
{{- end }}
|
custom.css 里改成全部走变量:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| /* 首页标题打字机
--type-chars / --type-ease 由 extend_head.html 按 profileMode.title 的字数注入,
下面两行只是兜底值,改标题后不需要再手改这里。 */
.profile .profile_inner h1 {
--type-chars: 9;
--type-ease: steps(9, end);
max-width: 100%;
width: calc(var(--type-chars) * 1em);
overflow: hidden;
white-space: nowrap;
border-right: 1px solid currentColor;
animation: profile-title-typing calc(var(--type-chars) * 0.24s) var(--type-ease),
profile-title-caret 0.75s step-end infinite;
}
@keyframes profile-title-typing {
from {
width: 0;
}
to {
width: calc(var(--type-chars) * 1em);
}
}
|
几个要点:
strings.RuneCount 数的是字符数而不是字节数,中文标题一个字算 1;- 全角字宽约等于 1em,所以
--type-chars 可以直接当 em 用:width: calc(var(--type-chars) * 1em); steps() 同样能吃变量(--type-ease: steps({{ $chars }}, end)),动画时长也按字数算(calc(var(--type-chars) * 0.24s)),维持每字约 0.24s 的节奏;- 模板只在首页、且
profileMode.enabled 时输出这段 <style>,其它页面不会带上无用样式; custom.css 里那两行兜底值保留的意义是:万一模板没注入,动画仍然能跑,不至于整个特效消失。
于是「改标题要同步改数字」这件事就不存在了。
验证方法#
- 浏览器
F12 → 设备模拟(手机图标); - 选择 iPhone 尺寸(375px)或更窄的 320px 视口;
- 刷新首页,等待打字机动画结束,确认标题完整显示;
- 桌面端确认打字机效果无回归。
注意事项#
- 标题文字修改后,需同步更新
10em 和 steps(10, end) 中的数字为新字符数。这一条已废弃:字符数现在由 extend_head.html 自动注入(见「后续改进」),改标题不用再动 CSS; - 若自定义了
--main-width 或页面内边距,仍要重新核算窄屏容器是否放得下标题(标题宽度 ≈ 字数 × font-size;本站标题 9 字,480px 以下 font-size: 1.75em)。