用 css 伪元素 ::after 实现红点:父容器设 position: relative,红点用 content: "" + background + border-radius: 50% 绘制,通过 data-unread 属性控制显隐,配合 transform: translate(50%, -50%) 精准定位右上角,移动端适配尺寸,状态变更仅改属性值。

怎么用纯 HTML + CSS 实现未读消息红点
纯 HTML 本身不支持“状态”或“动态计数”,红点必须靠 CSS 模拟,且需配合 HTML 结构标记“有未读”。常见错误是只写 <span class="badge">●</span>,结果红点位置飘、遮不住图标、响应式下错位。
- 红点本质是
::after伪元素 + 绝对定位,不是真实 DOM 节点 - 父容器必须设
position: relative,否则absolute会相对 body 定位 - 用
content: ""+background: #f00+border-radius: 50%画圆,别用字符 ●(字号缩放不一致、抗锯齿差) - 未读状态建议用
data-unread="1"属性控制显隐,比 class 切换更语义清晰
红点位置总偏移?检查这四个定位参数
红点贴在右上角但总差几像素,多半是 top/right 值没对齐图标配基准线。图标尺寸、行高、字体度量都会干扰。不要凭感觉调数值。
-
top: 0和right: 0是起点,但需配合transform: translate(50%, -50%)精准锚定右上角 - 若图标是
<i></i>或<svg></svg>,确保其 display 是inline-block或block,避免 inline 元素的基线对齐干扰 - 红点大小固定用
width: 8px; height: 8px,别用em或rem,否则随父级字体缩放失真 - 移动端需加
@media (max-width: 768px)把红点缩小到6px,不然小屏看着太突兀
怎么让红点自动消失?别用 JS 删除 DOM
很多人一看到“已读”就用 JS 移除 <span class="badge"></span>,结果动画断掉、React/Vue 更新异常。红点显隐应只靠 CSS 控制,状态交由属性驱动。
- 给父容器加
[data-unread="0"] .badge { display: none; },JS 只需改dataset.unread = "0" - 需要淡出效果?用
opacity+transition,别用display(无法过渡) - 如果红点要显示数字(如
9+),用content: attr(data-count),但注意attr()只能取字符串,99+需提前拼好再塞进data-count - 避免用
:empty判断——空格、换行符都会让元素不为空,不可靠
Vue/React 里红点不更新?检查属性绑定方式
框架里直接写 data-unread="{{ unread }}" 或 data-unread={unread} 很危险:值为 0 或 false 时属性可能被完全移除,导致 CSS 选择器失效。
- Vue 中用
:attr="{ 'data-unread': unread || '0' }",确保属性始终存在 - React 中用
data-unread={unread ? String(unread) : "0"},强制转字符串 - 别依赖
v-if或{unread && <badge></badge>}渲染红点组件——这会让 DOM 节点反复挂载/卸载,影响性能和动画连贯性 - 服务端渲染(SSR)时,初始
data-unread必须和首屏状态一致,否则 hydration 会警告或错乱
红点看着简单,但位置精度、状态同步、框架集成三处最容易翻车。尤其是 data-unread 属性是否始终存在、伪元素定位是否脱离文档流、以及响应式下的尺寸缩放,这三个点漏掉一个,就可能在某个机型或状态下彻底失效。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











