FullCalendar中insertBefore()添加的CSS tooltip无法始终置顶问题
问题描述
给FullCalendar资源时间线视图的事件添加自定义tooltip时,tooltip会被相邻事件遮挡,调整z-index、position属性都无法解决,效果对比如下:
- 被遮挡效果

- 正常显示效果

对应Angular组件源码如下:
resource-timeline.component.html
<div class="container-fluid mt-2" style="padding-left: 1%; padding-right: 1%"> <div> <div class="row"> <div class="col-xl-12 col-md-12 col-sm-12"> <div class="p-3"> <full-calendar #resourceTimeline [options]="resourceTimelineOptions"></full-calendar> </div> </div> </div> </div> </div>
resource-timeline.component.scss
.timeline-tooltip { background-color: cornflowerblue; color: #fff; text-align: center; border-radius: 6px; padding: 5px; position: absolute; z-index: 9999; width: 120px; word-wrap: break-word; top: 100%; left: 50%; margin-left: -60px; &::after { content: ""; position: absolute; bottom: 100%; left: 50%; margin-left: -5px; border-width: 5px; border-style: solid; border-color: transparent transparent black transparent; } }
resource-timeline.component.ts
import {Component, ElementRef, OnInit, Renderer2, ViewChild} from '@angular/core' import {CalendarOptions, EventContentArg, FullCalendarComponent} from "@fullcalendar/angular" import resourceTimelinePlugin from '@fullcalendar/resource-timeline' import {ActivatedRoute, Router} from '@angular/router' import {EventHoveringArg} from "@fullcalendar/common" @Component({ selector: 'resource-timeline', templateUrl: './resource-timeline.component.html', styleUrls: ['./resource-timeline.component.scss'] }) export class ResourceTimelineComponent implements OnInit { @ViewChild('fc-timeline-event.fc-event-start', {static: false}) calendarEvent: ElementRef toolTipDiv: ElementRef @ViewChild('resourceTimeline') calendar: FullCalendarComponent resourceTimelineOptions: CalendarOptions = { schedulerLicenseKey: "CC-Attribution-NonCommercial-NoDerivatives", initialView: 'resourceTimelineDay', height: window.innerHeight - 380, eventMinWidth: 5, titleFormat: {year: 'numeric', month: 'long', day: '2-digit'}, plugins: [ resourceTimelinePlugin ], headerToolbar: { center: 'resourceTimelineDay', right: 'prev,today,next' }, resourceAreaColumns: [ { headerContent: 'Bench Name', field: 'resourceName', }, { headerContent: 'Description', field: 'description', } ], weekNumbers: true, firstDay: 1, scrollTime: '07:00:00', selectable: false, selectMirror: false, selectOverlap: false, eventOverlap: false, editable: false, nowIndicator: true, buttonText: { day: 'Day', week: 'Week', month: 'Month', today: 'Today' }, eventDidMount: (eventInfo: EventContentArg) => { }, eventMouseEnter: (eventInfo: EventHoveringArg) => { this.removeToolTip() this.insertToolTip(eventInfo.event.title, eventInfo.el) }, eventMouseLeave: (eventInfo: EventHoveringArg) => { this.removeToolTip() } } constructor(private router: Router, private activatedRoute: ActivatedRoute, private renderer: Renderer2, private domElement: ElementRef) { } ngOnInit(): void { this.resourceTimelineOptions.resources = [ { "id": "id1", "resourceName": "Resource 1", "description": "Description 1" }, { "id": "id2", "resourceName": "Resource 2", "description": "Description 2" } ] this.resourceTimelineOptions.events = [ { id: "id1", resourceId: "id1", title: 'All Day Event', start: '2022-06-13' }, { id: "id2", resourceId: "id2", title: 'Long Event', start: '2022-06-13', end: '2022-06-14' } ] } private insertToolTip(tooltipText: string, eventElement: HTMLElement): void { this.toolTipDiv = this.renderer.createElement('div') this.renderer.setAttribute(this.toolTipDiv, 'class', 'timeline-tooltip') const text = this.renderer.createText(tooltipText) this.renderer.appendChild(this.toolTipDiv, text) this.renderer.insertBefore(eventElement, this.toolTipDiv, this.calendarEvent) } private removeToolTip(): void { if (this.toolTipDiv !== undefined) { const elements = this.domElement.nativeElement.querySelectorAll('.timeline-tooltip') elements.forEach(e => { this.renderer.removeChild(e.parentElement, e) }) } } }
问题原因
遮挡的核心问题有两个:
- 你把tooltip作为子元素插入到了FullCalendar的事件DOM节点内部,FullCalendar的事件节点默认会创建独立层叠上下文,子元素的z-index只会在当前父元素的层叠范围内生效,永远不会高过父级同层的其他事件节点;同时部分FullCalendar容器自带
overflow: hidden属性,超出事件范围的内容会被直接裁剪。 - 你写的
@ViewChild('fc-timeline-event.fc-event-start')是无效的,@ViewChild只能获取模板里静态声明、挂了模板引用变量的元素,FullCalendar动态生成的事件节点没法通过这个方式拿到,原有插入逻辑本身就没有正确的参考节点。
修复方案
不需要调整z-index数值,改两个地方即可:
1. 修改tooltip挂载逻辑
不要把tooltip插到事件元素内部,直接挂载到FullCalendar根容器下,脱离事件的层叠上下文限制,同时手动计算tooltip位置:
private insertToolTip(tooltipText: string, eventElement: HTMLElement): void { this.removeToolTip() // 创建tooltip节点 this.toolTipDiv = this.renderer.createElement('div') this.renderer.setAttribute(this.toolTipDiv, 'class', 'timeline-tooltip') const text = this.renderer.createText(tooltipText) this.renderer.appendChild(this.toolTipDiv, text) // 挂载到日历根容器,不要挂在事件元素内部 const calendarRoot = this.calendar.getApi().el this.renderer.appendChild(calendarRoot, this.toolTipDiv) // 计算位置,保持事件下方居中的显示效果 const eventRect = eventElement.getBoundingClientRect() const calendarRect = calendarRoot.getBoundingClientRect() const topOffset = eventRect.bottom - calendarRect.top + 8 // 加8px和事件拉开间距 const leftOffset = eventRect.left - calendarRect.left + eventRect.width/2 - 60 // 60是tooltip宽度的一半 this.renderer.setStyle(this.toolTipDiv, 'top', `${topOffset}px`) this.renderer.setStyle(this.toolTipDiv, 'left', `${leftOffset}px`) }
2. 调整样式
给日历根容器加相对定位,修正小箭头颜色,删除原有写死的定位属性:
:host ::ng-deep full-calendar { position: relative; } .timeline-tooltip { background-color: cornflowerblue; color: #fff; text-align: center; border-radius: 6px; padding: 5px; position: absolute; z-index: 9999; width: 120px; word-wrap: break-word; // 删除原有的top、left、margin-left属性,位置由TS动态计算 &::after { content: ""; position: absolute; bottom: 100%; left: 50%; margin-left: -5px; border-width: 5px; border-style: solid; // 原来写的black和tooltip背景色不匹配,改成同色 border-color: transparent transparent cornflowerblue transparent; } }
可选优化
如果日历区域有横向/纵向滚动,建议给日历滚动容器加滚动监听,滚动时直接触发removeToolTip()隐藏tooltip,避免位置错位。
内容的提问来源于stack exchange,提问作者Ivo
相关产品推荐
相关产品推荐

