跳到正文

Sheng's blog

CSS 下划线为什么会在字母下断开

我在 Markdown 阅读器里审一篇 TypeScript 文章时,发现目录链接的下划线总在 y 和 p 下面断开。切到博客,悬停时出现的下划线却能从字尾下方连着画过去。我以为博客用的是底边框,打开开发者工具才发现,两处都用原生下划线。当时阅读器的偏移是 0.17em,博客引入的样式用了 0.2rem。 这三行用的是同一套环境。Windows Chrome 157、设备像素比(DPR)2、悠哉常

用 font-synthesis 补齐缺失的粗体

博客换成悠哉以后,深色正文里的加粗词很容易扫过去。我用字体扩展给 GitHub 也换了悠哉,那里的粗体却很显眼。两边都请求较高的字重,我就用一句话比较这五个加粗的字。 雨停以后,窗边的书页被风轻轻翻过。 浅色和深色都用 16px,每种主题的正文和粗体用同一个颜色。Light 搭配 Medium 或 Regular 时,差别偏轻。用 Regular 合成粗体后,「窗」和「页」的笔画间隙更紧,但这五个

我所理解的 Hexo 最佳实践

文章多起来以后,我在 Hexo 里遇到的麻烦逐渐从「怎么生成一个页面」变成了「怎么继续整理已经发出去的内容」。中文文件名适合找稿,公开地址要照顾读者收藏的链接。图片在编辑器里应该能直接打开,连载也应该能从首页总览、归档和搜索分别进入。 由于篇幅问题,全文分成八部分来讲。 第一篇《用文件名管理文章,用稳定字段生成网址》从编辑器里难找旧稿说起,把中文文件名、发布日期和公开网址交给各自的字段,再用一篇旧

我所理解的 UI 设计最佳实践

我做前端时,经常遇到一种说起来简单、改起来很慢的问题。页面已经能用了,我却觉得哪里有点怪。让 Agent 调一版,它可能换了颜色、减了间距,真正妨碍使用的地方却还在。等我能指出自己刚才在做什么、在哪里被打断,修改通常就容易多了。 这个系列记录我在做博客、内部页面和桌面工具时,逐渐形成的设计判断。产品里的真实操作、公开设计稿里的局部做法,以及改完之后的实际表现,都可以拿来对照。 第一篇《把界面反馈说

我所理解的前端框架(React 和 Vue)最佳实践

我用 React 和 Vue 写页面时,经常会遇到相似的问题。组件怎么组织、状态放在哪里、交互怎么验证,很多判断可以放在一起谈。我把两个框架的实践放在同一个系列里,遇到共通的问题就先讲做决定的依据,再看各自的 API、组件库和工具怎样影响具体做法。 目前有两篇,可以按手头的问题选读。 第一篇《用表格组件减少通用渲染和交互的维护成本》先区分通用表格能力与项目的业务规则,再分别给出 Vue 和 Rea

我所理解的代码风格最佳实践

改一段陌生代码时,我通常先找到触发它的按钮、调用点或报错,再沿着名字和引用往里走。这个过程有时很顺,有时得先猜第三个参数的意思,再经过一层转发,最后还要弄清两个叫 profile 的值分别是谁的。写代码时省下的几行,维护时可能要花好几次跳转才能补回来。 我理解的代码风格,包含命名、调用方式、模块的公开入口,也包含状态放在哪里、目录怎样表达归属。格式可以统一交给工具。剩下的选择,我通常用一个具体问题

我所理解的 JavaScript 最佳实践

代码写下来之后,大部分时间是用来读的。排查一条记录为什么排错了位置,要先看列表沿着哪一份输入走。审查一次批量提交的改动,要找出刷新到底等了哪些请求。能在调用处找到答案,就少一次跳转,也少一个靠猜测补上的前提。 Agent 读代码很快,依然需要判断哪些顺序属于业务约定、哪些值必须共享、哪些分支会写入外部系统。这些关系能从代码里直接看出来,Agent 修改时就有更明确的边界,人工 Review 也更容

我所理解的 Markdown 最佳实践

