perf(messenger): оптимизация загрузки initial messages
Проблема: При открытии чата сначала загружались сообщения, затем происходила видимая прокрутка вниз - плохой UX Решение: 1. Скрываем контейнер (visibility: hidden) перед рендерингом 2. Используем DocumentFragment в renderMessages для батчевой вставки 3. Устанавливаем scrollTop ДО показа контейнера 4. Показываем контейнер - пользователь сразу видит конец диалога Преимущества: - Нет видимой прокрутки - сразу показывается конец чата - Меньше reflow (1 вместо N при forEach) - Быстрее рендеринг больших чатов (все элементы вставляются за раз) - Лучший UX - нет "прыжков" при загрузке Изменения: - userWebSocket.js: добавлено скрытие/показ контейнера, прямая установка scrollTop - messageRenderer.js: renderMessages использует DocumentFragment вместо множественных insertAdjacentHTML
This commit is contained in:
parent
1be2f3cdd4
commit
17f818656d
@ -22,19 +22,48 @@ export class MessageRenderer {
|
||||
return;
|
||||
}
|
||||
|
||||
// ОПТИМИЗАЦИЯ: Используем DocumentFragment для батчевой вставки
|
||||
const fragment = document.createDocumentFragment();
|
||||
let lastDay = null;
|
||||
|
||||
messages.forEach(msg => {
|
||||
// Проверяем на дубликаты
|
||||
const existingMessage = container.querySelector(`[data-message-id="${msg.id}"]`);
|
||||
if (existingMessage) {
|
||||
console.log('[MessageRenderer] Message already exists, skipping:', msg.id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Добавляем разделитель дня если нужно
|
||||
const msgDate = new Date(msg.created_ts);
|
||||
const msgDay = this.formatDay(msgDate);
|
||||
|
||||
if (msgDay !== lastDay) {
|
||||
this.addDayDivider(msgDay, container);
|
||||
const dividerEl = document.createElement('div');
|
||||
dividerEl.className = 'day-divider text-center small text-muted my-3';
|
||||
dividerEl.innerHTML = `<span class="px-3 py-1 rounded-pill bg-light">${msgDay}</span>`;
|
||||
fragment.appendChild(dividerEl);
|
||||
lastDay = msgDay;
|
||||
}
|
||||
|
||||
this.renderMessage(msg, container);
|
||||
// Создаем элемент сообщения и добавляем во fragment
|
||||
const isOwn = msg.author_id === this.currentUserId;
|
||||
const messageEl = document.createElement('div');
|
||||
messageEl.className = `d-flex mb-3 msg ${isOwn ? 'justify-content-end' : 'justify-content-start'}`;
|
||||
messageEl.setAttribute('data-id', String(msg.id));
|
||||
messageEl.setAttribute('data-message-id', String(msg.id));
|
||||
messageEl.setAttribute('data-ts', String(msg.created_ts));
|
||||
messageEl.setAttribute('data-author-id', String(msg.author_id || ''));
|
||||
messageEl.setAttribute('data-is-edited', String(msg.is_edited || false));
|
||||
messageEl.setAttribute('data-edited-at', String(msg.edited_at || ''));
|
||||
messageEl.setAttribute('data-reactions', JSON.stringify(msg.reactions_summary || {}));
|
||||
messageEl.innerHTML = this.buildMessageInnerHtml(msg, isOwn);
|
||||
|
||||
fragment.appendChild(messageEl);
|
||||
});
|
||||
|
||||
// Одна вставка всех элементов - минимизируем reflow
|
||||
container.appendChild(fragment);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -273,12 +273,19 @@ export function initUserWebSocket(options = {}) {
|
||||
}
|
||||
|
||||
console.log('[UserWS] Rendering %d initial messages via MessageRenderer', messages.length);
|
||||
messages.forEach(msg => {
|
||||
options.messageRenderer.renderMessage(msg);
|
||||
});
|
||||
|
||||
// Скроллим вниз
|
||||
scrollToBottom(true);
|
||||
|
||||
// ОПТИМИЗАЦИЯ: Скрываем контейнер перед рендерингом для предотвращения reflow
|
||||
const wasVisible = state.scrollEl.style.visibility;
|
||||
state.scrollEl.style.visibility = 'hidden';
|
||||
|
||||
// Используем батчевый renderMessages вместо forEach для лучшей производительности
|
||||
options.messageRenderer.renderMessages(messages);
|
||||
|
||||
// Устанавливаем scroll в конец ДО показа контейнера
|
||||
state.scrollEl.scrollTop = state.scrollEl.scrollHeight;
|
||||
|
||||
// Показываем контейнер - пользователь сразу видит конец диалога
|
||||
state.scrollEl.style.visibility = wasVisible || '';
|
||||
|
||||
// Инициализируем голосования после загрузки сообщений
|
||||
if (window.chatPoll) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user