🌐 原地址 👉 h3
🫰 demo 🫰
🧐 思路分析 🧐
这样看是不是一目了然呢~ 😏
如上👆gif👆
效果可以理解为👉 以鼠标位置为圆心,产生的背景圆,与box
的间隙产生的交叉
❓ 这么实现会不会有问题呢 ❓
- 效果只在
boxes
区域出现,是不是需要判断鼠标位置来添加粉色背景圆
呢 ❓ - 而且这个只有在接触到
box
才会有粉色背景圆
,box
以外的部分是没有颜色的,这个又如何解决呢 ❓ 或许也能实现,应该会麻烦些:)
🧐 不妨换个思路 🧐
给每个box
添加背景圆
,背景圆位置
根据鼠标位置变化,👇 如下所示 👇
背景圆
大小固定(比如200px),圆心位置
如何确定呢?
👉 初始位置 (0,0)
,参照系则是参考box左上角
;
👉 动态变化的位置取(clientX - left, clientY - top)
。left
和 top
即 box
元素相对浏览器视口的位置,通过 getBoundingClientRect
方法获取
👉 取差值(clientX - left, clientY - top)
也很好理解,因为伪元素位置是参照box左上
的位置变化,这样就能在 差值(绝对值) < 半径
的时候出现在 box间隙
内
🌟 关键点 🌟
- 盒子元素
box
添加伪元素before
,设置伪元素宽高均大于父元素,效果上类似于伪元素覆盖了box,同时设置偏移量inset为负值
,实现 "居中覆盖"(这样就能留出一个"空隙"
, 即👆gif👆
中粉色圆
填充before
与box
中间空白的部分) - 给伪元素背景设置背景色,demo中用的是 径向渐变,渐变的形状为
200px
的圆形,圆心位置记为--x
和--y
,通过css变量传入,颜色自定义即可(demo中采用的是rgba(245,158,11,.7)
到transparent
的渐变)不用粉色了🤣
👀 关于--x
和 --y
的获取 👀
- 记录鼠标位置
(mouseX, mouseY)
calBoxesPosition
方法获取每个box
的位置(left,top)
并记录差值(mouseX - left, mouseY - top)
(mouseX, mouseY)
变动的时候重新触发calBoxesPosition
方法即可
🚀 关于一些优化 🚀
- 第一次页面加载调用
calBoxesPosition
后,在不滑动页面
的情况下,每个box
位置相对固定
,可以缓存
下来位置信息,避免该函数内部频繁调用getBoundingClientRect
引发的性能问题造成卡顿 - 在
滑动页面
的时候,可以将记录box
位置信息的字段重置为(0,0)
,再移动鼠标重新触发calBoxesPosition
即可
👨💻代码(vue3实现)👨💻
PS: 不太会使用掘金的代码片段
,不知道如何引入第三方库😅,如果验证代码, @vueuse/core
和 tailwindcss
请自行安装🫠
(等我查一下怎么使用,再回来贴个代码片段~ ⏰@ 4-17 14:56 )
- template 结构
js
<template>
<div
class="w-full h-full p-8 bg-white dynamic-bg-page overflow-auto"
ref="pageRef"
>
<!-- 顶部元素(测试滚动) -->
<div class="w-full h-[200px] bg-slate-500"></div>
<!-- 鼠标位置 -->
<p class="text-center mb-4 font-bold text-xl">
mouseX: {{ mouseX }}, mouseY: {{ mouseY }}
</p>
<!-- boxes -->
<div
class="box-container grid grid-cols-3 gap-8"
:style="{
'--border-color': 'rgb(229,229,229)',
'--bg-color': 'rgba(245,158,11,.7)',
}"
>
<div
v-for="(_, i) in boxes"
:key="i"
:id="_.name"
class="box flex items-center justify-center p-[1px] rounded-xl"
:style="{
'--x': `${_.elementX}px`,
'--y': `${_.elementY}px`,
}"
>
<div
class="box_inner w-full h-full bg-white rounded-xl hover:bg-opacity-70 transition-[background-opacity]"
></div>
</div>
</div>
<!-- 底部元素(测试滚动) -->
<div class="w-full h-[1000px] bg-slate-500"></div>
</div>
</template>
- css样式
js
<style lang="scss" scoped>
.box {
width: 100%;
height: 200px;
border: 1px solid var(--border-color);
border-radius: 12px;
&::before {
position: absolute;
display: block;
content: '';
width: calc(100% + 4px);
height: calc(100% + 4px);
border-radius: 12px;
inset: -2px;
background: radial-gradient(
200px circle at var(--x) var(--y),
var(--bg-color) 0,
transparent 100%
);
will-change: background;
}
}
</style>
- js 部分
js
<script setup lang="ts">
import { useMouse, useElementBounding } from '@vueuse/core'
import { nextTick, onBeforeMount, onMounted, ref, watch } from 'vue'
const NUM_BOXES = 10
const boxes = ref<any[]>([])
const pageRef = ref<HTMLElement | null>(null)
const { x: mouseX, y: mouseY } = useMouse()
const getBoxPosition = (box: HTMLElement) => {
const { left, top } = useElementBounding(box)
return { left, top }
}
const calBoxesPosition = () => {
boxes.value.forEach((_, i) => {
const box = document.getElementById(_.name) as HTMLElement
if (_.top && _.left) {
// 🚀 cached
} else {
const { left, top } = getBoxPosition(box)
// 🚀 缓存下元素的位置信息
_.left = left.value
_.top = top.value
}
boxes.value[i] = {
..._,
elementX: mouseX.value - _.left,
elementY: mouseY.value - _.top,
}
})
}
watch([mouseX, mouseY], () => {
calBoxesPosition()
})
onBeforeMount(() => {
boxes.value = Array.from({ length: NUM_BOXES }, (_, i) => {
return {
name: `box-${i + 1}`,
elementX: 0, // --x
elementY: 0, // --y
left: 0, // element bounding left
top: 0, // element bounding top
}
})
})
onMounted(() => {
nextTick(() => {
calBoxesPosition()
})
pageRef.value?.addEventListener('scroll', () => {
boxes.value.forEach((_, i) => {
_.left = 0
_.top = 0
})
})
})
</script>