200字
在网页上实现OSU光标
2025-11-30
2026-03-15

🎯 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>

评论