При большом количестве элементов с датами неоптимальное обновление
может создавать заметные задержки интерфейса. Каждое обновление
textContent вызывает перерисовку браузера, а многочисленные
таймеры создают нагрузку на поток.
format в setInterval с малым интервалом.Не записывать, если значение не изменилось:
import { format } from 'timeago.js';
function updateIfChanged(element, date, locale = 'ru') {
const newText = format(date, locale);
if (element.textContent !== newText) {
element.textContent = newText;
}
}
Это предотвращает ложные перерисовки.
Объединять обновления в один кадр:
import { format } from 'timeago.js';
function batchUpdate(elements, locale = 'ru') {
requestAnimationFrame(() => {
elements.forEach(el => {
const date = el.getAttribute('datetime');
const newText = format(date, locale);
if (el.textContent !== newText) {
el.textContent = newText;
}
});
});
}
При вставке новых элементов с датами:
import { format } from 'timeago.js';
function addPosts(posts, container) {
const fragment = document.createDocumentFragment();
posts.forEach(post => {
const li = document.createElement('li');
li.innerHTML = `
<h3>${post.title}</h3>
<time datetime="${post.createdAt}">
${format(post.createdAt, 'ru')}
</time>
`;
fragment.appendChild(li);
});
container.appendChild(fragment); // одна вставка вместо N
}
Обновлять только видимые элементы:
import { render, cancel } from 'timeago.js';
function lazyTimeago(nodes, locale = 'ru') {
const io = new IntersectionObserver((entries) => {
entries.forEach(({ target, isIntersecting }) => {
if (isIntersecting) {
render(target, locale);
} else {
cancel(target);
}
});
});
nodes.forEach(node => io.observe(node));
return () => {
io.disconnect();
cancel(nodes);
};
}
Элементы за пределами viewport не создают таймеров.
Если render не устраивает, собственный адаптивный
таймер:
import { format } from 'timeago.js';
function adaptiveUpdate(elements, locale = 'ru') {
function getInterval(diffMs) {
if (diffMs < 60000) return 10000;
if (diffMs < 3600000) return 60000;
if (diffMs < 86400000) return 1800000;
return 86400000;
}
let timer;
function tick() {
elements.forEach(el => {
const date = el.getAttribute('datetime');
if (!date) return;
const newText = format(date, locale);
if (el.textContent !== newText) el.textContent = newText;
});
// Рассчитать следующий интервал по наиболее свежей дате
const minDiff = Math.min(...Array.from(elements).map(el =>
Math.abs(Date.now() - new Date(el.getAttribute('datetime')).getTime())
));
clearTimeout(timer);
timer = setTimeout(tick, getInterval(minDiff));
}
tick();
return () => clearTimeout(timer);
}
Для элемента “только что” можно использовать CSS-анимацию пульсации вместо постоянного обновления текста:
.time-fresh {
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }
}
import { format } from 'timeago.js';
function renderWithClass(el, date, locale = 'ru') {
const diff = Date.now() - new Date(date).getTime();
el.textContent = format(date, locale);
el.classList.toggle('time-fresh', diff < 30000);
}
Не инициализировать render для всех элементов сразу:
import { render } from 'timeago.js';
// Инициализировать только первые 10 элементов
const allNodes = Array.from(document.querySelectorAll('.time'));
const firstPage = allNodes.slice(0, 10);
render(firstPage, 'ru');
// Остальные — при прокрутке
window.addEventListener('scroll', () => {
const visible = allNodes.filter(el => isVisible(el));
render(visible, 'ru');
}, { passive: true });
function isVisible(el) {
const rect = el.getBoundingClientRect();
return rect.top < window.innerHeight && rect.bottom > 0;
}
Для анализа производительности:
Если Layout занимает существенную долю — оптимизировать через батчинг или IntersectionObserver.