解决日历事件顶部调整大小时底部抖动问题
解决日历事件顶部调整大小的抖动问题
问题根源
你遇到的抖动问题,核心原因是同时使用transform: translateY()修改位置和直接调整height,再加上父组件同时设置了top和bottom的绝对定位,导致浏览器在重排布局时出现计算冲突——浏览器既要根据top/bottom自动计算高度,又要处理你手动设置的height和transform,两种逻辑互相干扰,就出现了底部抖动。而底部调整时只修改height,没有触发位置计算冲突,所以正常。
修复方案
1. 简化父组件定位逻辑
父组件去掉bottom样式绑定,只通过top和height来控制事件的位置和大小,避免浏览器自动计算高度的逻辑干扰:
<user-calendar-event class="absolute px-1" [style.top]="event.top" <!-- 移除[style.bottom]绑定 --> [style.height]="event.height" <!-- 新增height绑定,替代bottom逻辑 --> [style.opacity]="eventOpacityChange(event)" [style.z-index]="hoveredEvent === event.id ? 10 : 1" [event]="event" [color]="event ? hoverColorChange(event.id, event.calendar_id) : ''" [weeklyEvent]="true" (newHeightAndTop)="onEventResizeTop($event, event)" <!-- 新增传递top+height的事件 --> (newHeight)="onEventResizeBottom($event, event)" (resizingEvent)="isResizing = $event" (click)="onClickedEvent($event)" (mouseover)="hoveredEventId(event.id)" (mouseleave)="onMouseLeave()" > </user-calendar-event>
2. 重写顶部调整逻辑,移除transform
不再用transform偏移位置,而是直接修改父组件的top值和当前组件的height,让布局计算完全基于标准定位属性,避免冲突:
startResizing(event: MouseEvent, position: string) { this.resizingEvent.emit(true); const startY = event.clientY; const startHeight = this.container.nativeElement.offsetHeight; const viewportHeight = window.innerHeight; const heightPercentage = 100 / viewportHeight; const pixelsPerTimeSlot = 3 / heightPercentage; // 顶部调整时记录父元素初始top值 const parentElement = this.container.nativeElement.parentElement; const startTop = position === 'top' ? parseInt(parentElement.style.top, 10) : 0; const mouseMove = (moveEvent: MouseEvent) => { const currentY = moveEvent.clientY; const diffY = currentY - startY; const timeSlotsMoved = Math.abs(diffY) / pixelsPerTimeSlot; const slotPixels = Math.round(timeSlotsMoved) * pixelsPerTimeSlot; let newHeight = startHeight; let newTop = startTop; if (position === 'top') { // 向上拖动:top减小,height增加;向下拖动:top增加,height减小 newTop = diffY > 0 ? startTop + slotPixels : startTop - slotPixels; newHeight = diffY > 0 ? startHeight - slotPixels : startHeight + slotPixels; // 更新父元素top,限制最小top为0 parentElement.style.top = `${Math.max(newTop, 0)}px`; } else { // 底部调整逻辑保持不变 newHeight = diffY > 0 ? startHeight + slotPixels : startHeight - slotPixels; } // 限制事件最小高度 const finalHeight = Math.max(newHeight, 20); this.container.nativeElement.style.height = `${finalHeight}px`; }; const mouseUp = () => { if (position === 'top') { const parentElement = this.container.nativeElement.parentElement; const finalTop = parseInt(parentElement.style.top, 10); const finalHeight = this.container.nativeElement.offsetHeight; // 向父组件传递top和height的百分比值 this.newHeightAndTop.emit({ topPercent: finalTop * heightPercentage, heightPercent: finalHeight * heightPercentage }); } else { const finalHeight = this.container.nativeElement.offsetHeight; const heightInPercent = finalHeight * heightPercentage; this.newHeight.emit(heightInPercent); } window.removeEventListener('mousemove', mouseMove); window.removeEventListener('mouseup', mouseUp); this.resizingEvent.emit(false); }; window.addEventListener('mousemove', mouseMove); window.addEventListener('mouseup', mouseUp); }
3. 更新父组件事件处理
新增onEventResizeTop方法,同步更新事件的top和height值:
onEventResizeTop(data: {topPercent: number, heightPercent: number}, event: any) { event.top = `${data.topPercent}%`; event.height = `${data.heightPercent}%`; // 后续保存/刷新逻辑... } onEventResizeBottom(heightPercent: number, event: any) { event.height = `${heightPercent}%`; // 后续保存/刷新逻辑... }
方案有效性说明
- 去掉父组件
bottom绑定后,浏览器不再自动计算事件高度,完全由我们通过top和height控制,避免了布局计算冲突。 - 移除
transform后,所有位置和大小修改都基于标准绝对定位属性,浏览器重排逻辑更稳定,不会出现抖动。 - 顶部调整时同步修改
top和height,逻辑和底部调整保持一致,保证了布局计算的统一性。
内容的提问来源于stack exchange,提问作者Eric
相关产品推荐
相关产品推荐

