В интерфейсах с большим количеством виджетов нагрузка на браузер может существенно возрастать. Каждый элемент сетки может содержать сложный DOM, графики, таблицы или сторонние компоненты. При одновременной инициализации всех элементов страница перегружается: увеличивается время загрузки, возрастает потребление памяти, ухудшается отзывчивость интерфейса.
Ленивая загрузка (lazy loading) — это подход, при котором содержимое элементов создаётся только тогда, когда оно действительно требуется. В контексте Gridstack.js этот механизм позволяет:
Основная идея состоит в том, что контейнер виджета создаётся сразу, но его содержимое загружается только при необходимости.
Каждый элемент Gridstack имеет стандартную структуру:
<div class="grid-stack-item">
<div class="grid-stack-item-content">
<!-- содержимое -->
</div>
</div>
Для ленивой загрузки обычно используется следующий принцип:
Пример базовой разметки:
<div class="grid-stack">
<div class="grid-stack-item" gs-w="4" gs-h="3">
<div class="grid-stack-item-content" data-widget="chart"></div>
</div>
</div>
Здесь атрибут data-widget используется как маркер
будущей загрузки.
Создание экземпляра Gridstack:
const grid = GridStack.init({
cellHeight: 80,
margin: 5
});
После инициализации сетка готова к работе, однако содержимое виджетов пока отсутствует.
Ленивая загрузка реализуется через проверку состояния элемента. Если содержимое ещё не было создано, выполняется его генерация.
Пример функции загрузки:
function loadWidgetContent(element) {
if (element.dataset.loaded) return;
const type = element.dataset.widget;
if (type === "chart") {
element.innerHTML = "<canvas id='chart'></canvas>";
}
element.dataset.loaded = "true";
}
В этом примере:
data-widget определяет тип компонента;data-loaded предотвращает повторную загрузку.Часто используется стратегия загрузки при попадании элемента в область видимости. Для этого применяется Intersection Observer API.
Создание наблюдателя:
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
loadWidgetContent(entry.target);
observer.unobserve(entry.target);
}
});
});
Подключение элементов:
document.querySelectorAll(".grid-stack-item-content")
.forEach(el => observer.observe(el));
Механизм работает следующим образом:
Такой подход особенно полезен при длинных панелях мониторинга.
Gridstack позволяет динамически добавлять виджеты. В этом случае ленивую загрузку необходимо учитывать сразу.
Добавление элемента:
grid.addWidget({
w: 4,
h: 3,
content: `<div class="grid-stack-item-content" data-widget="table"></div>`
});
После добавления необходимо подключить наблюдатель:
const widget = document.querySelector('[data-widget="table"]');
observer.observe(widget);
Таким образом, новый элемент также будет загружен только при необходимости.
Для улучшения восприятия интерфейса часто применяются плейсхолдеры — временные заглушки, отображающиеся до загрузки содержимого.
Пример:
<div class="grid-stack-item-content" data-widget="chart">
<div class="widget-placeholder">
Loading...
</div>
</div>
После загрузки:
element.innerHTML = `
<canvas class="chart"></canvas>
`;
CSS-плейсхолдер:
.widget-placeholder {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
background: #f3f3f3;
color: #888;
}
Это предотвращает визуальные скачки интерфейса.
Иногда основной вес создаёт не содержимое виджета, а подключённые библиотеки — например графические фреймворки. В этом случае применяется динамический импорт.
Пример:
async function loadChart(element) {
const { Chart } = await import('./chart-module.js');
const canvas = document.createElement('canvas');
element.appendChild(canvas);
new Chart(canvas, {
type: 'line',
data: {}
});
}
Загрузка библиотеки происходит только в момент вызова функции.
Преимущества:
Иногда содержимое загружается при взаимодействии пользователя.
Например, при клике:
document.addEventListener("click", e => {
const widget = e.target.closest(".grid-stack-item-content");
if (!widget) return;
loadWidgetContent(widget);
});
Такая схема применяется для:
Gridstack генерирует события при изменениях элементов. Эти события можно использовать для управления ленивой загрузкой.
Наиболее полезные:
addedchangedragstopresizestopПример обработки:
grid.on('added', function(event, items) {
items.forEach(item => {
const content = item.el.querySelector('.grid-stack-item-content');
observer.observe(content);
});
});
Теперь все новые элементы автоматически подключаются к механизму ленивой загрузки.
После загрузки содержимого часто требуется сохранить состояние. Это предотвращает повторные запросы данных.
Пример:
const widgetCache = new Map();
async function loadData(id) {
if (widgetCache.has(id)) {
return widgetCache.get(id);
}
const response = await fetch(`/api/widget/${id}`);
const data = await response.json();
widgetCache.set(id, data);
return data;
}
Использование кэша особенно важно для:
При большом количестве виджетов важно использовать один экземпляр IntersectionObserver для всей страницы.
Неправильный подход:
elements.forEach(el => {
new IntersectionObserver(...);
});
Правильный подход:
const observer = new IntersectionObserver(callback);
elements.forEach(el => observer.observe(el));
Это уменьшает нагрузку на браузер и экономит память.
Для контроля состояния обычно используются специальные атрибуты:
| Атрибут | Назначение |
|---|---|
data-widget |
тип виджета |
data-loaded |
признак загрузки |
data-loading |
процесс загрузки |
Пример:
async function loadWidget(element) {
if (element.dataset.loaded || element.dataset.loading) return;
element.dataset.loading = "true";
await loadContent(element);
element.dataset.loading = "false";
element.dataset.loaded = "true";
}
Это предотвращает ситуации, когда загрузка запускается несколько раз одновременно.
1. Скачки размеров элементов
Если содержимое изменяет высоту, сетка может перестраиваться. Решение — фиксировать размеры:
gs-h="4"
или заранее резервировать пространство.
2. Потеря контекста при перемещении
Если элемент перемещается до загрузки содержимого, могут возникать ошибки инициализации. Решение — проверять состояние в обработчиках событий Gridstack.
3. Повторная инициализация компонентов
Некоторые библиотеки не допускают повторной инициализации. Необходимо хранить флаг загрузки.
const grid = GridStack.init();
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (!entry.isIntersecting) return;
const el = entry.target;
loadWidgetContent(el);
observer.unobserve(el);
});
});
document.querySelectorAll('.grid-stack-item-content')
.forEach(el => observer.observe(el));
grid.on('added', (event, items) => {
items.forEach(item => {
const el = item.el.querySelector('.grid-stack-item-content');
observer.observe(el);
});
});
Схема работы:
Использование ленивой загрузки обязательно при:
Наиболее эффективная стратегия:
Такой подход позволяет строить масштабируемые панели мониторинга, содержащие десятки или сотни виджетов без значительного снижения производительности.