一篇技术文档通常要经过几次交接。作者在编辑器里写,读者在网页上看,有人把示例复制到项目里,审阅者再把意见交回给作者。Markdown 让这些人可以围绕同一份文本工作,也让解析、检查和批注各有了自己的边界。 由于篇幅问题,全文分成六部分来讲。 第一篇《中文加粗为什么会露出星号》从露在页面上的双星号说起,看看中文标点怎样影响 CommonMark 的强调边界。 第二篇《给 Markdown 文档接入

大型后台管理系统如何把业务操作做可靠

大型后台管理系统看起来很简单。查一笔订单,改几个字段,勾一批商品点下架。开发过一些后台,也用过一些后台之后,我们发现,许多需要反复推敲的地方,都藏在这些熟悉的操作后面。 客服点了退款,页面提示超时。他需要知道钱退了没有,才能回复客户。与此同时,这个入口还要被不同团队开发、联调和发布。组件需要让客服看懂结果,接口需要让页面查到结果,灰度发布则要让旧页面也能继续处理同一笔退款。 以多商户经营后台为例。

我所理解的 TypeScript 最佳实践

写 TypeScript 时,我通常让编译器推断局部变量和简单函数的返回值。到了接口、组件 props、模块导出这些边界,我会把约定写得更清楚:数据从哪里来,哪些字段可能缺失,调用方可以传什么。类型本身也有边界,外部数据需要在运行时校验,工具脚本则需要一条确实会执行的检查命令。 由于篇幅问题,全文分成六部分来讲。 第一篇《为什么选择 TypeScript》从浏览器脚本、Ajax 和 Node.js

给荣耀手机自定义一套线条图标主题

不知道有没有人像我一样,喜欢极简的风格,而手机上花花绿绿的图标让人看起来很是混乱。 好在 Android 往往提供图标自定义功能,我们可以选择第三方图标库来替换掉默认的。但这些图标库往往不能覆盖所有应用,这就导致有些应用图标还是原来的彩色样式。另外就是商店里的图标库不一定满足自己的想法,有的太小,有的太大。针对这个场景,我开始自己研究做一个图标库。 我想保留荣耀自带的桌面,就找了一套现成的线条图标

从 Node.js 到 FastAPI

会用 Express 或 Koa,再读 FastAPI 的接口,路由方法并不陌生。真正需要重新建立认识的是处理函数的调用约定:没有固定的 req、res 参数,函数可以直接声明登录用户、数据库会话、查询参数和模型请求体。框架从哪里取得这些值,又在什么时候执行校验、依赖和响应处理,是这组文章围绕的问题。 这里选择官方 Full Stack FastAPI Template 作为具体项目。它有用户、登

滚动卡顿复盘:隐藏浮层为什么还在定位

阅读掘金小册时遇到一个滚动明显卡顿的页面。页面上没有展开菜单,滚动期间却能看到浮层定位函数持续执行。当次检查中,7 个已经隐藏的浮层仍在监听滚动。 沿实例创建过程继续追踪,可以定位到页面所用组件封装的一条生命周期缺口:隐藏元素的尺寸通知进入更新方法,在未检查显示状态的情况下创建 Popper,并开启滚动监听。Tooltip 关闭、销毁后,同一条路径还可能把实例重新创建回来。 谁在滚动时更新当次检查

把缓存放在计算之前:从 Workers Cache 到 Node.js 实现

给动态页面加 CDN 时,一个很自然的期待是:第一次访问生成页面,后续访问直接返回已经生成的 HTML。页面内容几分钟都没变,查数据库、拼数据、渲染模板就不必每次重做。 但应用部署成 Cloudflare Worker 后,过去常见的缓存方式仍然需要先运行 Worker,再由代码查缓存。缓存虽然命中了,函数入口却照常执行。 看到 Workers Cache 的介绍,吸引我的就是这个变化:缓存可以放

用 Vue 3 封装原生 dialog 和 popover

编辑资料时,需要一个阻止背景操作的弹窗;邮箱旁边的说明按钮,又只需要展开一小段提示。这两种界面都浮在页面上,但对焦点、关闭方式和背景交互的要求不同。以前写组件,要自己处理这些行为,再借助 Teleport、遮罩和 z-index 解决显示位置。 浏览器提供的 <dialog> 和 Popover API 已经接走了其中一部分工作。模态 dialog 可以让背景变为不可交互,popover 可以原生

