惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Martin Fowler
Martin Fowler
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
A
About on SuperTechFans
Apple Machine Learning Research
Apple Machine Learning Research
The Register - Security
The Register - Security
Vercel News
Vercel News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
人人都是产品经理
人人都是产品经理
MyScale Blog
MyScale Blog
云风的 BLOG
云风的 BLOG
博客园_首页
U
Unit 42
T
Tailwind CSS Blog
G
GRAHAM CLULEY
F
Full Disclosure
V
Vulnerabilities – Threatpost
T
Tenable Blog
月光博客
月光博客
P
Privacy & Cybersecurity Law Blog
P
Privacy International News Feed
K
Kaspersky official blog
Scott Helme
Scott Helme
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
N
News and Events Feed by Topic
T
The Exploit Database - CXSecurity.com
N
News and Events Feed by Topic
有赞技术团队
有赞技术团队
Recent Commits to openclaw:main
Recent Commits to openclaw:main
L
LINUX DO - 最新话题
Recorded Future
Recorded Future
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Help Net Security
Help Net Security
The GitHub Blog
The GitHub Blog
Cisco Talos Blog
Cisco Talos Blog
SecWiki News
SecWiki News
P
Proofpoint News Feed
Security Latest
Security Latest
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
罗磊的独立博客
S
Security Affairs
M
MIT News - Artificial intelligence
L
LINUX DO - 热门话题
美团技术团队
Simon Willison's Weblog
Simon Willison's Weblog
T
Threat Research - Cisco Blogs
Stack Overflow Blog
Stack Overflow Blog
Forbes - Security
Forbes - Security
Hugging Face - Blog
Hugging Face - Blog
博客园 - Franky
V
Visual Studio Blog

皓子的小站

