Subject: FullCalendar: Optimize eventDidMount
for "More Events" in Week View
I'm using FullCalendar with vanilla JavaScript. I'm utilizing the eventDidMount
function to style events, specifically by adding images to them.
The problem arises when a user has a large number of events (e.g., 500 events in a weekly view). In this scenario, Chrome attempts to download all event images upfront, leading to significant RAM consumption and performance issues.
My goal is to optimize eventDidMount
so that it only processes visible events initially. When the "+X more" button is clicked, I'd like the hidden events for that specific day to then have their images added. This approach should significantly improve performance.
I attempted to implement this by adding the following check at the beginning of my eventDidMount
function:
if (info.el.closest('.fc-more-popover')) {
return;
}
However, this only prevented images from being added to non-visible events. It didn't trigger the image addition when the +X more
button was clicked, because the eventDidMount function isn't called again for those already-mounted-but-hidden events.
How can I modify my eventDidMount function or implement another strategy to achieve this lazy loading of event images when the +x more
button is activated?
I found a solution and want to share it as an answer for anyone who sees this in the future.
I defined those 2 vars before "new FullCalendar"
const dayLimit = 4;
const dailyEventCounts = {};
then added this inside "FullCalendar" parameter
dayMaxEvents: dayLimit,
And, used this algorithm inside "eventDidMount"
const eventDate = info.event.start.toISOString().split('T')[0];
dailyEventCounts[eventDate] = (dailyEventCounts[eventDate] || 0) + 1;
const isWithinDailyLimit = dailyEventCounts[eventDate] <= dayLimit;
if (isWithinDailyLimit) {
mediaElement.src = filePath;
} else {
mediaElement.dataset.src = filePath; // observer will set src using data-src for visible events using dayLimit
}
Finally, I used an observer for adding images when user clicked '+X more' button;
let observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.addedNodes) {
mutation.addedNodes.forEach(function(node) {
if (node.nodeType === 1 && (node.classList.contains('fc-popover') || node.classList.contains('fc-more-popover'))) {
setTimeout(() => {
loadImagesInPopover(node);
}, 50); // small delay can be helpful
}
});
}
});
});
observer.observe(document.body, { childList: true, subtree: true });
function loadImagesInPopover(popoverEl) {
popoverEl.querySelectorAll('.fc-daygrid-event-harness').forEach(eventEl => {
// Image addition
const img = eventEl.querySelector('img[data-src]');
if (img) {
if (!img.src) {
img.src = img.dataset.src;
img.onload = () => { delete img.dataset.src; };
}
}
// Video addition (can be usable with videos too)
const video = eventEl.querySelector('video[data-src]');
if (video) {
const source = video.querySelector('source');
if (source) {
if (!source.src) {
source.src = video.dataset.src;
video.load();
delete video.dataset.src;
}
}
}
});
}