Vue 3.6 Vapor 迁移实践:编译、测试与回归

这次把现有 Vue 项目从 3.5.44 迁到 3.6.0-rc.14,并在独立界面启用 Vapor。迁移涉及组件编译、应用入口、测试运行时和原有交互的回归,原来的业务处理可以继续保留。 最初从一个小型 Popup 开始,先验证兼容性,再把同一流程用于其他界面。后续又迁移了使用原生滚动条库的字体 Popup,以及保留 Nuxt、Element Plus 和 Reka 组件的搜索站。前者能完整采用纯

ESM 产物为什么还有 require:CommonJS、external 与宿主加载器

把 format 设为 esm 后,程序仍可能因为 Dynamic require 或找不到模块而失败。需要先看那一段依赖已被内联,还是只保留了留给宿主的引用。下面用固定的 esbuild/Node 实验区分模块格式、依赖实现与加载器;受限边缘运行时另按它实际提供的能力判断。 普通 CommonJS 可以内联,原生模块通常外置,动态路径还需要运行时资源与加载器。ESA 作为受限宿主的例子,单独核对

Node 程序怎样构建与交付:从本地链接到独立运行

在开发机上能运行一个 Node 程序,并不代表把入口文件发给别人也能运行。本地链接、生产依赖、模板文件与运行时都可能留在开发仓库里。这里从交付要求区分语法转换、依赖打包和发布包装,再用隔离目录中的固定实验检查产物究竟带齐了什么。 Node 能直接执行代码,为什么还要构建平时说「构建」,很容易把几种事情混在一起。 操作 解决什么问题 是否把依赖合进一个文件 语法转换 把 TypeScript、JSX

第三方页面适配:CSS 先处理外观,JS 再修正行为

打开一段异步加载的评论,某个词先以蓝色链接出现,随后又变成普通文字。最终结果没有错,但这一下颜色变化很显眼。深色弹窗先露出白底、搜索框先出现推荐词、内部滚动条先显示原生样式,也可能属于同一类问题:目标样式要等脚本发现节点、判断类型、写入标记之后才能生效。 做浏览器扩展和用户脚本时,我们通常不能修改原站组件,却可以提前声明样式。只要节点原本就带着足够的识别信息,外观便可以直接交给 CSS;需要解析地

浏览器能打开,Python 却报证书错误:一次 HTTPS 证书链排障

整理博客资料时,Matrix67 在浏览器里能打开,Python 却报证书校验失败。排查发现服务器只发送了站点证书,客户端需要另找中间证书才能构建可信路径。记录 OpenSSL 取证、系统证书库与原生链构建的区别,以及使用 truststore 后默认 Python 也恢复成功的复测变化,说明服务端补链、客户端兼容和关闭验证之间的边界。

Chrome executeScript 为什么拿不到外层变量:注入函数的序列化边界

扩展的 Popup 或 service worker 里定义了一个常量,再把用到它的函数交给 chrome.scripting.executeScript(),编辑器没有报错,普通函数调用也正常。真正注入页面时,却出现 ReferenceError。 问题出在执行边界:Chrome 传过去的是函数的序列化结果,原来模块的词法环境不会一起进入目标页面。 看起来是回调,实际已经离开模块const st

模块入口与 import 后缀:公开 API 和运行环境要分别判断

import 路径同时表达两件事:调用方从哪个公开入口拿能力,以及运行环境怎样找到文件。把这两件事混在一起,容易把「所有地方都不写后缀」或「每个目录都建 barrel」当成统一规范。 我主导的项目里,会用目录入口管理稳定模块公开面;前端 bundler 源码通常省略本地源码后缀。维护其他项目时,先沿用它已有的明确规范和稳定写法。Node ESM 的解析要求则需要单独满足。 用 index.ts 声

单行省略为什么会裁掉字:line-height、字形和 overflow 的边界

昵称和表格单元格常用单行省略。有时省略号正常出现,字母底部或重音却少了一截。开发者容易先改宽度,甚至换成 JavaScript 截字符串;真正需要看的往往是行高和裁切区域。 省略号只处理行内溢出常见的 truncate 对应 overflow: hidden、text-overflow: ellipsis 和 white-space: nowrap。它解决的是横向空间不足,没有承诺字形在垂直方向一

