cursor-effects全称 90s Cursor Effects ,是一个网页端鼠光标特效库。具体效果可以看这里。
基本使用
使用方式非常简单,只要引入对应的光标特效函数,然后调用即可。
1 | import { emojiCursor } from "cursor-effects"; |
在写这篇文章的时候,目前支持 12种 光标特效,后面有可能会增加,具体如下:
- Rainbow Cursor
- Emoji Rain
- Elastic Emoji
- Ghost Following
- Trailing Cursor
- Text Flag Cursor
- Following Dot
- Bubbles Particles
- Snowflake Particles
- Fairy Dust
- Clock Cursor
- Character Cursor
每一种光标特效的参数稍微有些差异,具体可以看Github中的简绍。
emojiCursor
cursor-effects 暴露了12个光标特效函数,每一个函数的基本结构大致都是相同的,我们这里先以 emojiCursor 为例。
emojiCursor 的效果如下:

emojiCursor 的函数结构,如下:
1 | export function emojiCursor(options) { |
由上可见,emojiCursor 函数调用了 init 函数来初始化光标特效,同时返回一个含有 destroy 方法的对象。
上面还有一段 prefersReducedMotion 相关的代码,如果用户开启了 prefers-reduced-motion: reduce 则不展示光标特效,以避免对用户造成过度刺激(如癫痫患者或对动态内容敏感的用户)。
init 函数
emojiCursor 函数内部的 init 函数如下:
1 | export function emojiCursor(options) { |
在 emojiCursor 函数刚开始中,这里可以设置 emoji、delay、element、zIndex 四个参数,然后定义了一些变量。init 函数中,先创建 canvas 根据是否传 element 来判断挂载到 element 还是 document.body 中。然后根据传入的 possibleEmoji 绘制到 canvas 上,并把绘制有 emoji 的 canvas 保存在 canvImages 数组里,这样后面可以通过 context.drawImage() 来绘制这个 emoji。最后调用 bindEvents() 绑定鼠标移动事件,并调用 loop() 函数开始动画循环。
bindEvents 函数如下,监听鼠标、触摸和窗口重置事件。
1 | function bindEvents() { |
onMouseMove 函数和 onTouchMove 函数分别处理鼠标和触摸事件,核心逻辑是获取鼠标或触摸位置,并添加粒子。鼠标和触摸稍微有点不同的地方是触摸的时候直接在触发的位置添加粒子,而鼠标移动的时候有节流操作,同时移动超过1像素才添加粒子。onMouseMove 事件之所有在 onMouseMove 中使用requestAnimationFrame, 是因为它结合 lastTimestamp 做了节流,可以限制 requestAnimationFrame 中的函数每一帧只执行一次,避免同一帧内多次执行导致性能浪费。
1 | function onTouchMove(e) { |
Particle 函数
Particle 函数定义的是粒子类,它放在每一个光标特效函数的内部,因为每种特效稍微有点不同。 emojiCursor 中的 Particle 函数接收三个参数,分别是粒子的初始位置 x、y,以及要绘制的 canvas(即 emoji)。在 Particle 函数中,定义了粒子的生命周期 lifeSpan、速度 velocity、位置 position。update 方法中,根据粒子的速度更新粒子的位置,同时减少 lifeSpan,并根据 lifeSpan 计算缩放比例,最后调用 context.drawImage() 绘制粒子,这里 scale 会随着 lifeSpan 的变小而变小,所以就有了 emoji 变小的效果。
1 | function Particle(x, y, canvasItem) { |
updateParticles 函数
在 loop 函数中调用 updateParticles 函数,更新粒子的位置和生命周期,然后开启动画循环。updateParticles 函数每一帧执行一次,它会更新粒子的位置并绘制,对于 lifeSpan 小于 0 的粒子,会从数组中删除。
1 | function loop() { |
上面200多行代码就实现了 emoji 的光标动画效果。
fairyDustCursor
fairyDustCursor 的效果如下,效果与 emojiCursor 类似,只是掉落 emoji 换成了掉落星星。

fairyDustCursor 的代码与 emojiCursor 几乎相同,如下,只是把绘制 emoji 替换为绘制不同颜色的星号 * (即 fairySymbol 变量对应的符号),其他代码几乎一样只是粒子参数稍微修改了一下。
1 | export function fairyDustCursor(options) { |
bubbleCursor
bubbleCursor 的效果如下,鼠标移动时,会生成一些小泡泡,小泡泡会浮起来并且越来越大,然后消失。

bubbleCursor 代码如下:
1 | export function bubbleCursor(options) { |
bubbleCursor 与 emojiCursor 代码也类似,只是 Particle 函数稍有不同,emojiCursor 函数的 Particle 函数是 drawImage 而 bubbleCursor 函数的 Particle 函数是调用 arc 画圆(泡泡)。
followingDotCursor
followingDotCursor 的效果如下,鼠标移动时,会有一个跟随鼠标移动的圆点。

followingDotCursor 代码如下:
1 | export function followingDotCursor(options) { |
followingDotCursor 把粒子命名成了 Dot,而且只有一个粒子。因为圆点要跟随鼠标移动,所以用到了缓动动画。缓动动画公式如下,缓动动画更多详情可以看这里。
当前速度 = (最终位置 - 当前位置) * 缓动系数。
新的位置 = 当前位置 + 当前速度。
trailingCursor
trailingCursor 的效果如下,鼠标移动时,会有一系列的鼠标做缓动动画。

trailingCursor 的代码如下:
1 | export function trailingCursor(options) { |
鼠标的样式由 baseImageSrc 决定,默认给了一个默认鼠标样式的base64图片。Particle 只要在特定位置绘制图片即可。在 updateParticles 中,第一个粒子是鼠标的位置(cursor.x 和 cursor.y),其他粒子是根据前一个粒子的位置做缓动动画,这样就有了一系列的跟随东海。需要注意的是最后一个粒子没有下一个粒子,这里把第一个粒子赋值给 nextParticle,是为了让程序走下去,最后一个的 nextParticle 计算出来的 x 与 y 不会被使用到。
springyEmojiCursor
springyEmojiCursor 的效果如下,相当于把多个 emoji 用一根绳子连接在了一起。

springyEmojiCursor 的代码如下:
1 | export function springyEmojiCursor(options) { |
目前看上面代码没啥特别的,绳子弹性效果的代码在 updateParticles 中。
1 | // 二维向量类 |
上述用到了基础力学知识,先计算出 弹力(spring) 和 阻力(resist),然后根据他们和重力计算出加速度,最后根据加速度计算出速度和位置,代码有点长不过根据注释来看条理还是很清晰。最后对超出边界处理,处理方式是速度反向并乘以一个小于1的系数来模拟弹性反弹并损失部分速度的过程,位置则修改到刚触碰到边界的位置。
这里还有一个 springForce 函数,用来计算两个粒子之间的弹力,代码如下。
1 | function springForce(i, j, spring) { |
先计算出绳子的长度,如果绳子比原来的长度伸长了,则计算弹力。弹力的大小是 弹性系数 * (绳子拉伸后长度 - 绳子原长度) ,也就是胡克定律,最后根据三角形的相似计算出 x轴 和 y轴 的分弹力。
胡克定律:
弹力 = 弹性系数 * (绳子拉伸后长度 - 绳子原长度)
rainbowCursor
rainbowCursor 的效果如下,光标拥有彩虹的特效。

rainbowCursor 的代码如下:
1 | export function rainbowCursor(options) { |
这里的 Particle 只是用来记录坐标,而没有绘制的逻辑,核心逻辑在 updateParticles 中。updateParticles 函数中,第一个粒子的位置是鼠标当前的位置,然后后面的粒子跟前一个粒子做缓动动画,这样就有了彩虹跟随鼠标的缓动效果。彩虹的绘制也是很特别,它是在对应点下画了6条不同颜色的线(彩虹),将 lineCap 设置为 round,使线更加平滑,由于每次鼠标移动的距离很短(都是像素级别的),而每一条线又有 totalParticles - 1(默认20个点,19个小线段)个小线段,这样就有了弯曲的彩虹效果效果。updateParticles 实现的图片通过下面这个图,会更好理解一些,这里假设鼠标从右边移动到左边。

还有一些特效,原理与上面类似,这里就不再赘述了