Создание собственных рендеров

Стандартная функция render в timeago.js работает с DOM-элементами через атрибут datetime, обновляя textContent. Для нестандартных сценариев — другие атрибуты, вывод в лог, работа в Node.js — создаются собственные рендеры.


Когда нужен собственный рендер

  • Нужно обновлять атрибут title или aria-label вместо textContent.
  • Используется нестандартный атрибут вместо datetime.
  • Нужна кастомная логика обновления (анимация, трекинг).
  • Приложение работает в среде без DOM (Node.js, Deno).
  • Нужна интеграция с виртуальным DOM.

Ручное построение рендера через format + setInterval

Простейший самодельный рендер:

import { format } from 'timeago.js';

function createRenderer(element, dateStr, locale = 'ru') {
  function update() {
    element.textContent = format(dateStr, locale);
  }

  update(); // первое обновление

  const timer = setInterval(update, 60000); // каждую минуту

  return {
    stop() {
      clearInterval(timer);
    }
  };
}

const el     = document.getElementById('time');
const renderer = createRenderer(el, '2025-05-26T08:00:00Z', 'ru');

// Остановить при уничтожении компонента
renderer.stop();

Адаптивный интервал обновления

Умный рендер меняет частоту обновлений в зависимости от давности даты:

import { format } from 'timeago.js';

function adaptiveRenderer(element, dateStr, locale = 'ru') {
  let timer = null;

  function getInterval(diffMs) {
    if (diffMs < 60000)     return 10000;   // < 1 мин: каждые 10с
    if (diffMs < 3600000)   return 60000;   // < 1 ч: каждую минуту
    if (diffMs < 86400000)  return 3600000; // < 1 д: каждый час
    return 86400000;                         // иначе: раз в день
  }

  function update() {
    const diff = Math.abs(Date.now() - new Date(dateStr).getTime());
    element.textContent = format(dateStr, locale);

    clearTimeout(timer);
    timer = setTimeout(update, getInterval(diff));
  }

  update();

  return { stop: () => clearTimeout(timer) };
}

Рендер в атрибут title

import { format } from 'timeago.js';

function renderTitle(elements, locale = 'ru') {
  const timers = [];

  elements.forEach(el => {
    const date = el.getAttribute('datetime');

    function update() {
      el.setAttribute('title', format(date, locale));
    }

    update();
    const id = setInterval(update, 60000);
    timers.push(id);
  });

  return {
    cancel() {
      timers.forEach(clearInterval);
    }
  };
}

Рендер в aria-label для доступности

import { format } from 'timeago.js';

function renderAccessible(elements, locale = 'ru') {
  elements.forEach(el => {
    const date = el.getAttribute('datetime');

    function update() {
      const relative = format(date, locale);
      el.setAttribute('aria-label', relative);
      el.textContent = relative;
    }

    update();
    setInterval(update, 60000);
  });
}

Рендер для кастомного атрибута data-date

import { format } from 'timeago.js';

function renderDataDate(locale = 'ru') {
  const elements = document.querySelectorAll('[data-date]');

  elements.forEach(el => {
    const date = el.dataset.date;

    function update() {
      el.textContent = format(date, locale);
    }

    update();
    setInterval(update, 30000);
  });
}

Рендер в Node.js (без DOM)

Для серверного рендеринга или генерации шаблонов:

import { format } from 'timeago.js';

function renderToString(dates, locale = 'ru') {
  return dates.map(date => ({
    date,
    label: format(date, locale),
  }));
}

const items = renderToString([
  '2025-05-26T08:00:00Z',
  '2025-05-25T14:00:00Z',
], 'ru');

Рендер с анимацией при обновлении

import { format } from 'timeago.js';

function animatedRenderer(element, dateStr, locale = 'ru') {
  function update() {
    const newText = format(dateStr, locale);

    if (element.textContent !== newText) {
      element.classList.add('time-updating');
      element.textContent = newText;

      setTimeout(() => {
        element.classList.remove('time-updating');
      }, 300);
    }
  }

  update();
  return setInterval(update, 60000);
}

CSS:

.time-updating {
  opacity: 0.5;
  transition: opacity 0.3s;
}

Рендер с observer (MutationObserver)

Обновлять время при изменении атрибута datetime:

import { format } from 'timeago.js';

function watchAndRender(element, locale = 'ru') {
  function update() {
    const date = element.getAttribute('datetime');
    if (date) element.textContent = format(date, locale);
  }

  update();

  const observer = new MutationObserver(mutations => {
    if (mutations.some(m => m.attributeName === 'datetime')) {
      update();
    }
  });

  observer.observe(element, { attributes: true });

  return {
    stop() {
      observer.disconnect();
    }
  };
}

Рендер с requestAnimationFrame

Для плавного обновления без мерцания:

import { format } from 'timeago.js';

function rafRenderer(elements, locale = 'ru') {
  let running = true;
  let lastUpdate = 0;

  function loop(timestamp) {
    if (!running) return;

    if (timestamp - lastUpdate > 10000) { // не чаще раза в 10 секунд
      elements.forEach(el => {
        el.textContent = format(el.getAttribute('datetime'), locale);
      });
      lastUpdate = timestamp;
    }

    requestAnimationFrame(loop);
  }

  requestAnimationFrame(loop);

  return { stop: () => { running = false; } };
}