环境变量怎么管理:共用配置、个人覆盖与框架差异

一个项目里既有团队共用的 API 地址,也有个人调试覆盖和部署密钥。把它们都交给一份 .env,会混淆归属;照搬另一套框架的文件名,又可能遇到不同加载顺序和公开规则。 由于篇幅问题,全文分成 2 部分来讲。 第一篇《共用默认值、个人覆盖与部署配置》介绍了配置归属、共享默认值、本机覆盖、启动加载、Git 保护和构建/运行时差异,并用 Vite 与 Nuxt 的相同输入检查实际结果。 第二篇《前后端框

从搜索动效原型到 Nuxt 应用:滚动、正文渲染和工程边界

做这个搜索工具时,我先找了一份喜欢的动效参考:The Collaboratory — Refine Search。输入框从首页移到结果页,资料旁边有相关、不相关和收藏操作,选中的资料再进入细化面板。这套交互比单纯堆一个搜索框和列表更有意思,适合做一个可以逐步整理线索的小工具。 演示数据很容易把效果撑起来。真正接上搜索接口,问题才陆续出现:一篇长摘要让整个弹窗开始滚动,标题和关闭按钮一起消失;上游文

固定输入框里一打字,页面就往上跳:scroll-padding 与焦点滚动

搜索结果页往下滚了一段,顶部搜索框仍然可见。点进去补几个字,下面的结果却跟着往上跳;继续输入,页面会一步步退回顶部。 这个问题出现在一个 Nuxt 搜索页面里。起初值得怀疑的地方不少:输入会更新字符计数,筛选条件显示成标签,Header 使用 sticky,页面还接了 OverlayScrollbars。测量以后发现,搜索框的位置和尺寸一直没变,变的是文档的 scrollY。 最终修复只有一行:删

桌面快捷方式为什么找不到脚本:一次 MSIX 文件系统虚拟化排查

给 Windows 上的 WSL 装好 KDE 桌面后,我希望像打开普通软件一样,双击桌面的「Ubuntu KDE Plasma」就能进入 Linux 桌面。启动器负责唤起后台服务,再用 Edge 的独立窗口打开 noVNC。 桌面本身已经能用了,快捷方式却反复出问题:最早提示「无法找到脚本文件」,换成 CMD 启动器后,又表现为双击没有反应,或者命令窗口一闪而过。奇怪的是,让 Codex 执行同

在 WSL 中用 noVNC 操作 KDE Wayland:一次嵌套桌面的实践

在 Windows 上装好 WSL 和 KDE,再把桌面放进浏览器,看起来已经很接近一台普通的 Linux 电脑:有任务栏,可以打开应用,也可以最小化、最大化窗口。 但在 4K 屏幕上用起来,问题接连出现。画面铺满了,字却不一定清楚;分辨率提高了,按钮又变得很小;在 KDE 设置里修改缩放,关闭 noVNC 再打开,界面还是老样子。 我因此想把原来的 KDE X11 会话换成 Wayland,继续

Regex Tuesday 题解

Regex Tuesday 是 Callum Macrae 做的一组在线正则练习。每道题都有一组输入和预期结果,可以直接在页面里填写正则,看哪些用例通过、哪些没有通过。题型分为匹配和查找替换两类,内容从重复单词、日期、URL,到元素符号、音乐和国际象棋记号,适合用来练习把文字规则变成正则。 偶然发现这个网站以后,我把里面的 20 道题逐一做完,也记录了解题时用到的规则和踩过的坑。最新一题发布于 2

浏览器如何识别网页、图片和下载:MIME、Content-Type 与内容嗅探

浏览器收到响应后,怎样决定渲染网页、展示图片,还是下载文件?MIME 类型提供声明,内容嗅探处理部分缺失或错误信息,加载场景与安全规则继续限制使用方式。结合 Chrome 实验,可以看清扩展名、类型响应头、下载指令和文档属性各自的作用,解释脚本类型报错、图片解码失败与上传校验中的常见误区。

DNS 之前,浏览器在忙什么:Chrome Omnibox 原理与自定义

