🎯 Vue+TypeScript实现OSU风格跟随光标
Vue+TypeScript实现 OSU 一样的的光标,光标具备旋转环绕、点击涟漪视觉与交互特性,提升页面交互的反馈性与视觉辨识度。使用Vue3组合式API开发。
📸 一、效果预览
要实现的个性化光标包含三个核心元素和多种交互效果:
-
核心元素:圆形光标(带发光样式)、旋转环绕圈(动态视觉元素)、定位十字标(辅助定位元素)
-
交互特效:鼠标移动精准跟随、点击时光标放大+旋转圈尺寸变化+扩散涟漪生成、鼠标锁定状态自动隐藏
-
兼容性:使用Vue3组合式API和原生JS事件,适配主流浏览器,无特殊依赖
🧠 二、实现思路
-
用Vue的teleport组件将光标元素挂载到html根节点,避免被父容器样式影响
-
监听鼠标移动、点击、释放等事件,实现光标跟随、状态切换等核心逻辑
-
通过CSS实现旋转动画、发光效果、涟漪扩散等视觉特效
以下按上述思路分模块实现,各模块包含完整代码及关键技术点解析。
🔧 三、实现方案
采用组件化开发模式,创建独立的CustomCursor.vue组件,便于项目中复用及维护。
🎨 1. HTML和样式
组件的HTML和样式如下,构建光标DOM结构和样式,通过Vue的teleport组件将光标元素挂载至html根节点,避免父容器样式干扰。
<template>
<!-- teleport将光标挂载到html根节点,确保层级最高且不受父容器影响 -->
<teleport to="html">
<!-- 光标容器,控制整体显示隐藏和位置 -->
<div class="cursor-box" ref="cursorBox">
<!-- 旋转环绕圈 -->
<div class="revolve" ref="revolve"></div>
<!-- 中心光标 -->
<div class="cursor" ref="cursor"></div>
<!-- 定位十字标 -->
<div class="cross"></div>
</div>
</teleport>
</template>
<style>
/* 全局样式:点击涟漪效果(需放在无scoped的style中,避免样式隔离) */
.circle {
position: fixed;
left: 0;
top: 0;
border-radius: 50%;
width: 50px;
height: 50px;
border: 1px solid white;
/* 涟漪扩散动画 */
animation: ripple .4s linear forwards;
/* 避免涟漪遮挡鼠标事件 */
pointer-events: none;
}
/* 涟漪动画关键帧 */
@keyframes ripple {
100% {
transform: scale(5);
opacity: 0;
}
}
/* 光标容器 */
.cursor-box {
width: 100px;
height: 100px;
left: 0;
top: 0;
position: fixed;
/* 关键:让光标不遮挡页面其他元素的点击事件 */
pointer-events: none;
display: flex;
justify-content: center;
align-items: center;
}
/* 旋转环绕圈 */
.revolve {
width: 20px;
height: 20px;
position: absolute;
border-radius: 50%;
border-top: 6px solid rgba(255, 255, 255, 0.5);
border-bottom: 6px solid rgba(255, 255, 255, 0.5);
border-left: 6px solid transparent;
border-right: 6px solid transparent;
/* 旋转动画 */
animation: rx 8s infinite linear;
/* 状态切换过渡效果 */
transition: 0.1s;
}
/* 旋转动画关键帧 */
@keyframes rx {
from {
transform: rotate(0deg)
}
to {
transform: rotate(360deg)
}
}
/* 中心光标 */
.cursor {
width: 40px;
height: 40px;
border-radius: 50%;
border: 4px solid white;
/* 多层阴影营造发光效果 */
box-shadow: 0 0 8px 5px rgb(255, 139, 209), 0 0 10px 5px white;
/* 径向渐变增强立体感 */
background: radial-gradient(circle, transparent, transparent, transparent, white, white);
transition: 0.1s;
}
/* 定位十字标 */
.cross {
position: absolute;
/* 用阴影实现十字标,避免额外DOM元素 */
box-shadow: 0 0 0 0.15em dodgerblue,
0.2em 0 0 0.1em dodgerblue,
-0.2em 0 0 0.1em dodgerblue,
0 0.2em 0 0.1em dodgerblue,
0 -0.2em 0 0.1em dodgerblue;
}
</style>
⚙️ 2. 脚本部分:TypeScript控制核心行为
脚本模块基于Vue3组合式API开发,负责实现光标跟随、交互响应等核心业务逻辑;。
<script setup lang="ts">
import { onMounted, onUnmounted, ref, useTemplateRef } from "vue";
// 1. 获取DOM元素引用
let cursorBox = useTemplateRef<HTMLDivElement>("cursorBox");
let revolve = useTemplateRef<HTMLDivElement>("revolve");
let cursor = useTemplateRef<HTMLDivElement>("cursor");
// 2. 存储鼠标坐标
let [x, y] = [0, 0];
// 3. 鼠标移动事件
const mousemoveFunc = (event: MouseEvent) => {
// 获取鼠标当前坐标
x = event.x;
y = event.y;
// 计算光标容器的定位(让光标中心与鼠标坐标对齐)
let top = y - cursorBox.value!.clientWidth / 2;
let left = x - cursorBox.value!.clientHeight / 2;
// 避免重复赋值导致的性能浪费
if (+cursorBox.value!.style.top !== top && +cursorBox.value!.style.left !== left) {
cursorBox.value!.style.top = top + "px";
cursorBox.value!.style.left = left + "px";
}
}
// 4. 生成点击涟漪效果
const createRipple = () => {
// 动态创建涟漪元素
const ripples = document.createElement("span");
ripples.setAttribute("class", "circle");
document.body.appendChild(ripples);
// 让涟漪中心与鼠标坐标对齐
ripples.style.top = y - ripples.clientHeight / 2 + "px";
ripples.style.left = x - ripples.clientWidth / 2 + "px";
// 涟漪动画结束后移除元素,避免DOM堆积
ripples.addEventListener("animationend", () => {
ripples.remove();
})
return ripples;
}
// 5. 鼠标按下事件:切换光标激活状态
const mousedownFunc = () => {
// 鼠标锁定时不执行(如视频全屏时)
if (document.pointerLockElement) return;
// 激活状态:旋转圈变大、中心光标放大
revolve.value!.style.width = 28 + "px";
revolve.value!.style.height = 28 + "px";
cursor.value!.style.transform = "scale(1.3)";
// 生成点击涟漪
createRipple();
}
// 6. 鼠标释放事件:恢复光标默认状态
const mouseupFunc = () => {
// 恢复默认状态
cursor.value!.style.transform = "scale(1)";
revolve.value!.style.width = 20 + "px";
revolve.value!.style.height = 20 + "px";
}
// 8. 组件挂载:绑定事件监听
onMounted(() => {
window.addEventListener("mousemove", mousemoveFunc);
window.addEventListener("mousedown", mousedownFunc);
window.addEventListener("mouseup", mouseupFunc);
// 隐藏原生光标,显示自定义光标
document.body.style.cursor = "none";
});
// 9. 组件卸载:移除事件监听
onUnmounted(() => {
window.removeEventListener("mousemove", mousemoveFunc);
window.removeEventListener("mousedown", mousedownFunc);
window.removeEventListener("mouseup", mouseupFunc);
// 恢复原生光标
document.body.style.cursor = "auto";
})
</script>++
📦 3. 组件调用方式
组件无额外依赖,在Vue3项目中按以下方式引入即可使用:
<template>
<div>
<CustomCursor />
</div>
</template>
<script setup lang="ts">
import CustomCursor from '@/components/CustomCursor.vue';
</script>