网站字体应用之坑——font-family 篇 糟糕的 meta name="theme-color" How to Automatically Track Newly Supported ESLint Rules in Oxlint How to Gradually Migrate from ESLint to Oxlint (Without Breaking Everything) 静态资源预压缩:零运行时开销,极致节省带宽 CVE-2025-70886 Proof of Concept (PoC) | A Script to Crash Halo CMS Comment Backend CVE-2025-70886 漏洞复现/PoC | 一个脚本让 Halo CMS 评论后台瘫痪 2025 年终总结 & 博客两周年 老用户专享!已有 Halo 专业版授权可免费升级商业版 自动追踪 Oxlint 对 ESLint 规则的新增支持 Halo 贡献者证书与实体周边盲盒开箱 博客评论系统指南 CDN 回源跟随配置导致登录异常问题排查 SCDN 免费赞助计划:助力高质量博客&博客圈 锐捷校园网:网络共享与带宽叠加方案(哈理工案例) ESLint 到 Oxlint 渐进式迁移快速上手指南 Fixing Vite Breaking Inline JS & CSS in Thymeleaf Templates 解决 Vite 破坏 Thymeleaf 模板内联 JS & CSS 的方法 我的博客,为什么是月更? 博客俱乐部一周年纪念品开箱 网站字体加载之坑——format 篇 经历 1000000000 次 DDoS 请求攻击后,我总结了三条经验 题解分享:[AtCoder Beginner Contest 414 E] Count A%B=C 题解分享:[蓝桥杯 2025 国 Python A] 杨辉三角 P12876 FiF口语训练破解刷分教程(适用于 Windows) 不要与蠢人辩论 小心网络地雷·续篇 当心网络组织“开往”·续篇 当心网络组织“开往” 小心网络地雷 在 Halo CMS 中通过模板实现随机推荐多篇文章 回应某博客上有关“开往”组织在内部会议上对我的污蔑(上) 再遭 DDoS 攻击:CDN 流量限额拦不住请求计费飙升 2025年团体程序设计天梯赛 - Python 个人赛时暴力题解 网站流量分析工具 Umami 中国大陆合规版发布 把握当下,享受生活 Folo 认证功能反成漏洞?警惕 RSS 订阅源所有权被恶意盗取风险 在 Halo CMS 中通过模板实现随机文章跳转功能 从沉默展柜到时代回响:731部队罪证陈列馆参访手记 脚本分享:CDN 流量触顶后自动切换至 Cloudflare 官方 BUG 快薅!Halo 永久/买断用户可额外获得一份买断许可 老用户专享!已有 1Panel 永久授权/买断许可用户可再领免费买断许可证! 限时特惠!1¥ 立即解锁 1Panel 专业版全功能 61 天 混合云架构下的动态 IPv6 智能加速实践:从家庭网络到全球分发的低成本部署指南 如何用 AI 全方面辅助博客创作 - 从博文之内到博文之外 Random Number Generation and Formatting in Thymeleaf Thymeleaf 随机数生成与格式化详解 列举我维护的项目和发布的软件包 快速部署教程 - 通过 Webhook 触发邮件通知的开源自托管服务 Going Against the Grain - Four Websites That Make Your URL Longer 反其道而行 - 四个让你网址变得更长的网站 2024 年终总结 & 博客一周年 基于 OneBot API 的简易异步 QQ 群批量发消息脚本 如何快速完成中文排版优化 如何给文章分类和打标签 群规之敏感违禁词对抗 一图展示​ ​RSS3 世界 - The Open(RSS3) Universe 开源项目发到 bilibili 后可能获得的 PR C/C++:为什么 a[i][j] 等价于 *(*(a+i)+j) Fast Methods to Convert Uppercase Letters to Lowercase in C++ C++ 中快速将字符串大写字母转为小写的方法 Fixing GitHub Actions push to ghcr '403 Forbidden' error Fixing docker pull image "ghcr.io..." Unauthorized error 本站“算法题练习/题解分享”相关文章汇总 不怕流氓多,就怕流氓有文化——站长应该防范的几个问题及对应解决方法分享 题解分享:HRBUST 1132 水数 新手程序员必备的三项技能 如何快速排查插件(模组)的兼容性问题(基于二分法和分治思想) 通过 Umami 最新功能观察定时发布文章的最佳时段 踩坑分享:如何一个人搬运 126.7 斤的书坐动车 两篇文章分享:别像弱智一样提问 & 提问的智慧 论下落式音游/定轨音游中的优质粪铺(文末附铺面推荐) 为什么选择无纸化阅读?为什么买墨水屏电子书?为什么 Kindle“失败”了? 我维护的 Halo 主题,现已发布到 Halo 应用商店! OSU!Standard(osu!std)/OSU!Taiko 转 OSU!Mania 铺面转换器发布 我常用的 OSU!Mania 皮肤分享 电荷守恒辨析:为什么一个离子带多个电荷要乘上所带电荷数而不是除以所带电荷数 注意!align-items: flex-start 会导致 overflow-wrap: break-word 失效 惊人 98 元限定T恤!1Panel, Halo 开发商开启推广积分奖励活动 Minecraft Spigot Bukkit 服务器插件开发 教程收集 中文博客倡议 Windows 10 遇到“只能重装系统”但不想重装的解决方案 Windows 10 文件系统错误 -2144926975 -2147219196 修复 Windows 10 升级 Windows 11 简明教程 CSS 预处理器如何正确的复用样式(stylus 中使用 @extend 继承的坑) 请一定要回复博客下的匿名评论!对于博主和评论者有关匿名评论的倡议 一图解释单一质点运动波动图像和振动图像的关系 解决 Docker pull image "ghcr.io..." Unauthorized error 解决 GitHub Actions push to ghcr '403 Forbidden' error 解决 Umami API 更新延迟问题(如何修改开源项目代码并构建 Docker 镜像-以 Umami 为例) 在国家公祭日自动将网站变灰 中文博客圈列表 解决 Halo CMS 的 Umami Plugin CORS&CSP 跨域报错问题 Windows 创建存储池报错 无法创建池 不支持该请求 (0x00000032) Windows 显示器突然黑白 桌面突然黑白 鼠标和截图还是彩色的 通过指令过滤出正确的公网 IPv6 地址(记一次折腾 IPv6 DDNS) 我的世界免费科技地址 1.7-1.21 网站 Favicon 下载工具发布 键盘 Bios 内可用 系统内无法输入 设备管理器无黄色感叹号 设备管理器 网卡一堆黄色感叹号 例如 WAN, Miniport
自定义网页鼠标指针——一段曲折的旅程
HowieHz · 2026-01-17 · via 皓子的小站

