news 2026/9/28 19:54:51

Cursor 光标类型集合:用 CSS 与 JavaScript 打造可复制的自定义光标方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Cursor 光标类型集合:用 CSS 与 JavaScript 打造可复制的自定义光标方案

1. 光标类型到底有多少种,为什么你总记不全

做前端页面时,cursor这个属性几乎天天见,但真要一口气说出所有关键字,多数人只能想起pointer、text、move这几个。等到要做一个拖拽排序、表格列宽调整、或者画布缩放功能时,才发现浏览器其实内置了一整套光标类型,只是平时没系统整理过。

cursor是 CSS 里负责“鼠标指针长什么样”的属性,它既能用关键字指定系统内置形状,也能用图片 URL 加载自定义光标。它适合所有需要跟鼠标交互的前端场景:按钮、链接、可拖拽元素、文本输入区、加载状态、帮助提示、以及各种 resize 边缘。换句话说,只要用户会用鼠标点你的页面,cursor就有用武之地。

这篇内容我会把 CSS 关键字光标、JavaScript 动态切换、以及自定义图片光标三块串起来,给你一份可以直接复制进项目的配置。核心思路是:先用 CSS 变量把光标类型集中管理,再用一个 JS 函数按状态切换,最后在浏览器里逐项验证。整个过程不需要装任何依赖,打开 DevTools 就能跟做。

需要说明的是,浏览器内置光标的具体外观由操作系统和浏览器共同决定,同一关键字在 Windows、macOS、Linux 上长得不完全一样,这是正常现象,不影响功能。

2. 前置准备:用 TaoToken 补齐文档与代码生成环节

写这类偏“查表 + 试错”的前端内容,最耗时间的往往不是写代码,而是查每个关键字的准确拼写和适用场景。我自己的习惯是开一个模型对话窗口,把光标关键字列表、CSS 变量写法、JS 切换逻辑一次性问清楚,再粘到编辑器里改。

如果你也想用这种方式提速,可以先把 TaoToken 的 API Key 配好。它的接入地址是https://taotoken.net/api,控制台里能创建和管理密钥。拿到 Key 之后,无论是接进编辑器插件做代码补全,还是接进自己的脚本做批量生成,都走同一个入口。

具体操作上,先打开控制台创建 API Key:

https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite

创建完在密钥管理页复制出来,放到环境变量里,别硬编码进前端代码:

export TAOTOKEN_API_KEY="你的key"

如果你更想先验证模型对光标关键字的理解是否准确,可以直接用模型对话页测试:

https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite

把“列出 CSS cursor 所有关键字并说明适用场景”丢进去,对比它给的列表和你项目里实际用到的,很快就能发现遗漏。接入文档在https://taotoken.net/doc,里面有完整的请求格式和参数说明,遇到 401 或 429 时先翻这一页。

注意:API Key 属于敏感凭证,只放在服务端或本地环境变量里,不要提交到 Git 仓库,也不要写进浏览器可见的 JS 文件。

3. 可复制配置:CSS 变量集中管理光标类型

先把光标类型用 CSS 自定义属性收拢到一处。这样做的好处是,后面 JS 切换时只需要改一个变量名,不用到处写死字符串。

:root { /* 基础交互 */ --cursor-default: default; --cursor-auto: auto; --cursor-pointer: pointer; --cursor-text: text; --cursor-move: move; --cursor-help: help; --cursor-wait: wait; --cursor-crosshair: crosshair; /* 方向缩放:n 北 s 南 e 东 w 西 */ --cursor-n-resize: n-resize; --cursor-s-resize: s-resize; --cursor-e-resize: e-resize; --cursor-w-resize: w-resize; --cursor-ne-resize: ne-resize; --cursor-nw-resize: nw-resize; --cursor-se-resize: se-resize; --cursor-sw-resize: sw-resize; /* 双向缩放 */ --cursor-ew-resize: ew-resize; --cursor-ns-resize: ns-resize; --cursor-nesw-resize: nesw-resize; --cursor-nwse-resize: nwse-resize; /* 其他 */ --cursor-not-allowed: not-allowed; --cursor-grab: grab; --cursor-grabbing: grabbing; --cursor-zoom-in: zoom-in; --cursor-zoom-out: zoom-out; --cursor-none: none; }

