91 lines
2.0 KiB
Vue
91 lines
2.0 KiB
Vue
<template>
|
|
<div v-if="visible" class="spotlight-overlay">
|
|
<div
|
|
class="spotlight-hole"
|
|
:style="{
|
|
top: rect.top + 'px',
|
|
left: rect.left + 'px',
|
|
width: rect.width + 'px',
|
|
height: rect.height + 'px',
|
|
borderRadius: borderRadius + 'px',
|
|
boxShadow: `0 0 0 9999px rgba(0,0,0,0.6)`
|
|
}"
|
|
></div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { reactive, watch, onMounted, onBeforeUnmount } from 'vue'
|
|
|
|
interface Props {
|
|
/** 是否可见 */
|
|
visible?: boolean
|
|
/** 目标元素选择器 */
|
|
targetSelector?: string | null
|
|
/** 额外内边距 */
|
|
padding?: number
|
|
/** 圆角 */
|
|
borderRadius?: number
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
visible: false,
|
|
targetSelector: null,
|
|
padding: 8,
|
|
borderRadius: 16
|
|
})
|
|
|
|
const rect = reactive({ top: 0, left: 0, width: 0, height: 0 })
|
|
|
|
const measure = () => {
|
|
if (!props.visible || !props.targetSelector) return
|
|
const el = document.querySelector(props.targetSelector) as HTMLElement | null
|
|
if (!el) return
|
|
const r = el.getBoundingClientRect()
|
|
const pad = Math.max(0, props.padding)
|
|
// 使用视口坐标(fixed层),不叠加滚动偏移
|
|
rect.top = r.top - pad
|
|
rect.left = r.left - pad
|
|
rect.width = r.width + pad * 2
|
|
rect.height = r.height + pad * 2
|
|
}
|
|
|
|
watch(
|
|
() => [props.visible, props.targetSelector, props.padding],
|
|
() => {
|
|
requestAnimationFrame(measure)
|
|
},
|
|
{ immediate: true }
|
|
)
|
|
|
|
const onResize = () => measure()
|
|
const onScroll = () => measure()
|
|
|
|
onMounted(() => {
|
|
window.addEventListener('resize', onResize)
|
|
window.addEventListener('scroll', onScroll, { passive: true })
|
|
measure()
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
window.removeEventListener('resize', onResize)
|
|
window.removeEventListener('scroll', onScroll)
|
|
})
|
|
|
|
const borderRadius = props.borderRadius
|
|
</script>
|
|
|
|
<style scoped>
|
|
.spotlight-overlay {
|
|
position: fixed;
|
|
inset: 0;
|
|
pointer-events: auto;
|
|
z-index: 100000;
|
|
}
|
|
|
|
.spotlight-hole {
|
|
position: absolute;
|
|
pointer-events: none;
|
|
}
|
|
</style>
|