JSON是更可持续的选择,建议用fetch加载本地achievements.json文件,结构清晰且易于维护复用;字段应包含id、title、desc、icon和unlocked,图标统一存icons/目录下用img引用,避免内联臃肿。

徽章数据用 JSON 还是硬编码在 HTML 里?
直接写死在 里看着快,但加新徽章、改描述、调顺序就得翻 HTML,协作和维护全崩。JSON 是更可持续的选择——哪怕只是本地 achievements.json 文件,用 fetch() 加载,结构清晰,前端逻辑也容易复用。
- 徽章字段建议至少包含:
id(唯一,用于状态判断)、title、desc、icon(SVG 字符串或 CDN 路径)、unlocked(布尔值,决定是否加 unlocked class)
- 不要用
localStorage 存整个徽章数组来模拟“已获得”,它没持久同步能力;真要存状态,优先用 localStorage.setItem('ach_123', 'true') 单 key 控制,避免序列化/解析出错
- 如果徽章图标用 SVG,别直接贴大段内联 SVG 到 HTML;统一放在
icons/ 目录下,用 
,加载失败有 fallback,也方便换主题色
徽章“未解锁”状态怎么视觉降级又不丑?
纯灰度 + 透明度降低是最常见错误:设 opacity: 0.4 后文字可读性暴跌,且无法点击时连 hover 都失效。正确做法是保留完整 DOM 结构,只改视觉权重。
- 用
filter: grayscale(70%) brightness(0.85) 替代 opacity,图标和文字仍保持清晰边缘
- 加一层半透黑色遮罩
::before,但只对 .badge--locked 生效,同时设 pointer-events: none 确保不影响下方元素交互
- “解锁”动效别用 JS 手动 addClass + setTimeout 模拟;用 CSS
transition 控制 transform 和 filter,例如:.badge { transition: transform 0.3s ease, filter 0.3s ease; },JS 只需切换 unlocked class
响应式徽章墙在小屏上撑开横向滚动怎么办?display: grid 布局在移动端默认单列没问题,但一旦设了 grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)),部分安卓 WebView 或旧版 Safari 会错误计算 minmax,导致列宽溢出视口。
- 改用
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr))(注意是 auto-fit),它会收缩空列,兼容性更好
- 必须加
grid-auto-flow: dense 防止空隙卡住布局
- 在容器上加
overflow-x: auto 和 -webkit-overflow-scrolling: touch,但关键是要限制最大宽度:max-width: 100vw,否则横向滚动条会藏在 viewport 外边,用户根本拉不到
徽章点击后弹窗详情,为什么 event.stopPropagation() 总被漏掉?
徽章墙常套在可拖拽容器或带全局点击关闭的 modal 外层里。点徽章触发详情弹窗,结果弹窗一闪就关——八成是父级监听了 document click 并无差别地关闭所有浮层,而徽章内部的 click 事件冒泡上去了。
- 弹窗按钮的
onclick 或事件监听里,必须写 event.stopPropagation(),不能只靠 event.preventDefault()
- 更稳妥的做法:给徽章容器加
data-no-close="true",然后在全局 click handler 里判断:if (e.target.closest('[data-no-close]')) return;
- 弹窗本身用
position: fixed,但记得加 inert 属性到背景内容区域(如 document.body.setAttribute('inert', '')),防键盘焦点穿透,这是无障碍和实际体验的关键细节
徽章墙看似简单,真正卡住人的从来不是“怎么画一个徽章”,而是状态同步时机、CSS 布局容错边界、以及事件流里那些看不见的冒泡路径。尤其当你要加成就进度条、分享按钮或离线缓存时,这些点会立刻暴露出来。
achievements.json 文件,用 fetch() 加载,结构清晰,前端逻辑也容易复用。
- 徽章字段建议至少包含:
id(唯一,用于状态判断)、title、desc、icon(SVG 字符串或 CDN 路径)、unlocked(布尔值,决定是否加unlockedclass) - 不要用
localStorage存整个徽章数组来模拟“已获得”,它没持久同步能力;真要存状态,优先用localStorage.setItem('ach_123', 'true')单 key 控制,避免序列化/解析出错 - 如果徽章图标用 SVG,别直接贴大段内联 SVG 到 HTML;统一放在
icons/目录下,用,加载失败有 fallback,也方便换主题色
徽章“未解锁”状态怎么视觉降级又不丑?
纯灰度 + 透明度降低是最常见错误:设 opacity: 0.4 后文字可读性暴跌,且无法点击时连 hover 都失效。正确做法是保留完整 DOM 结构,只改视觉权重。
- 用
filter: grayscale(70%) brightness(0.85) 替代 opacity,图标和文字仍保持清晰边缘
- 加一层半透黑色遮罩
::before,但只对 .badge--locked 生效,同时设 pointer-events: none 确保不影响下方元素交互
- “解锁”动效别用 JS 手动 addClass + setTimeout 模拟;用 CSS
transition 控制 transform 和 filter,例如:.badge { transition: transform 0.3s ease, filter 0.3s ease; },JS 只需切换 unlocked class
响应式徽章墙在小屏上撑开横向滚动怎么办?display: grid 布局在移动端默认单列没问题,但一旦设了 grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)),部分安卓 WebView 或旧版 Safari 会错误计算 minmax,导致列宽溢出视口。
- 改用
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr))(注意是 auto-fit),它会收缩空列,兼容性更好
- 必须加
grid-auto-flow: dense 防止空隙卡住布局
- 在容器上加
overflow-x: auto 和 -webkit-overflow-scrolling: touch,但关键是要限制最大宽度:max-width: 100vw,否则横向滚动条会藏在 viewport 外边,用户根本拉不到
徽章点击后弹窗详情,为什么 event.stopPropagation() 总被漏掉?
徽章墙常套在可拖拽容器或带全局点击关闭的 modal 外层里。点徽章触发详情弹窗,结果弹窗一闪就关——八成是父级监听了 document click 并无差别地关闭所有浮层,而徽章内部的 click 事件冒泡上去了。
- 弹窗按钮的
onclick 或事件监听里,必须写 event.stopPropagation(),不能只靠 event.preventDefault()
- 更稳妥的做法:给徽章容器加
data-no-close="true",然后在全局 click handler 里判断:if (e.target.closest('[data-no-close]')) return;
- 弹窗本身用
position: fixed,但记得加 inert 属性到背景内容区域(如 document.body.setAttribute('inert', '')),防键盘焦点穿透,这是无障碍和实际体验的关键细节
filter: grayscale(70%) brightness(0.85) 替代 opacity,图标和文字仍保持清晰边缘::before,但只对 .badge--locked 生效,同时设 pointer-events: none 确保不影响下方元素交互transition 控制 transform 和 filter,例如:.badge { transition: transform 0.3s ease, filter 0.3s ease; },JS 只需切换 unlocked classdisplay: grid 布局在移动端默认单列没问题,但一旦设了 grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)),部分安卓 WebView 或旧版 Safari 会错误计算 minmax,导致列宽溢出视口。
- 改用
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr))(注意是auto-fit),它会收缩空列,兼容性更好 - 必须加
grid-auto-flow: dense防止空隙卡住布局 - 在容器上加
overflow-x: auto和-webkit-overflow-scrolling: touch,但关键是要限制最大宽度:max-width: 100vw,否则横向滚动条会藏在 viewport 外边,用户根本拉不到
徽章点击后弹窗详情,为什么 event.stopPropagation() 总被漏掉?
徽章墙常套在可拖拽容器或带全局点击关闭的 modal 外层里。点徽章触发详情弹窗,结果弹窗一闪就关——八成是父级监听了 document click 并无差别地关闭所有浮层,而徽章内部的 click 事件冒泡上去了。
- 弹窗按钮的
onclick 或事件监听里,必须写 event.stopPropagation(),不能只靠 event.preventDefault()
- 更稳妥的做法:给徽章容器加
data-no-close="true",然后在全局 click handler 里判断:if (e.target.closest('[data-no-close]')) return;
- 弹窗本身用
position: fixed,但记得加 inert 属性到背景内容区域(如 document.body.setAttribute('inert', '')),防键盘焦点穿透,这是无障碍和实际体验的关键细节
onclick 或事件监听里,必须写 event.stopPropagation(),不能只靠 event.preventDefault()
data-no-close="true",然后在全局 click handler 里判断:if (e.target.closest('[data-no-close]')) return;
position: fixed,但记得加 inert 属性到背景内容区域(如 document.body.setAttribute('inert', '')),防键盘焦点穿透,这是无障碍和实际体验的关键细节徽章墙看似简单,真正卡住人的从来不是“怎么画一个徽章”,而是状态同步时机、CSS 布局容错边界、以及事件流里那些看不见的冒泡路径。尤其当你要加成就进度条、分享按钮或离线缓存时,这些点会立刻暴露出来。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