看到别人主题有自定义鼠标指针的功能,我也想给我主题加一个玩玩。

探索

初探

找到了相关文档:

看起来似乎只需要下面 CSS 代码就完事了。

:root {
  cursor: url("xxx.cur"), auto;
}

随便下载了一个图标包,傻眼了:一个包里面有若干个 CUR 文件。
好吧,CUR 格式是一种图形文件格式,而不是打包了一组图标。

显然下面的写法不是很符合正确 CSS 写法。

:root {
  cursor: url("auto.cur"), auto;
  cursor: url("context-menu.cur"), context-menu;  /* [!code ++] */
  /* 省略更多 */  /* [!code ++] */
}

那有没有什么标准,说明什么网页元素用什么光标?

从 User Agent Stylesheet 学习

我想到了找 User Agent Stylesheet(用户代理样式表)

用户代理样式表中有这些 cursor 相关的定义:

点我展开相关定义

Chromium

label {
  cursor: default;  /* [!code highlight] */
}

input {
  -webkit-appearance: textfield;
  padding: 1px;
  background-color: white;
  border: 2px inset;
  -webkit-rtl-ordering: logical;
  -webkit-user-select: text;
  cursor: auto;  /* [!code highlight] */
}

input::-webkit-inner-spin-button {
  -webkit-appearance: inner-spin-button;
  display: inline-block;
  cursor: default;  /* [!code highlight] */
  flex: none;
  align-self: stretch;
  -webkit-user-select: none;
  -webkit-user-modify: read-only !important;
  opacity: 0;
  pointer-events: none;
}

textarea {
  -webkit-appearance: textarea;
  background-color: white;
  border: 1px solid;
  -webkit-rtl-ordering: logical;
  -webkit-user-select: text;
  flex-direction: column;
  resize: auto;
  cursor: auto;  /* [!code highlight] */
  padding: 2px;
  white-space: pre-wrap;
  word-wrap: break-word;
}

input[type="button" i],
input[type="submit" i],
input[type="reset" i],
input[type="file" i]::-webkit-file-upload-button,
button {
  align-items: flex-start;
  text-align: center;
  cursor: default;  /* [!code highlight] */
  color: ButtonText;
  padding: 2px 6px 3px 6px;
  border: 2px outset ButtonFace;
  background-color: ButtonFace;
  box-sizing: border-box
}

area {
  display: inline;
  cursor: pointer;  /* [!code highlight] */
}

select {
  -webkit-appearance: menulist;
  box-sizing: border-box;
  align-items: center;
  border: 1px solid;
  white-space: pre;
  -webkit-rtl-ordering: logical;
  color: black;
  background-color: white;
  cursor: default;  /* [!code highlight] */
}

a:-webkit-any-link {
  color: -webkit-link;
  text-decoration: underline;
  cursor: auto;  /* [!code highlight] */
}

Firefox

.mozGrabber:-moz-native-anonymous {
  outline: ridge 2px silver;
  padding: 2px;
  position: absolute;
  width: 12px;
  height: 12px;
  background-image: url("resource://gre/res/grabber.gif");
  background-repeat: no-repeat;
  background-position: center center;
  user-select: none;
  cursor: move;  /* [!code highlight] */
}
提取聚合相关声明
label {
  cursor: default;
}

input {
  cursor: auto;
}

input::-webkit-inner-spin-button {
  cursor: default;
}

textarea {
  cursor: auto;
}

input[type="button" i],
input[type="submit" i],
input[type="reset" i],
input[type="file" i]::-webkit-file-upload-button,
button {
  cursor: default;
}

area {
  cursor: pointer;
}

select {
  cursor: default;
}

a:-webkit-any-link {
  cursor: auto;
}

.mozGrabber:-moz-native-anonymous {
  cursor: move;
}

我们可以将这些拿出来作为声明。

从 cursor 实现学习

忽然想到,我找下 cursor: auto 是怎么实现的,对照设置下就好了,于是找到以下源码:

bool EventHandler::ShouldShowIBeamForNode(const Node* node,
                                          const HitTestResult& result) {
  if (!node)
    return false;

  if (node->IsTextNode() && (node->CanStartSelection() || result.IsOverLink()))
    return true;

  return IsEditable(*node);
}