地址栏输入一段文字后,浏览器需要判断导航与搜索意图,组织历史、书签、搜索和扩展候选,再执行用户确认的动作。这个过程决定了 DNS 与页面加载之前的目标选择,也决定了自定义入口应该接在哪一层。机制篇保留输入分类、候选排序和协议分流,实践篇集中说明站点快捷字、OpenSearch、扩展建议与企业策略的适用边界。

设计组件库时需要考虑什么:从通用 UI 到业务组件

面试里问「如果让你设计一个组件库,需要考虑什么」,很容易从 TypeScript、打包工具、按需加载一路讲到单元测试。这些都需要考虑,但在选工具之前,还有一个决定后续设计的问题:准备把什么东西复用起来? 按钮、下拉框复用的是视觉和交互;查询表格复用的是查询、分页、排序等页面模式;店铺选择器复用的则可能是店铺身份、可选范围、历史值回显和业务限制。它们都叫组件,承担的责任却不一样。 知乎这道关于组件库

最长递增子序列:从长度到具体下标

最长递增子序列需要保持原数组的先后顺序,但不要求位置连续。只知道最大长度,还不能告诉 DOM Diff 哪些项可以保留;这又引出具体下标的恢复问题。下面从容易验证的动态规划走到二分结尾表,再用前驱记录找回一条真实序列。 LeetCode 300:从求长度到还原具体序列LeetCode 300「最长递增子序列」给定一个整数数组,要求返回严格递增子序列的最大长度。Diff 还多了一项要求:知道究竟保留

React、Vue 2 与 Vue 3 的 DOM Diff:从节点身份到最长递增子序列

最近一道前端面试题问到了 React、Vue 2 和 Vue 3 的 DOM Diff。这个问题既涉及列表如何重排,也涉及更基础的更新:同一个按钮改了 class 与文本,是否重建 DOM;把 div 换成 section,原来的子组件状态还在不在;同一种组件收到新 props,和换成另一种组件又有什么区别。 这些场景需要先回答「旧节点与新节点是否属于同一个身份」,再决定更新什么。列表还多了一层顺

Vue 的 VNode 生命周期事件:在模板里监听子组件挂载

Vue 有一些不太常见的写法。比如,我们可以直接在组件标签上监听它的挂载: <Child @vue:mounted="onChildMounted" /> 日常写组件时,生命周期通常出现在 mounted()、onMounted() 这些位置。模板里的 @ 除了监听点击、输入或子组件发出的业务事件,也能让父组件在使用子组件的地方,监听这个子组件对应节点的生命周期。 这个能力由 Vue 自身提供,官

无法接入虚拟滚动时,怎样减少长列表的屏外渲染

一个不断加载内容的列表,滚得越久,页面里的卡片就越多。理想的处理方式是让列表组件只挂载视口附近的节点,也就是虚拟滚动。但有时列表由第三方组件或现成页面管理,我们只能给已有 DOM 加样式和少量脚本,碰不到它的数据源、分页状态与节点复用逻辑。 这种约束下,仍然可以减少一部分浏览器工作:保留卡片及其占据的空间,让浏览器暂时跳过远离视口的内容布局和绘制。负责这件事的是 content-visibilit

让 Codex 调用普通 ChatGPT:一次本地代理链路验证

看到网罗灯下黑的《不加钱,把 ChatGPT 几乎无限的网页额度当 Codex 用》后,我想验证另一种接法:继续在本地 Codex 里读文件、执行命令、改代码,把模型请求转给普通 ChatGPT。 这样既不用把工作搬到网页,也能保留 Codex 的本地执行环境。CC Switch 可以帮忙改配置,不过我更想知道,能不能直接启动一个本地服务完成转发。 2026 年 10 月 5 日,Codex 通过

复用成熟 App 的主题配色:从原生模块到可复用数据

QQ 的「地海蔚蓝」在深色模式下是一片灰蓝。配置里的第一项背景色 #628D9B 比聊天区中央的实测值 #48575C 鲜亮许多。若想把这种灰蓝带到阅读应用里,先要找出两种颜色之间经过了哪些绘制步骤。 这组文章以 Windows QQ 9.9.36-53644 和 Windows Codex 26.1103.4239.0 为例。QQ 有静态配色、由基础色生成的模板、纹理和深色遮罩。Codex 桌面