
本文介绍一种基于 CSS ::after 伪元素与 JavaScript 状态控制的优雅方案,实现仅在当前正在输入的段落末尾显示单个闪烁光标,避免多光标重叠或错位问题。
本文介绍一种基于 css `::after` 伪元素与 javascript 状态控制的优雅方案,实现仅在当前正在输入的段落末尾显示单个闪烁光标,避免多光标重叠或错位问题。
在实现「打字机效果」时,一个常见但棘手的需求是:光标(caret)必须严格跟随当前正在输入的文本末尾,且仅出现在该行,不跨行、不残留、不漂移到行右边界。原方案中直接对 应用 border-right 动画,会导致光标始终锚定在整行容器右侧(因块级元素默认占满宽度),而非动态文本末端;同时所有 共享样式,造成多光标叠加。
✅ 正确解法:伪元素 + 精准状态控制
核心思路是放弃直接修饰 元素本身,改用 ::after 伪元素作为光标载体,并通过 CSS 选择器动态控制其显示逻辑:
- 使用
display: inline-block让伪元素紧贴文本末尾(继承p的内联流式布局); - 利用
:not(:empty)排除空段落(避免初始闪烁); - 利用
:not(.complete:not(:last-child))实现关键逻辑:
→ 已完成的段落(.complete)不显示光标;
→ 除非它是最后一段(:last-child),此时即使完成也保留光标(体现“输入结束态”)。
? JavaScript 改进点
需在每段打字完成时添加 complete 类,触发 CSS 隐藏逻辑:
const paragraphs = document.querySelectorAll('#str p');
let delay = 0;
paragraphs.forEach(p => {
const text = p.textContent;
p.textContent = '';
let index = 0;
function typeWriter() {
if (index <h3>? CSS 光标样式(精简可靠)</h3><pre class="brush:php;toolbar:false;">#str {
padding: 5px;
font-family: sans-serif;
font-size: 15px;
margin: 5px auto;
color: #000;
font-weight: bold;
}
/* 关键选择器:仅作用于非空、且(未完成 或 是最后一段)的 p */
#str p:not(:empty):not(.complete:not(:last-child)):after {
content: "";
display: inline-block;
height: 1em; /* 与字体行高一致 */
border-right: 0.15em solid orange;
vertical-align: bottom; /* 对齐基线,避免下沉 */
animation: blink-caret 0.5s step-end infinite;
}
@keyframes blink-caret {
from, to { border-color: transparent; }
50% { border-color: orange; }
}
⚠️ 注意事项
-
不要给
设置display: inline:这会破坏换行需求;伪元素天然支持inline-block,无需改动父元素显示模式。 -
vertical-align: bottom很重要:防止光标因基线对齐而下移,确保与文字底部平齐。 -
动画使用
step-end:保证光标闪烁为“开关式”(非渐变),符合传统终端光标行为。 - 若需响应式适配,建议将
height和border-right改为em单位(如示例),使其随字体缩放。
此方案兼顾语义清晰性、CSS 可维护性与视觉精确性,是现代打字效果中光标控制的推荐实践。