std::optional<ui::Cursor> EventHandler::SelectCursor(
  const ui::Cursor& i_beam = style.IsHorizontalWritingMode() ? IBeamCursor() : VerticalTextCursor();

  switch (style.Cursor()) {
    case ECursor::kAuto:
      return SelectAutoCursor(result, node, i_beam);
    // 省略
    case ECursor::kText:
      return i_beam;
    // 省略
  }
  return PointerCursor();
}

std::optional<ui::Cursor> EventHandler::SelectAutoCursor(
    const HitTestResult& result,
    Node* node,
    const ui::Cursor& i_beam) {
  if (ShouldShowIBeamForNode(node, result))
    return i_beam;

  return PointerCursor();
}

cursor: auto 的实现逻辑非常简单:

  1. 调用 ShouldShowIBeamForNode() 判断当前节点是否满足:可选择的文本/链接文本/可编辑区域(如 <input>, <textarea> 或带 contenteditable 属性的元素)

    • 如果是,返回 i_beam(即 cursor: text
  2. 默认情况返回普通箭头光标(即 cursor: default

结论:把可选择的文本/链接文本/可编辑区域设置为 cursor: text

100% 还原实现思路:写一段 JS 监听 mousemove 事件,如果满足要求,就将光标换为自定义的 cursor: text

妥协:我希望尽量使用 CSS 来实现,覆盖大部分情况,如果有需要再用 JS 判断边缘情况(如:user-select: none)。得到了以下 CSS 声明:

点我展开 CSS 相关声明
/* 1. 可编辑元素 - 对应 IsEditable() */
input[type="text"],
input[type="email"],
input[type="password"],
input[type="search"],
input[type="tel"],
input[type="url"],
input[type="number"],
textarea,
[contenteditable="true"] {
  cursor: text;
}

/* 2. 文本内容元素 - 对应 IsTextNode() && CanStartSelection() */
p,
span,
div,
h1,
h2,
h3,
h4,
h5,
h6,
article,
section,
aside,
main,
li,
dd,
dt,
blockquote,
pre,
code,
td,
th,
label,
legend,
figcaption {
  cursor: text;
}

/* 3. 链接内的文本 - 对应 result.IsOverLink() */
a {
  cursor: pointer;
}

/* 但链接内的文本节点也显示文本光标(Chromium 的行为) */
a span,
a p,
a div {
  cursor: text;
}

/* 4. 垂直文本 */
[style*="writing-mode: vertical"] {
  cursor: vertical-text;
}

发挥主观能动性

发挥主观能动性,观察标准 HTML 元素/ARIA 属性,按语义进行标注,得到了以下 CSS 声明:

一些 CSS 声明
:root {
  cursor: default;
}

/* 禁止操作 */
:disabled,
[aria-disabled="true"] {
  cursor: not-allowed;
}

/* 帮助提示 */
[title],
abbr,
acronym,
[role="tooltip"] {
  cursor: help;
}

/* 调整大小 - 东西 */
input[type="range"],
[role="slider"] {
  cursor: ew-resize;
}

/* 抓取 */
input[type="range"]::-webkit-slider-thumb {
  cursor: grab;
}

/* 抓取中 */
[draggable="true"]:active,
input[type="range"]::-webkit-slider-thumb:active {
  cursor: grabbing;
}

/* 单元格 */
td,
th,
[role="grid"],
[role="gridcell"] {
  cursor: cell;
}

/* 等待 */
[aria-busy="true"] {
  cursor: wait;
}

/* 文本光标 */
input:not([type]),
input[type="text"],
input[type="password"],
input[type="email"],
input[type="search"],
input[type="tel"],
input[type="url"],
input[type="number"],
[contenteditable="true"],
textarea,
code,
kbd,
samp,
var,
pre,
[role="textbox"],
[role="searchbox"] {
  cursor: text;
}

/* 指针光标 */
a,
button,
select,
label,
input[type="button"],
input[type="submit"],
input[type="reset"],
input[type="file"],
input[type="checkbox"],
input[type="radio"],
input[type="color"],
input[type="date"],
input[type="datetime-local"],
input[type="time"],
input[type="month"],
input[type="week"],
video,
audio,
video::-webkit-media-controls,
audio::-webkit-media-controls,
img[onclick],
img[role="button"],
option,
summary,
li[onclick],
li[role="button"],
tr[onclick],
svg [onclick],
[role="button"],
[role="link"],
[role="menuitem"],
[role="menuitemcheckbox"],
[role="menuitemradio"],
[role="tab"],
[role="treeitem"],
[role="option"],
[role="switch"],
[role="checkbox"],
[role="radio"],
[role="combobox"],
*::-webkit-clear-button,
*::-webkit-scrollbar-thumb,
*::-webkit-file-upload-button,
*::-webkit-search-cancel-button,
*::-webkit-calendar-picker-indicator {
  cursor: pointer;
}

/* 准星 */
canvas {
  cursor: crosshair;
}

/* 抓取(可拖拽元素) */
[draggable="true"] {
  cursor: grab;
}

成果

聚合上面的阶段性成果,并按情况解释 cursor: auto 实际值,得到了以下的最终版本:

注:经过一次修订,去除了 *::-ms-browse(会导致 CSS 选择器失效)和 *::-ms-clear(有 ::-webkit-file-upload-button 了)。

点我展开最终版本 CSS 声明
:root,
label,
input::-webkit-inner-spin-button,
input[type="button" i],
input[type="submit" i],
input[type="reset" i],
input[type="file" i]::-webkit-file-upload-button,
button,
select {
  cursor: default;
}

.mozGrabber:-moz-native-anonymous {
  cursor: move;
}

:disabled,
[aria-disabled="true"] {
  cursor: not-allowed;
}

[title],
abbr,
acronym,
[role="tooltip"] {
  cursor: help;
}

input[type="range"],
[role="slider"] {
  cursor: ew-resize;
}

[draggable="true"],
input[type="range"]::-webkit-slider-thumb {
  cursor: grab;
}

[draggable="true"]:active,
input[type="range"]::-webkit-slider-thumb:active {
  cursor: grabbing;
}

td,
th,
[role="grid"],
[role="gridcell"] {
  cursor: cell;
}

[aria-busy="true"] {
  cursor: wait;
}

input:not([type]),
input[type="text"],
input[type="password"],
input[type="email"],
input[type="search"],
input[type="tel"],
input[type="url"],
input[type="number"],
[contenteditable="true"],
textarea,
code,
kbd,
samp,
var,
pre,
p,
span,
div,
h1,
h2,
h3,
h4,
h5,
h6,
article,
section,
aside,
main,
li,
dd,
dt,
blockquote,
pre,
code,
td,
th,
label,
legend,
figcaption,
a span,
a p,
a div,
[role="textbox"],
[role="searchbox"] {
  cursor: text;
}

area,
a,
a:-webkit-any-link,
button,
select,
label,
input[type="button"],
input[type="submit"],
input[type="reset"],
input[type="file"],
input[type="checkbox"],
input[type="radio"],
input[type="color"],
input[type="date"],
input[type="datetime-local"],
input[type="time"],
input[type="month"],
input[type="week"],
input[type="range"],
input[type="image"],
video,
audio,
video::-webkit-media-controls,
audio::-webkit-media-controls,
img[onclick],
img[role="button"],
option,
summary,
li[onclick],
li[role="button"],
tr[onclick],
svg [onclick],
[role="button"],
[role="link"],
[role="menuitem"],
[role="menuitemcheckbox"],
[role="menuitemradio"],
[role="tab"],
[role="treeitem"],
[role="option"],
[role="switch"],
[role="checkbox"],
[role="radio"],
[role="combobox"],
*::-webkit-clear-button,
*::-webkit-scrollbar-thumb,
*::-webkit-file-upload-button,
*::-webkit-search-cancel-button,
*::-webkit-calendar-picker-indicator {
  cursor: pointer;
}

canvas {
  cursor: crosshair;
}

[style*="writing-mode: vertical"] {
  cursor: vertical-text;
}

使用方法:

:root {
    cursor: default;  /* [!code --] */
    ursor: url("实际的图标地址.cur"), default;  /* [!code ++] */
}

/* 此外再补充其他需要的声明 */ /* [!code ++] */

结语

感谢您看到这里,欢迎在评论区留言。