В современных приложениях DOM постоянно меняется: новые посты загружаются через infinite scroll, уведомления приходят через WebSocket, комментарии добавляются без перезагрузки. Для каждого нового элемента с датой нужно подключать автообновление.
render обрабатывает только элементы, существующие на
момент вызова. Если позже добавляется новый элемент с
datetime, он не попадает в систему обновлений
автоматически.
import { render } from 'timeago.js';
function addComment(comment) {
const li = document.createElement('li');
li.innerHTML = `
<p>${comment.text}</p>
<time class="comment-time" datetime="${comment.createdAt}"></time>
`;
document.getElementById('comments').appendChild(li);
// Явно подключить новый элемент
render(li.querySelector('.comment-time'), 'ru');
}
import { render } from 'timeago.js';
async function loadMorePosts() {
const response = await fetch('/api/posts?page=2');
const posts = await response.json();
const list = document.getElementById('feed');
posts.forEach(post => {
const li = document.createElement('li');
li.innerHTML = `
<h3>${post.title}</h3>
<time datetime="${post.createdAt}"></time>
`;
list.appendChild(li);
});
// Инициализировать все новые элементы
render(list.querySelectorAll('time'), 'ru');
}
import { render } from 'timeago.js';
const socket = new WebSocket('wss://example.com/feed');
socket.onmess age = (event) => {
const post = JSON.parse(event.data);
const el = document.createElement('article');
el.innerHTML = `
<h2>${post.title}</h2>
<time datetime="${post.createdAt}"></time>
`;
document.getElementById('stream').prepend(el);
render(el.querySelector('time'), 'ru');
};
Для полностью автоматической инициализации — без ручных вызовов:
import { render, cancel } from 'timeago.js';
function initAutoTimeago(locale = 'ru') {
// Инициализировать существующие элементы
render(document.querySelectorAll('time[datetime]'), locale);
const observer = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType !== Node.ELEMENT_NODE) return;
// Проверить сам элемент
if (node.matches('time[datetime]')) {
render(node, locale);
}
// Проверить дочерние элементы
const timeNodes = node.querySelectorAll('time[datetime]');
if (timeNodes.length) {
render(timeNodes, locale);
}
});
mutation.removedNodes.forEach(node => {
if (node.nodeType !== Node.ELEMENT_NODE) return;
if (node.matches('time[datetime]')) {
cancel(node);
}
node.querySelectorAll?.('time[datetime]').forEach(cancel);
});
});
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
return {
destroy() {
observer.disconnect();
cancel();
}
};
}
const watcher = initAutoTimeago('ru');
import { useEffect, useRef } from 'react';
import { render, cancel } from 'timeago.js';
function FeedItem({ post }) {
const timeRef = useRef(null);
useEffect(() => {
if (!timeRef.current) return;
render(timeRef.current, 'ru');
return () => cancel(timeRef.current);
}, [post.createdAt]);
return (
<article>
<h3>{post.title}</h3>
<time ref={timeRef} dateTime={post.createdAt} />
</article>
);
}
import { render } from 'timeago.js';
let page = 1;
async function loadNextPage() {
const data = await fetch(`/api/posts?page=${page++}`).then(r => r.json());
const fragment = document.createDocumentFragment();
data.posts.forEach(post => {
const li = document.createElement('li');
li.innerHTML = `<time datetime="${post.createdAt}"></time>`;
fragment.appendChild(li);
});
const container = document.getElementById('feed');
container.appendChild(fragment);
// Инициализировать только новые элементы
const newTimes = container.querySelectorAll('li:not([data-initialized]) time[datetime]');
render(newTimes, 'ru');
newTimes.forEach(t => t.closest('li')?.setAttribute('data-initialized', '1'));
}
import { render, cancel } from 'timeago.js';
class TimeagoManager {
constructor(locale = 'ru') {
this.locale = locale;
this.elements = new Set();
}
add(element) {
if (!this.elements.has(element)) {
this.elements.add(element);
render(element, this.locale);
}
}
remove(element) {
cancel(element);
this.elements.delete(element);
}
destroy() {
this.elements.forEach(el => cancel(el));
this.elements.clear();
}
}
const manager = new TimeagoManager('ru');
// При добавлении нового поста
manager.add(newTimeElement);
// При удалении поста
manager.remove(oldTimeElement);