这里有几个容易混的点值得单独说。e-resize、w-resize、n-resize、s-resize里的字母分别代表东、西、北、南,也就是右、左、上、下。而ew-resize和ns-resize是双向的,分别表示水平方向和垂直方向可缩放。nesw-resize和nwse-resize则是两条对角线方向,常用于右下角和左上角的缩放手柄。

grab和grabbing是一对,前者是“可以抓”,后者是“正在抓”,做拖拽列表时体验很自然。not-allowed适合禁用态按钮,zoom-in/zoom-out适合图片预览。

定义好变量后,在具体元素上引用:

.btn { cursor: var(--cursor-pointer); } .input-area { cursor: var(--cursor-text); } .drag-handle { cursor: var(--cursor-grab); } .drag-handle:active { cursor: var(--cursor-grabbing); } .resize-handle-se { cursor: var(--cursor-se-resize); } .disabled-btn { cursor: var(--cursor-not-allowed); }

如果你要的是自定义图片光标,写法是在关键字前面加url(),并且必须在末尾保留一个普通关键字兜底,否则图片加载失败时浏览器可能不显示任何光标:

.custom-cursor { cursor: url("./cursors/pen.png") 4 4, var(--cursor-pointer); }

url()后面的两个数字是热点坐标,也就是光标“实际点击位置”相对图片左上角的偏移。不写的话默认是0 0,也就是图片左上角。做画笔类光标时,热点通常设在笔尖位置。

4. JavaScript 动态切换:一个函数搞定状态映射

CSS 变量解决了“定义”,JS 解决“切换”。下面这个函数接收元素和状态名,从映射表里取出对应的 CSS 变量值并写回style.cursor。

const cursorMap = { default: "var(--cursor-default)", pointer: "var(--cursor-pointer)", text: "var(--cursor-text)", move: "var(--cursor-move)", help: "var(--cursor-help)", wait: "var(--cursor-wait)", crosshair: "var(--cursor-crosshair)", "n-resize": "var(--cursor-n-resize)", "s-resize": "var(--cursor-s-resize)", "e-resize": "var(--cursor-e-resize)", "w-resize": "var(--cursor-w-resize)", "ne-resize": "var(--cursor-ne-resize)", "nw-resize": "var(--cursor-nw-resize)", "se-resize": "var(--cursor-se-resize)", "sw-resize": "var(--cursor-sw-resize)", "ew-resize": "var(--cursor-ew-resize)", "ns-resize": "var(--cursor-ns-resize)", "nesw-resize": "var(--cursor-nesw-resize)", "nwse-resize": "var(--cursor-nwse-resize)", "not-allowed": "var(--cursor-not-allowed)", grab: "var(--cursor-grab)", grabbing: "var(--cursor-grabbing)", "zoom-in": "var(--cursor-zoom-in)", "zoom-out": "var(--cursor-zoom-out)", none: "var(--cursor-none)", }; function setCursor(el, type) { if (!el) return; const value = cursorMap[type]; if (!value) { console.warn(`[setCursor] 未知光标类型: ${type}`); return; } el.style.cursor = value; } function resetCursor(el) { if (!el) return; el.style.cursor = ""; }

调用方式很直接:

const box = document.querySelector("#box"); setCursor(box, "grab"); box.addEventListener("mousedown", () => setCursor(box, "grabbing")); box.addEventListener("mouseup", () => setCursor(box, "grab")); box.addEventListener("mouseleave", () => resetCursor(box));

这里有个细节:resetCursor把style.cursor置空,元素会回退到 CSS 里定义的值,而不是强制变成default。这样你在 CSS 里给父级设的光标不会被意外覆盖。

