基于真实 DOM 还原 Skeleton 的工程思考
在前端开发中,Skeleton(骨架屏)是提升用户感知性能的重要手段。常见做法是单独写一套灰色占位组件,但这种方法存在一些明显的问题: 骨架屏和真实 UI 结构脱节,修改 UI 需要额外同步 skeleton; 文字行高和布局难以精准还原,容易出现错位; 页面渲染过程中容易出现布局抖动(CLS)。 本文分享我在组件库中对骨架屏的思考,以及如何通过与组件耦合的方式实现精准还原。 --- 传统 Skeleton 的问题 常见的实现方式是页面层写一套假的 Skeleton,例如: 这种方式存在几个问题: 结构脱节\ \ 骨架屏和真实组件是两个平行的版本,修改组件必须同步改 skeleton。 布局不精确\ \ 文字、行高、padding、响应式高度等都可能不一致,导致骨架屏和实际内容错位。 维护成本高\ \ 组件库或页面升级时,需要额外维护 skeleton 组件。 --- 我的方法:Skeleton 与组件结构耦合 我的核心思路是: 骨架屏应该是组件的一种状态,而不是独立组件。
从 "组件外也能改 Zustand 状态" 聊到它的底层原理
这次对 Zustand 的理解升级,其实是从一个很简单的问题开始的: 为什么我可以在组件外面通过 修改状态,而且组件里的值还能自动更新? 一开始我一直以为 Zustand 是基于 React Context 实现的。既然是基于 Context,那它应该只能在组件树里用,离开组件就没法工作。但实际测试下来发现: 在组件外调用 完全没问题 组件内通过 订阅的值也会自动更新 根本不需要 Provider(在默认用法下) 这让我意识到,我对 Zustand 的理解是错的。 一、Zustand 不是基于 React Context 很多人(包括我)会自然地把“全局状态管理”跟 React Context 绑定在一起。 但 Zustand 的默认实现,其实完全不依赖 React Context。 如果你这样创建一个 store: 这个 做的事情本质上是: 在内存里创建一个 store 对象 这个对象内部维护: 当前 state 一组 listeners(订阅者) getState setState subscribe
从 shadcn/ui Button 到可定制 loading 的业务按钮
在前端开发中,按钮是最常用的组件之一。shadcn/ui 提供了默认的 Button 组件,但在实际业务中,它存在一些不足: 默认按钮没有 loading 状态 无法阻止重复点击 loader 样式无法自定义,不适应不同主题 为了解决这些问题,我在 shadcn/ui Button 的基础上,封装了一个业务 Button 组件。 组件设计 核心目标: 增加 loading 状态 当按钮正在处理请求时显示 loader 自动禁用按钮,防止重复提交 支持 loader 样式自定义 通过 可以修改 loader 样式,例如深色模式下反色 继承原生 Button 属性 保留所有原生属性,如 , , 安全点击逻辑 当按钮处于 loading 或 disabled 时,阻止点击事件 核心代码 使用示例 为 true 时显示 loader 按钮禁用,防止重复点击 loader 样式可通过 自定义 总结 这个业务 Button 组件解决了 shadcn/ui 默认 Button 的不足: 增加 loading 状态 loader 可自定义样式 自动禁用点击,
秒杀系统中的前端倒计时设计
秒杀活动倒计时,看起来很简单:几秒、几分钟倒计时而已。但实际上,如果设计不好,会出现倒计时跳秒、提前结束、或者被用户改本地时间作弊的情况。今天就聊聊前端秒杀倒计时的思路和最佳实践。 倒计时的核心目标 准:显示的时间尽量和服务器同步,不能因为本地时间不准就“提前开始”或者“晚结束”。 顺滑:数字滚动、翻牌动画要流畅,不要跳秒。 安全:倒计时只是给用户看的,不能决定是否能下单,真正权限还是服务器说了算。 时间基准怎么搞 别完全靠本地时间,像 这种,用户一改系统时间就不准了。 靠谱做法:加载页面的时候拿一次服务器时间,然后算个偏差: 如果想更精准,还可以用 SSE 或 WebSocket,服务器实时推送时间,倒计时就完全跟服务器走。 SSE授时的典型实现 服务端 (Node.js 示例) 前端 (React 示例)
小小倒计时,没那么简单
倒计时是一个非常常见的 UI: 活动上线倒计时 发布会倒计时 秒杀系统倒计时 看起来只是简单地显示: 但当你真正实现一个体验不错的倒计时页面时,其实有不少细节需要考虑。 数字要等宽显示(tabular-nums) 这是一个我最近才注意到的小细节。很多字体里,数字宽度其实是不同的。 如果你给数字加了动画,倒计时变化时,容器的宽度会变化,导致 抖动感。 CSS 其实提供了一个专门的属性: 作用是: 让所有数字使用等宽排列。 也就是: 每个数字占用的宽度一致。 如果你使用 Tailwind,使用 这个属性需要字体的支持,并不是所有字体都支持这个属性 时区问题 倒计时最容易出问题的其实是 时区。 比如活动时间是: 这其实是一个 不完整的时间信息,因为缺少时区。 如果你直接写: 浏览器会默认按 用户本地时区解析。 如果你的用户分布在不同地区: 北京 东京 纽约 倒计时就可能全部不一样。 更安全的方式 使用带时区的时间(ISO 8601 格式) ISO 8601 时间格式是国际标准化组织制定的日期和时间表示法,标准格式为 。它通过 分隔日期与时间,