You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现弹窗始终精准定位在目标日历事件元素正上方

日历弹窗定位问题求助

我正在开发一个日历项目,需求是点击选中的事件后,弹窗要精准显示在被点击元素的正上方。最开始我尝试基于点击元素的物理位置来实现,但效果非常不稳定,弹窗总是出现偏移(附件图片能展示这个问题,忽略弹窗内文本的异常格式,隐私内容已做删减)。

我想请教有没有可靠的方法,能让弹窗始终相对于黄色事件元素(就是日历中数字13上方的黄色事件框,不是底部的那个)固定在正上方,而且不管页面尺寸怎么变化,弹窗都能牢牢锁定这个位置?

以下是我尝试实现该功能的代码:

const Cal = () => {
 const [popupBoxVisibility, usePopupBoxVisibility] = useState('hidden'); //控制弹窗的显示/隐藏状态
 const [clientPosition, useClientPosition] = useState([0, 0]); //用于计算弹窗的定位
 const [eventDetails, useEventDetails] = useState(null); //弹窗中要展示的事件详情数据
 const [currMonth, useCurrMonth] = useState(null); //当前月份,用来动态调整前后切换箭头的间距
 const [currDate, useCurrDate] = useState(null); //当前点击事件的日期
 const calendarRef = useRef(null); //FullCalendar组件的ref,用于判断组件是否挂载完成
 const containerRef = useRef(null); //用于避免页面滚动影响弹窗定位

 const handleEventClick = (e) => {
 //点击事件的处理逻辑:原本想通过日历总宽度430px、7天均分来计算事件中点,但因为弹窗需要额外margin调整,最终效果不理想
 useEventDetails(e.event); //把当前点击的事件详情存入状态,传给Popupbox组件
 const pastClientPosition = clientPosition; //更新前的弹窗位置
 const pastEventDate = currDate; //上一次点击事件的日期
 const eventRect = e.el.getBoundingClientRect();
 const containerRect = containerRef.current.getBoundingClientRect();
 const newClientPosition = [
 eventRect.left - containerRect.left,
 eventRect.top - containerRect.top,
 ];
 //获取当前点击事件的边界盒信息
 useClientPosition(newClientPosition); //更新弹窗位置状态
 useCurrDate(e.event.startStr);
 console.log(`currDate: ${currDate}`);
 console.log(`pastEventDate: ${pastEventDate}`);

 if (
 JSON.stringify(pastEventDate) === JSON.stringify(e.event.startStr) ||
 JSON.stringify(pastClientPosition) === '[0,0]'
 ) {
 //判断是否点击了同一个事件,或者是首次点击,切换弹窗的显示/隐藏状态
 usePopupBoxVisibility(
 popupBoxVisibility === 'visible' ? 'hidden' : 'visible'
 );
 } else if (
 JSON.stringify(pastEventDate) !== JSON.stringify(e.event.startStr)
 ) {
 //处理特殊场景:如果之前点击事件后隐藏了弹窗,再点击不同事件时直接显示弹窗,避免无响应的情况
 usePopupBoxVisibility('visible');
 }
 };

 const handleEventEnter = (e) => {
 //鼠标悬浮事件时,修改事件背景色
 e.el.style.backgroundColor = '#ffce78';
 };

 const handleEventLeave = (e) => {
 //鼠标离开事件时,恢复原背景色
 e.el.style.backgroundColor = '#DCAA47';
 };

 return (
 <section className="calendar-section" ref={containerRef}>
 {/* 注:原代码中前后切换箭头的JSX部分存在内容截断,此处保留原样 */}
 {/* <div className="prev-button" onClick={(e) => {
 if (calendarRef.current) {
 const calendar = calendarRef.current.getApi();
 calendar.prev();
 }
 }} style={{ marginLeft: calendarRef.current ? {isBigScreen} ? `${-110 - 3 * currMonth.length}px` : `${80 - 3* currMonth.length}px` : `-110px`, marginTop: {isBigScreen} ? '133.5px' : '14px',
 }} ></div>
 <div className="next-button" onClick={(e) => {
 if (calendarRef.current) {
 const calendar = calendarRef.current.getApi();
 calendar.next();
 }
 }} style={{ marginLeft: calendarRef.current ? {isBigScreen} ? `${25 + 3 * currMonth.length}px` : `${-90 + 3 * currMonth.length}` : `25px` }} ></div> */}
 {/* 此处应包含FullCalendar组件及Popupbox组件的代码,原内容未完整提供 */}
 </section>
 );
};

内容的提问来源于stack exchange,提问作者zichyboy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 10:17:32