perf: Refactor menu item height synchronization to prevent layout thrashing and introduce a debounced resize listener.
This commit is contained in:
@@ -1,3 +1,10 @@
|
||||
## v1.6.25 (2026-03-12)
|
||||
- ⚡ **Performance**: Debounced Resize-Listener hinzugefügt. Die Höhen-Synchronisierung der Menü-Karten wird nun auch bei Viewport-Änderungen (z.B. Fenster-Skalierung oder Orientierungswechsel) automatisch und effizient ausgeführt.
|
||||
- 🧹 **Tech**: `debounce` Utility-Funktion in `utils.js` ergänzt.
|
||||
|
||||
## v1.6.24 (2026-03-12)
|
||||
- ⚡ **Performance**: Layout Thrashing in `syncMenuItemHeights` behoben. Durch Batch-Verarbeitung von DOM-Lese- und Schreibvorgängen wurde die Rendering-Effizienz beim Wochenwechsel verbessert.
|
||||
|
||||
## v1.6.23 (2026-03-12)
|
||||
- 🎨 **UI**: Umfassende UI-Verbesserungen umgesetzt:
|
||||
- **Glassmorphism**: Header-Hintergrundtransparenz auf 72% reduziert (war 90%) – der Blur-Effekt ist nun beim Scrollen sichtbar.
|
||||
|
||||
2
dist/bookmarklet-payload.js
vendored
2
dist/bookmarklet-payload.js
vendored
File diff suppressed because one or more lines are too long
2
dist/bookmarklet.txt
vendored
2
dist/bookmarklet.txt
vendored
File diff suppressed because one or more lines are too long
21
dist/install.html
vendored
21
dist/install.html
vendored
File diff suppressed because one or more lines are too long
77
dist/kantine-standalone.html
vendored
77
dist/kantine-standalone.html
vendored
File diff suppressed because one or more lines are too long
69
dist/kantine.bundle.js
vendored
69
dist/kantine.bundle.js
vendored
@@ -1578,9 +1578,10 @@ function setLangMode(lang) {
|
||||
/* harmony export */ OR: () => (/* binding */ renderVisibleWeeks),
|
||||
/* harmony export */ Ux: () => (/* binding */ checkForUpdates),
|
||||
/* harmony export */ gJ: () => (/* binding */ updateNextWeekBadge),
|
||||
/* harmony export */ showErrorModal: () => (/* binding */ showErrorModal)
|
||||
/* harmony export */ showErrorModal: () => (/* binding */ showErrorModal),
|
||||
/* harmony export */ wy: () => (/* binding */ syncMenuItemHeights)
|
||||
/* harmony export */ });
|
||||
/* unused harmony exports syncMenuItemHeights, createDayCard, fetchVersions, updateCountdown, removeCountdown */
|
||||
/* unused harmony exports createDayCard, fetchVersions, updateCountdown, removeCountdown */
|
||||
/* harmony import */ var _state_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(901);
|
||||
/* harmony import */ var _utils_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(413);
|
||||
/* harmony import */ var _constants_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(521);
|
||||
@@ -1738,23 +1739,39 @@ function renderVisibleWeeks() {
|
||||
function syncMenuItemHeights(grid) {
|
||||
const cards = grid.querySelectorAll('.menu-card');
|
||||
if (cards.length === 0) return;
|
||||
|
||||
// 1. Gather all menu-item groups (rows) across cards
|
||||
const itemRows = [];
|
||||
let maxItems = 0;
|
||||
cards.forEach(card => {
|
||||
maxItems = Math.max(maxItems, card.querySelectorAll('.menu-item').length);
|
||||
|
||||
const cardItems = Array.from(cards).map(card => {
|
||||
const items = Array.from(card.querySelectorAll('.menu-item'));
|
||||
maxItems = Math.max(maxItems, items.length);
|
||||
return items;
|
||||
});
|
||||
|
||||
for (let i = 0; i < maxItems; i++) {
|
||||
let maxHeight = 0;
|
||||
const itemsAtPos = [];
|
||||
cards.forEach(card => {
|
||||
const items = card.querySelectorAll('.menu-item');
|
||||
if (items[i]) {
|
||||
items[i].style.height = 'auto';
|
||||
maxHeight = Math.max(maxHeight, items[i].offsetHeight);
|
||||
itemsAtPos.push(items[i]);
|
||||
}
|
||||
});
|
||||
itemsAtPos.forEach(item => { item.style.height = `${maxHeight}px`; });
|
||||
// Collect i-th item from each card (forming a "row")
|
||||
itemRows[i] = cardItems.map(items => items[i]).filter(item => !!item);
|
||||
}
|
||||
|
||||
// 2. Batch Reset (Write phase) - clear old heights to let them flow naturally
|
||||
itemRows.flat().forEach(item => {
|
||||
item.style.height = 'auto';
|
||||
});
|
||||
|
||||
// 3. Batch Read (Read phase) - measure all heights in one pass to avoid layout thrashing
|
||||
const rowMaxHeights = itemRows.map(row => {
|
||||
return Math.max(...row.map(item => item.offsetHeight));
|
||||
});
|
||||
|
||||
// 4. Batch Apply (Write phase) - set synchronized heights
|
||||
itemRows.forEach((row, i) => {
|
||||
const height = `${rowMaxHeights[i]}px`;
|
||||
row.forEach(item => {
|
||||
item.style.height = height;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createDayCard(day) {
|
||||
@@ -2329,6 +2346,7 @@ function updateAlarmBell() {
|
||||
/* harmony export */ U4: () => (/* binding */ isNewer),
|
||||
/* harmony export */ ZD: () => (/* binding */ escapeHtml),
|
||||
/* harmony export */ gs: () => (/* binding */ getRelativeTime),
|
||||
/* harmony export */ sg: () => (/* binding */ debounce),
|
||||
/* harmony export */ sn: () => (/* binding */ getISOWeek)
|
||||
/* harmony export */ });
|
||||
/* unused harmony export splitLanguage */
|
||||
@@ -2582,6 +2600,18 @@ function getLocalizedText(text) {
|
||||
return split.de || split.raw;
|
||||
}
|
||||
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/***/ }
|
||||
|
||||
@@ -2884,6 +2914,8 @@ var constants = __webpack_require__(521);
|
||||
var api = __webpack_require__(672);
|
||||
// EXTERNAL MODULE: ./src/i18n.js
|
||||
var i18n = __webpack_require__(646);
|
||||
// EXTERNAL MODULE: ./src/utils.js
|
||||
var utils = __webpack_require__(413);
|
||||
;// ./src/events.js
|
||||
|
||||
|
||||
@@ -2892,6 +2924,7 @@ var i18n = __webpack_require__(646);
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Updates all static UI labels/tooltips to match the current language.
|
||||
* Called when the user switches the language toggle.
|
||||
@@ -3251,6 +3284,12 @@ function bindEvents() {
|
||||
(0,actions/* updateAuthUI */.i_)();
|
||||
(0,ui_helpers/* renderVisibleWeeks */.OR)();
|
||||
});
|
||||
|
||||
// Sync heights on window resize (FR-Performance)
|
||||
window.addEventListener('resize', (0,utils/* debounce */.sg)(() => {
|
||||
const grid = document.querySelector('.days-grid');
|
||||
if (grid) (0,ui_helpers/* syncMenuItemHeights */.wy)(grid);
|
||||
}, 150));
|
||||
}
|
||||
|
||||
;// ./src/index.js
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { displayMode, langMode, authToken, currentUser, orderMap, userFlags, pollIntervalId, setLangMode, setDisplayMode, setAuthToken, setCurrentUser, setOrderMap } from './state.js';
|
||||
import { updateAuthUI, loadMenuDataFromAPI, fetchOrders, startPolling, stopPolling, fetchFullOrderHistory, addHighlightTag, renderTagsList, refreshFlaggedItems } from './actions.js';
|
||||
import { renderVisibleWeeks, openVersionMenu, updateNextWeekBadge, updateAlarmBell } from './ui_helpers.js';
|
||||
import { renderVisibleWeeks, openVersionMenu, updateNextWeekBadge, updateAlarmBell, syncMenuItemHeights } from './ui_helpers.js';
|
||||
import { API_BASE, GUEST_TOKEN, LS } from './constants.js';
|
||||
import { apiHeaders } from './api.js';
|
||||
import { t } from './i18n.js';
|
||||
import { debounce } from './utils.js';
|
||||
|
||||
/**
|
||||
* Updates all static UI labels/tooltips to match the current language.
|
||||
@@ -364,4 +365,10 @@ export function bindEvents() {
|
||||
updateAuthUI();
|
||||
renderVisibleWeeks();
|
||||
});
|
||||
|
||||
// Sync heights on window resize (FR-Performance)
|
||||
window.addEventListener('resize', debounce(() => {
|
||||
const grid = document.querySelector('.days-grid');
|
||||
if (grid) syncMenuItemHeights(grid);
|
||||
}, 150));
|
||||
}
|
||||
|
||||
@@ -149,23 +149,39 @@ export function renderVisibleWeeks() {
|
||||
export function syncMenuItemHeights(grid) {
|
||||
const cards = grid.querySelectorAll('.menu-card');
|
||||
if (cards.length === 0) return;
|
||||
|
||||
// 1. Gather all menu-item groups (rows) across cards
|
||||
const itemRows = [];
|
||||
let maxItems = 0;
|
||||
cards.forEach(card => {
|
||||
maxItems = Math.max(maxItems, card.querySelectorAll('.menu-item').length);
|
||||
|
||||
const cardItems = Array.from(cards).map(card => {
|
||||
const items = Array.from(card.querySelectorAll('.menu-item'));
|
||||
maxItems = Math.max(maxItems, items.length);
|
||||
return items;
|
||||
});
|
||||
|
||||
for (let i = 0; i < maxItems; i++) {
|
||||
let maxHeight = 0;
|
||||
const itemsAtPos = [];
|
||||
cards.forEach(card => {
|
||||
const items = card.querySelectorAll('.menu-item');
|
||||
if (items[i]) {
|
||||
items[i].style.height = 'auto';
|
||||
maxHeight = Math.max(maxHeight, items[i].offsetHeight);
|
||||
itemsAtPos.push(items[i]);
|
||||
}
|
||||
});
|
||||
itemsAtPos.forEach(item => { item.style.height = `${maxHeight}px`; });
|
||||
// Collect i-th item from each card (forming a "row")
|
||||
itemRows[i] = cardItems.map(items => items[i]).filter(item => !!item);
|
||||
}
|
||||
|
||||
// 2. Batch Reset (Write phase) - clear old heights to let them flow naturally
|
||||
itemRows.flat().forEach(item => {
|
||||
item.style.height = 'auto';
|
||||
});
|
||||
|
||||
// 3. Batch Read (Read phase) - measure all heights in one pass to avoid layout thrashing
|
||||
const rowMaxHeights = itemRows.map(row => {
|
||||
return Math.max(...row.map(item => item.offsetHeight));
|
||||
});
|
||||
|
||||
// 4. Batch Apply (Write phase) - set synchronized heights
|
||||
itemRows.forEach((row, i) => {
|
||||
const height = `${rowMaxHeights[i]}px`;
|
||||
row.forEach(item => {
|
||||
item.style.height = height;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function createDayCard(day) {
|
||||
|
||||
12
src/utils.js
12
src/utils.js
@@ -246,3 +246,15 @@ export function getLocalizedText(text) {
|
||||
if (langMode === 'en') return split.en || split.raw;
|
||||
return split.de || split.raw;
|
||||
}
|
||||
|
||||
export function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
v1.6.23
|
||||
v1.6.25
|
||||
|
||||
Reference in New Issue
Block a user