使用Vue3实现鼠标跟随效果
编辑1. 创建组件基本结构
首先,创建一个 Vue3 组件,我们把它命名为 PageCursor.vue。基本结构如下:
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
const props = withDefaults(defineProps<{
hideCursorSelector?: string | string[]
}>(), {
hideCursorSelector: '.hide-page-cursor'
})
const cursor = ref<HTMLElement | null>(null)
const cursorType = ref('auto')
const cursorState = ref('')
onMounted(() => {})
onUnmounted(() => {})
</script>
<template>
<div
ref="cursor"
class="page-cursor"
:class="[cursorType, cursorState]"
></div>
</template>
<style lang="scss" scoped>
.page-cursor {
--cursor-size: 20px;
position: fixed;
z-index: 9999;
top: calc(-1 * var(--cursor-size) / 2);
left: calc(-1 * var(--cursor-size) / 2);
width: var(--cursor-size);
height: var(--cursor-size);
border-radius: 50%;
backdrop-filter: invert(100%);
pointer-events: none;
opacity: 0;
}
</style>
在组件中,我们定义了 props 对象、三个响应式对象和一个 page-cursor 样式类
props 对象用于接收参数。使用 props 传参可以在外部引用组件时控制组件的样式或行为,这里我们只定义了一个 hideCursorSelector
参数用于设置隐藏光标这个行为。
三个响应式对象分别是:
cursor
:跟随鼠标运动的光标元素。cursorType
:光标的类型。cursorState
:光标的状态。
page-cursor 样式类:
--cursor-size
:主要是设置光标的大小,后续有多个地方会用到,所以将其定义为 CSS 变量。top
、left
、width
、height
:基于--cursor-size
变量进行位置和大小的设置。backdrop-filter
:将其值设置为invert(100%)
为光标后面区域添加反色效果。pointer-events
:将其值设置为none
来禁用光标的指针事件,使其不会影响页面上其他元素的交互。
2. 添加鼠标响应事件
添加鼠标响应事件(移动、按下、弹起)并在组件挂载时注册事件,在组件卸载时移除事件:
function onMousemove() {}
function onMousedown() {}
function onMouseup() {}
onMounted(() => {
document.addEventListener('mousemove', onMousemove)
document.addEventListener('mousedown', onMousedown)
document.addEventListener('mouseup', onMouseup)
})
onUnmounted(() => {
document.removeEventListener('mousemove', onMousemove)
document.removeEventListener('mousedown', onMousedown)
document.removeEventListener('mouseup', onMouseup)
})
3. 实现具体功能
在 onMousedown
和 onMouseup
中修改光标状态:
function onMousedown() {
cursorState.value = 'pressed'
}
function onMouseup() {
cursorState.value = ''
}
在 onMousemove
事件中获取鼠标位置,并在 requestAnimationFrame
方法中进行更新位置:
let myReq: number = 0
function onMousemove(event: MouseEvent) {
if(!cursor.value) return
cancelAnimationFrame(myReq)
const { clientX, clientY } = event
const target = event.target as HTMLElement
myReq = requestAnimationFrame(() => {
const style = cursor.value!.style
style.transform = `translate3d(${clientX}px, ${clientY}px, 0)`
cursorType.value = getComputedStyle(target)?.cursor || 'auto'
const hideCursorSelectorList = Array.isArray(props.hideCursorSelector)
? props.hideCursorSelector
: [props.hideCursorSelector]
const hideCursor = hideCursorSelectorList.some(item => target.closest(item) !== null)
style.opacity = hideCursor ? '0' : '1'
style.transition = hideCursor ? '0.2s ease-out' : '0.125s ease-out'
})
}
在这段代码中,首先使用 cancelAnimationFrame
方法关闭之前创建的动画帧任务。然后获取当前鼠标的坐标和指向的元素。利用 requestAnimationFrame
方法在下一帧渲染前进行样式设置,以防止在同一帧内执行多次样式设置。并使用 getComputedStyle
方法获取当前鼠标指向元素的 CSS 属性,并从中获取鼠标指针的类型。
最后,将 hideCursorSelector
格式化为 hideCursorSelectorList
,通过检查鼠标指向元素与 hideCursorSelectorList
匹配特定选择器且离当前元素最近的祖先元素是否存在来判断是否隐藏光标。
4. 光标的状态设置
.page-cursor {
// 其他 page-cursor 样式
// 鼠标光标类型为指针时
&.pointer {
--cursor-size: 40px;
// 指针类型并且按下时
&.pressed {
--cursor-size: 20px;
}
}
// 默认类型按下时
&.pressed {
--cursor-size: 10px;
}
}
你还可以在不同的鼠标事件中对 cursorState
和 cursorType
进行赋值,并对 page-cursor
样式类进行更多的定义,来实现更多光标形态的展示。
5. 完整代码
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
const props = withDefaults(defineProps<{
hideCursorSelector?: string | string[]
}>(), {
hideCursorSelector: '.hide-page-cursor'
})
const cursor = ref<HTMLElement | null>(null)
const cursorType = ref('auto')
const cursorState = ref('')
let myReq: number = 0
function onMousemove(event: MouseEvent) {
if(!cursor.value) return
cancelAnimationFrame(myReq)
const { clientX, clientY } = event
const target = event.target as HTMLElement
myReq = requestAnimationFrame(() => {
const style = cursor.value!.style
style.transform = `translate3d(${clientX}px, ${clientY}px, 0)`
cursorType.value = getComputedStyle(target)?.cursor || 'auto'
const hideCursorSelectorList = Array.isArray(props.hideCursorSelector)
? props.hideCursorSelector
: [props.hideCursorSelector]
const hideCursor = hideCursorSelectorList.some(item => target.closest(item) !== null)
style.opacity = hideCursor ? '0' : '1'
style.transition = hideCursor ? '0.2s ease-out' : '0.125s ease-out'
})
}
function onMousedown() {
cursorState.value = 'pressed'
}
function onMouseup() {
cursorState.value = ''
}
onMounted(() => {
globalThis.document.addEventListener('mousemove', onMousemove)
globalThis.document.addEventListener('mousedown', onMousedown)
globalThis.document.addEventListener('mouseup', onMouseup)
})
onUnmounted(() => {
globalThis.document.removeEventListener('mousemove', onMousemove)
globalThis.document.removeEventListener('mousedown', onMousedown)
globalThis.document.removeEventListener('mouseup', onMouseup)
})
</script>
<template>
<div
ref="cursor"
class="page-cursor"
:class="[cursorType, cursorState]"
></div>
</template>
<style lang="scss" scoped>
.page-cursor {
--cursor-size: 20px;
position: fixed;
z-index: 9999;
top: calc(-1 * var(--cursor-size) / 2);
left: calc(-1 * var(--cursor-size) / 2);
width: var(--cursor-size);
height: var(--cursor-size);
border-radius: 50%;
backdrop-filter: invert(100%);
pointer-events: none;
opacity: 0;
&.pointer {
--cursor-size: 40px;
&.pressed {
--cursor-size: 20px;
}
}
&.pressed {
--cursor-size: 10px;
}
}
</style>
- 0
- 0
-
分享