Введение
На сайтах с большим количеством статей, инструкций и гайдов пользователи часто возвращаются к уже прочитанному контенту. Через день, неделю или месяц. И в этот момент у человека возникает простой вопрос. Я это уже читал или нет.
uCoz из коробки такую вещь не показывает. Ни визуально, ни логически. Мы это исправим аккуратным клиентским решением без серверной логики, без базы и без сторонних библиотек.
В итоге получаем небольшую плашку в начале статьи, которая честно сообщает, что пользователь уже был на этой странице, и показывает дату последнего визита.
Что это за решение и зачем оно нужно
Это JavaScript скрипт, который запоминает факт посещения конкретной статьи в браузере пользователя через localStorage.
При повторном заходе на ту же страницу он:
- определяет, что это именно страница материала;
- проверяет, был ли пользователь здесь раньше;
- если был, показывает плашку с датой последнего визита.
Решение работает:
- на стандартных URL uCoz вида /news/...-123;
- на красивых URL без ID вида /lipkoe-oglavlenie-stati-ucoz;
- на кастомных шаблонах;
- без привязки к модулю;
- без нагрузки на сервер.
Почему это важно:
- повышает UX, особенно в блогах и базах знаний;
- помогает пользователю ориентироваться в контенте;
- не вмешивается в SEO и индексацию;
- не ломается при смене URL.
Как это работает технически
Логика простая и надежная.
- Скрипт определяет, является ли страница страницей статьи, а не списком или главной.
- Для идентификации страницы используется canonical URL. Это ключевой момент.
- Canonical приводится к стабильному виду без параметров и якорей.
- В localStorage сохраняется timestamp последнего визита.
- При следующем заходе дата извлекается и выводится пользователю.
Если canonical отсутствует, используется текущий путь страницы.
Применение на практике и чем это хорошо
Такую плашку особенно полезно использовать:
- в блогах с длинными техническими статьями;
- в базах знаний;
- в инструкциях и мануалах;
- на сайтах с обучающим контентом.
Плюсы решения:
- не требует правок базы данных;
- не зависит от модуля uCoz;
- одинаково работает на старых и новых URL;
- легко кастомизируется под любой дизайн;
- не влияет на скорость загрузки страницы.
По факту это маленькая деталь, которая делает сайт ощущаемо умнее.
Установка и подключение
Куда вставлять CSS
- Панель управления сайта
- Управление дизайном
- Таблица стилей CSS
Вставь код полностью.
.uc-read-flag {
position: relative;
margin: 14px 0;
padding: 12px 14px;
border-radius: 12px;
background: rgba(0, 0, 0, 0.06);
border: 1px solid rgba(0, 0, 0, 0.10);
line-height: 1.35;
font-size: 14px;
}
@media (prefers-color-scheme: dark) {
.uc-read-flag {
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.12);
}
}
.uc-read-flag__title {
font-weight: 700;
margin: 0 0 4px 0;
font-size: 15px;
}
.uc-read-flag__meta {
opacity: 0.85;
margin: 0;
}
.uc-read-flag__btn {
position: absolute;
top: 10px;
right: 10px;
border: 0;
border-radius: 10px;
padding: 6px 10px;
cursor: pointer;
background: rgba(0, 0, 0, 0.10);
font-size: 13px;
}
@media (prefers-color-scheme: dark) {
.uc-read-flag__btn {
background: rgba(255, 255, 255, 0.12);
}
}
Куда вставлять JavaScript
- Панель управления сайта
- Управление дизайном
- Модуль куда ставите
- Страница материала и комментариев
- Перед закрывающим </body>
<script>
(function () {
'use strict';
var KEEP_DAYS = 180;
var SAVE_AFTER_MS = 2500;
function nowMs() { return Date.now(); }
function pad2(n) { return (n < 10 ? '0' : '') + n; }
function formatDate(ts) {
var d = new Date(ts);
return pad2(d.getDate()) + '.' + pad2(d.getMonth() + 1) + '.' + d.getFullYear() +
' ' + pad2(d.getHours()) + ':' + pad2(d.getMinutes());
}
function safeJsonParse(s) {
try { return JSON.parse(s); } catch (e) { return null; }
}
function normUrl(u) {
try {
var x = new URL(u, location.href);
return x.origin + x.pathname.replace(/\/+$/, '');
} catch (e) {
return (u || '').split('#')[0].split('?')[0].replace(/\/+$/, '');
}
}
function getPageKey() {
var canonical = document.querySelector('link[rel="canonical"]');
var base = canonical && canonical.href ? canonical.href : location.href;
return normUrl(base);
}
function isEntryPage() {
if (document.querySelector('.eMessage')) return true;
if (document.querySelector('article, .entry, .post, .article-post')) return true;
var og = document.querySelector('meta[property="og:type"][content="article"]');
return !!og;
}
function findEntryNode() {
return document.querySelector('article.article-post') ||
document.querySelector('.article-post') ||
document.querySelector('.post') ||
document.querySelector('.entry') ||
document.querySelector('.eMessage') ||
document.querySelector('article');
}
if (!isEntryPage()) return;
var entryNode = findEntryNode();
if (!entryNode) return;
var STORAGE_KEY = 'uc_read_flag:' + getPageKey();
var prevRaw = localStorage.getItem(STORAGE_KEY);
var prev = prevRaw ? safeJsonParse(prevRaw) : null;
if (prev && prev.last && (nowMs() - prev.last) > KEEP_DAYS * 86400000) {
localStorage.removeItem(STORAGE_KEY);
prev = null;
}
if (prev && prev.last) {
var el = document.createElement('div');
el.className = 'uc-read-flag';
el.innerHTML =
'<div class="uc-read-flag__title">Вы уже читали эту страницу.</div>' +
'<p class="uc-read-flag__meta">Последний визит: <b>' + formatDate(prev.last) + '</b>.</p>' +
'<button class="uc-read-flag__btn" type="button">Скрыть</button>';
el.querySelector('.uc-read-flag__btn').onclick = function () {
el.remove();
};
entryNode.parentNode.insertBefore(el, entryNode);
}
setTimeout(function () {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify({ last: nowMs() }));
} catch (e) {}
}, SAVE_AFTER_MS);
})();
</script>
Какие классы используются и как адаптировать под свой шаблон
Основной контейнер плашки:
- .uc-read-flag - Это весь блок уведомления.
Внутренние элементы:
- .uc-read-flag__title - Заголовок плашки.
- .uc-read-flag__meta - Текст с датой последнего визита.
- .uc-read-flag__btn - Кнопка скрытия плашки.
Если у тебя другой контейнер статьи, просто добавь его в функцию findEntryNode().
Например, если статья лежит в .article-post-content, добавь первой строкой:
return document.querySelector('.article-post-content') || ...
Логика скрипта от этого не ломается.
Заключение
Это решение не про вау-эффект. Оно про удобство, честность и уважение к пользователю.
Плашка ненавязчивая, информативная и работает стабильно независимо от того, использует ли сайт стандартные URL uCoz или давно перешёл на красивые.
Такие мелочи делают сайт ощущаемо продуманным. А именно это отличает нормальный проект от просто набора страниц.
Оцените полезность материала!
Лицензия: CC BY-SA 4.0
Автор: Юрий Герук
Комментарии