如果你要做“根据鼠标位置判断缩放方向”的效果,可以结合getBoundingClientRect算距离边缘的像素:

function handleResizeCursor(e, el, edge = 8) { const rect = el.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; const nearRight = x > rect.width - edge; const nearBottom = y > rect.height - edge; const nearLeft = x < edge; const nearTop = y < edge; if (nearRight && nearBottom) return setCursor(el, "nwse-resize"); if (nearLeft && nearTop) return setCursor(el, "nwse-resize"); if (nearRight && nearTop) return setCursor(el, "nesw-resize"); if (nearLeft && nearBottom) return setCursor(el, "nesw-resize"); if (nearRight || nearLeft) return setCursor(el, "ew-resize"); if (nearTop || nearBottom) return setCursor(el, "ns-resize"); resetCursor(el); }

这段逻辑在表格列宽拖拽、画布缩放里很常用,实测下来比单纯用 CSS 的resize属性灵活得多。

5. 逐项验证:在浏览器里确认每种光标都生效

配置写完了,怎么确认真的生效?最稳的办法是做一个临时验证页,把关键字逐个渲染出来,鼠标移上去看形状变化。

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <title>cursor 类型验证</title> <style> :root { --cursor-pointer: pointer; --cursor-text: text; --cursor-move: move; --cursor-help: help; --cursor-wait: wait; --cursor-crosshair: crosshair; --cursor-grab: grab; --cursor-grabbing: grabbing; --cursor-not-allowed: not-allowed; --cursor-zoom-in: zoom-in; --cursor-zoom-out: zoom-out; --cursor-se-resize: se-resize; --cursor-ew-resize: ew-resize; --cursor-ns-resize: ns-resize; } .grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; padding: 24px; } .cell { border: 1px solid #ccc; padding: 20px; text-align: center; font-size: 14px; user-select: none; } </style> </head> <body> <div class="grid" id="grid"></div> <script> const types = [ "pointer", "text", "move", "help", "wait", "crosshair", "grab", "grabbing", "not-allowed", "zoom-in", "zoom-out", "se-resize", "ew-resize", "ns-resize" ]; const grid = document.getElementById("grid"); types.forEach((t) => { const div = document.createElement("div"); div.className = "cell"; div.textContent = t; div.style.cursor = `var(--cursor-${t})`; grid.appendChild(div); }); </script> </body> </html>

打开这个页面后,把鼠标依次移到每个格子上,观察指针形状是否和文字描述一致。如果某个格子没变化,先检查:root里对应的变量名有没有拼错,再看 DevTools 的 Computed 面板里cursor最终解析成了什么值。

验证自定义图片光标时,把图片路径换成真实存在的文件,并在 Network 面板确认图片返回 200。如果图片 404,浏览器会回退到url()后面那个关键字,这也是为什么末尾一定要写兜底值。

提示:cursor: none会完全隐藏光标,验证时容易“找不到鼠标”,建议单独放一个格子并做好标记,验证完立刻移开。

6. 常见报错与排查清单

光标完全不生效:先确认元素本身有尺寸,宽高为 0 的元素鼠标根本进不去。再看是不是被父级的pointer-events: none挡住了,这种情况下子元素也收不到鼠标事件。

自定义图片光标不显示:最常见的原因是图片路径错误或跨域。url()里的路径是相对于 CSS 文件的位置,不是 HTML 文件。如果图片在别的域名下,需要对方允许跨域,否则浏览器会静默回退到兜底关键字。

热点坐标对不上:url()后面的两个数字是相对图片左上角的偏移,单位是像素。图片尺寸建议控制在 32×32 以内,太大在部分系统上会被忽略。

JS 设置后又被 CSS 覆盖:element.style.cursor是行内样式,优先级高于普通 CSS 选择器,但如果 CSS 里用了!important,行内样式也会被压过去。排查时在 DevTools 里看cursor那一行有没有被划掉。

移动端没反应:cursor是桌面端概念,触屏设备上大多数光标关键字没有视觉效果,这是预期行为,不用当成 bug。

wait和progress的区别:wait表示程序忙、用户需要等,通常是沙漏或转圈;progress表示程序在后台忙但用户仍可操作。两者外观接近,语义不同,按场景选。

如果你在接入模型辅助生成这些配置时遇到 401,先检查 API Key 是否带上了Bearer前缀;遇到 429 则是触发限流,降低请求频率即可。密钥管理和文档入口分别是:

https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite

7. 把光标方案接进你的工作流

光标类型本身不复杂,难的是记全、用对、并且在不同状态下切换自然。我现在的做法是:项目里建一个cursor.css专门放变量,再建一个cursor.js放映射表和切换函数,任何组件需要改光标就 import 这两个文件,不再散落各处写cursor: pointer。

如果你经常做拖拽、缩放、画布类交互,建议把第 4 节那个handleResizeCursor封装成可复用的工具函数,传入元素和边缘阈值就能自动判断方向。长期写这类交互代码的话,用 Coding Plan 把常用片段沉淀成模板会更省事:

https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite

最后留一个实用技巧:验证光标时不要只盯着一个浏览器,Chrome 和 Firefox 对grab、zoom-in这类关键字的渲染细节有差异,跨浏览器项目里最好两个都过一遍。把验证页存成书签,下次改光标配置直接打开对照,比凭记忆靠谱得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 19:53:17

示波器还是高速采集卡?自动化测试场景下的选型指南

我有个老客户&#xff0c;年前找我聊测试平台升级的事情。他们研发部配了三台旗舰示波器&#xff0c;带宽都是500MHz往上走&#xff0c;测起波形细节来完全够用。可搭建自动化测试平台时&#xff0c;示波器这边的角色就尴尬了——人得定期去导数据、看波形、手动标记异常&#…

作者头像 李华
网站建设 2026/9/28 19:53:02

微信开源知识库系统:RAG与Agent结合的企业智能问答部署实践

1. 微信开源的这个知识库项目&#xff0c;到底解决了什么问题最近微信开源了一个知识库项目&#xff0c;技术圈讨论热度很高&#xff0c;很多做 RAG、做企业内部知识问答的同行都在转。先说结论&#xff1a;这不是一个简单的文档问答 Demo&#xff0c;而是一套把“文档解析、向…

作者头像 李华
网站建设 2026/9/28 19:52:56

Cadence Allegro 17.4元件库与原理图工程实战指南

Cadence Allegro 17.4这套工具&#xff0c;我用了快三年。中间踩过的坑&#xff0c;比我写过的笔记还多。经常有刚转硬件的朋友跑来问&#xff1a;原理图工程到底怎么建&#xff1f;元件库从哪来&#xff1f;为什么我自己画好的元件放到原理图里总报警告&#xff1f;这些问题看…

作者头像 李华
网站建设 2026/9/28 19:51:03

DMA原理与408考点全解析:从底层机制到STM32实战

1. 为什么DMA是408大题里最容易丢分的“送分题”很多人复习计算机组成原理&#xff0c;看到I/O控制方式这一章会觉得“不就是程序查询、中断、DMA三种嘛”&#xff0c;翻书五分钟就过了。结果一到真题&#xff0c;尤其是24年45题那种综合型大题&#xff0c;直接懵掉——DMA的传…

作者头像 李华
网站建设 2026/9/28 19:50:49

葡萄成熟度检测数据集:VOC+YOLO双格式1123张

简介&#xff1a;本资源是一个面向计算机视觉初学者与农业AI应用开发者的葡萄成熟度检测专用数据集&#xff0c;聚焦果实分级识别任务&#xff0c;适用于目标检测模型训练与算法验证。数据集共2000个文件&#xff0c;包含1123张高质量葡萄图像&#xff08;JPG&#xff09;、112…

作者头像 李华