Vue-директивы позволяют декларативно подключать timeago.js к DOM-элементам прямо в шаблоне, без написания компонентов.
Директива — это объект с хуками жизненного цикла:
mounted, updated, unmounted.
Директива для timeago.js вызывает render при монтировании и
cancel при размонтировании.
// directives/timeago.js
import { render, cancel } from 'timeago.js';
export const vTimeago = {
mounted(el, binding) {
const locale = binding.value || binding.arg || 'ru';
render(el, locale);
},
updated(el, binding) {
const locale = binding.value || binding.arg || 'ru';
cancel(el);
render(el, locale);
},
unmounted(el) {
cancel(el);
},
};
Регистрация:
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import { vTimeago } from './directives/timeago';
const app = createApp(App);
app.directive('timeago', vTimeago);
app.mount('#app');
<template>
<!-- Локаль по умолчанию -->
<time v-timeago datetime="2025-05-26T10:00:00Z" />
<!-- Локаль через значение -->
<time v-timeago="'ru'" datetime="2025-05-26T10:00:00Z" />
<!-- Локаль через arg -->
<time v-timeago:de datetime="2025-05-26T10:00:00Z" />
</template>
export const vTimeago = {
mounted(el, binding) {
const locale = binding.value || 'ru';
// Модификатор .noUpdate — без автообновления
if (binding.modifiers.noUpdate) {
const { format } = require('timeago.js');
el.textContent = format(el.getAttribute('datetime'), locale);
return;
}
render(el, locale);
// Модификатор .title — добавить абсолютное время в title
if (binding.modifiers.title) {
const date = el.getAttribute('datetime');
el.title = new Intl.DateTimeFormat('ru', {
dateStyle: 'medium',
timeStyle: 'short',
}).format(new Date(date));
}
},
unmounted: (el) => cancel(el),
};
Использование:
<time v-timeago.noUpdate="'ru'" datetime="2025-05-26T10:00:00Z" />
<time v-timeago.title="'ru'" datetime="2025-05-26T10:00:00Z" />
export const vTimeago = {
mounted(el) {
const locale = el.dataset.locale || 'ru';
render(el, locale);
},
unmounted: (el) => cancel(el),
};
<time v-timeago data-locale="de" datetime="2025-05-26T10:00:00Z" />
// plugins/timeago.js
import { register, render, cancel } from 'timeago.js';
import ru from 'timeago.js/esm/lang/ru';
export const TimeagoPlugin = {
install(app, options = {}) {
const defaultLocale = options.locale || 'ru';
// Регистрировать локали из опций
if (options.locales) {
Object.entries(options.locales).forEach(([key, fn]) => register(key, fn));
} else {
register('ru', ru);
}
app.directive('timeago', {
mounted(el, binding) {
const locale = binding.value || defaultLocale;
render(el, locale);
},
updated(el, binding) {
const locale = binding.value || defaultLocale;
cancel(el);
render(el, locale);
},
unmounted: (el) => cancel(el),
});
},
};
Подключение:
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import { TimeagoPlugin } from './plugins/timeago';
import ru from 'timeago.js/esm/lang/ru';
import de from 'timeago.js/esm/lang/de';
const app = createApp(App);
app.use(TimeagoPlugin, {
locale: 'ru',
locales: { ru, de },
});
app.mount('#app');
import { watch } from 'vue';
import { render, cancel } from 'timeago.js';
export function createReactiveTimeago(localeRef) {
return {
mounted(el, binding) {
render(el, localeRef.value);
el._localeWatcher = watch(localeRef, (newLocale) => {
cancel(el);
render(el, newLocale);
});
},
unmounted(el) {
cancel(el);
el._localeWatcher?.();
},
};
}
<template>
<ul>
<li v-for="post in posts" :key="post.id">
<h3>{{ post.title }}</h3>
<time v-timeago="locale" :datetime="post.createdAt" />
</li>
</ul>
</template>
<script setup>
import { ref } from 'vue';
const locale = ref('ru');
const posts = [
{ id: 1, title: 'Пост 1', createdAt: '2025-05-26T10:00:00Z' },
{ id: 2, title: 'Пост 2', createdAt: '2025-05-25T08:00:00Z' },
];
</script>