SCENE / 001
全部文章CODE / 代码笔记

给博客添加自定义鼠标效果

基于 shiyin.cafe 实现了三层自定义鼠标效果:SVG 光标替换、延迟跟随灰圆、点击涟漪。本文拆解实现方案和关键细节

#前端交互#视觉效果
本文目录
  1. 效果拆解
  2. 方案选择
  3. 实现
  4. CSS
  5. JavaScript
  6. 注意点
  7. 可调整参数
  8. 完整代码
阅读记录0%
0%

效果拆解

整个鼠标效果由三层组成,逐层叠加:

层级 实现方式 作用
1 cursor: url(data:image/svg+xml...) 替换系统光标 白色小圆点代替默认箭头,零延迟
2 <div id="g-pointer"> 跟随鼠标移动 灰色半透明圆,0.1s 过渡延迟产生粘滞感
3 .click-effect 动画元素 鼠标松开时扩散涟漪,动画结束后移除

关键视觉点是第二层的延迟跟随——白色光标在鼠标实际位置,灰色圆因为 transition: transform 0.1s ease会略微滞后,快速移动时两层错开,产生层次感

方案选择

定位跟随元素有两种方式,性能差异明显:

方案 触发流程 性能
left / top Layout → Paint → Composite 差,高频mousemove下卡顿
transform: translate() Composite 仅合成层 好,GPU 加速

使用 will-change: transform 提前告知浏览器分配独立合成层

实现

CSS

/* SVG 光标:亮色用黑点,暗色用白点 */
html.custom-cursor-active,
html.custom-cursor-active * {
  cursor: url("data:image/svg+xml,...fill='black'...") 4 4, auto !important;
}
html.dark.custom-cursor-active,
html.dark.custom-cursor-active * {
  cursor: url("data:image/svg+xml,...fill='white'...") 4 4, auto !important;
}

/* 跟随灰圆:transform 定位 + 过渡延迟 */
#g-pointer {
  position: fixed;
  top: 0;
  left: 0;
  width: 20px;
  height: 20px;
  background: rgba(128, 128, 128, 0.3);
  border-radius: 50%;
  pointer-events: none;
  z-index: 9999999;
  transition: transform 0.1s ease;
  will-change: transform;
}

/* 点击涟漪 */
.click-effect {
  position: fixed;
  background: rgba(128, 128, 128, 0.3);
  border-radius: 50%;
  transform: scale(0);
  animation: clickAnimation 0.6s ease-out;
  pointer-events: none;
  z-index: 9999999;
}

@keyframes clickAnimation {
  0% { transform: scale(0); opacity: 0.8; }
  100% { transform: scale(4); opacity: 0; }
}

/* 移动端降级 */
@media (max-width: 1023px) {
  html.custom-cursor-active,
  html.custom-cursor-active * { cursor: auto !important; }
  #g-pointer { display: none; }
}

JavaScript

var MIN_WIDTH = 1024;
var gPointer = null;
var isEnabled = false;
var isPressing = false;

function buildTransform(x, y) {
  var t = "translate(" + (x - 8.5) + "px, " + (y - 8.5) + "px)";
  if (isPressing) t += " scale(0.15)";
  return t;
}

function onMouseMove(e) {
  gPointer.style.transform = buildTransform(e.clientX, e.clientY);
}

function onMouseDown(e) {
  if (!isEnabled) return;
  isPressing = true;
  gPointer.style.transform = buildTransform(e.clientX, e.clientY);
}

function onMouseUp(e) {
  if (!isEnabled) return;
  isPressing = false;
  gPointer.style.transform = buildTransform(e.clientX, e.clientY);
  // 创建涟漪
  var el = document.createElement("div");
  el.className = "click-effect";
  el.style.left = (e.clientX - 17) + "px";
  el.style.top = (e.clientY - 17) + "px";
  el.style.width = "34px";
  el.style.height = "34px";
  document.body.appendChild(el);
  el.addEventListener("animationend", function () { el.remove(); });
}

注:当前站点实际实现已不再全局屏蔽右键菜单,只保留自定义光标本身。

效果激活时同步屏蔽右键菜单和文本选中,避免原生光标残留导致体验割裂:

function onContextMenu(e) { e.preventDefault(); }
function onSelectStart(e) { e.preventDefault(); }

注意点

  • scale变换原点:不推荐用CSS scale属性做按下缩放,它的变换原点默认是元素在文档流中的位置(top:0; left:0),而非当前屏幕位置。应写在同一个 transform 字符串中,函数从左到右执行,先translate定位再scale收缩,圆心不会偏移
  • 涟漪定位left/tope.clientX - 半径 使元素中心对准鼠标位置,再用 animationend事件确保动画结束后移除DOM节点
  • 离线/重连处理:PJAX或SPA导航后需要重新激活效果,监听 astro:page-loadswupcontent:replace事件,执行 deactivate() + activate()重新绑定
  • 移动端降级< 1024px时完全禁用,恢复系统光标,解绑所有事件,不留副作用
  • 跨域iframe限制:Giscus等评论区通过跨域iframe加载,浏览器出于安全隔离不允许父页面的CSS规则穿透到iframe内部,因此鼠标移入评论区后会恢复系统光标。父页面至少可以在移入 iframe 时隐藏自定义光标,避免双光标;但无法在 iframe 内部继续显示自定义光标,这属于浏览器安全策略限制

可调整参数

参数 位置 说明
- 8.5 JS偏移量 灰圆相对鼠标的偏移像素,修改它微调对齐
scale(0.15) JS buildTransform 按下时灰圆缩放倍率
0.1s CSS transition 灰圆跟随延迟,越大越粘滞
34px / scale(4) JS/CSS keyframe 涟漪初始尺寸和扩散倍率
1023px CSS media query 效果生效的屏幕宽度阈值

完整代码

当前站点实际实现已改为浏览器原生 SVG 光标,避免 DOM 跟随元素天然晚于系统 硬件光标一帧以上。完整代码在:

  • public/assets/cursors/ — 默认箭头与可点击十字的 SVG 光标资源
  • src/styles/interactive.css — 原生 cursor: url(...) 的三态规则
  • src/components/interactive/InteractionSurface.astro — 根据设备能力与可访问性偏好切换状态

BaseLayout.astro 中已经全局引入;如果要在其他布局中使用:

import { InteractionSurface } from "@/components/interactive";

<InteractionSurface />

需要带有拖尾、粒子或物理效果时,才建议额外叠加 DOM 视觉层;基础指针应始终由 原生 cursor 渲染,避免与系统硬件光标竞争帧时序。

READING / FOCUSOFF

留言

评论区暂未开放;可先通过 RSS 或「关于」页联系。

站内搜索

输入关键词开始搜索