Проект · Урок 19

Проект: блог

Второй проектный урок: лента постов с категориями и «избранным», которое переживает перезагрузку. Двигаемся от функции к функции — в конце получится работающий блог.

Шаг 1: массив постов

Держим всё в одном месте: карточку, категорию и дату. Рендер и фильтры читают только этот массив.

const posts = [
  { id: 1, title: "Первый пост", cat: "новости", date: "2026-01-14" },
  { id: 2, title: "Как учить JS", cat: "обучение", date: "2026-02-03" },
  { id: 3, title: "CSS в 2026", cat:  "css", date: "2026-03-01" }
];

Шаг 2: карточка и лента

Снова map, createElement, и всё подряд знакомым языком. Добавим звёздочку-избранное с data-id.

function postCard(p) {
  const a = document.createElement("article");
  a.className = "post";
  a.innerHTML = `<h3>${p.title}</h3>
    <span class="tag">${p.cat}</span>
    <button data-id="${p.id}" class="fav">☆</button>`;
  return a;
}

function renderFeed(list) {
  const feed = document.getElementById("feed");
  feed.innerHTML = "";
  list.forEach(p => feed.appendChild(postCard(p)));
}

Шаг 3: категории как фильтр

Тот же приём, что в магазине: клик по тегу — фильтр по массиву.

document.getElementById("tags").addEventListener("click", e => {
  const tag = e.target.closest("[data-tag]");
  if (!tag) return;
  const value = tag.dataset.tag;
  renderFeed(value === "все" ? posts : posts.filter(p => p.cat === value));
});
💡
closest() ищет ближайшего подходящего родителя — так клик по внутренности тега (тексту, иконке) всё равно отловится. Гораздо надёжнее, чем цепляться за конкретный элемент.

Шаг 4: избранное в localStorage

Идентично корзине: массив id → строкой в storage. На перерисовке сразу проставляем активные звёзды.

const saved = JSON.parse(localStorage.getItem("favorites")) || [];

function toggleFav(id) {
  const i = saved.indexOf(id);
  i === -1 ? saved.push(id) : saved.splice(i, 1);
  localStorage.setItem("favorites", JSON.stringify(saved));
  renderFeed(currentList);
}
⚠️
Не храните в localStorage секреты и не ставьте его в один ряд с базой данных — это максимум несколько мегабайт на устройстве пользователя, и он может его очистить.

Шаг 5: «показать ещё»

Не выводим все посты сразу — сперва часть, по клику добавляем ещё несколько.

let shown = 4;
function renderMore() {
  renderFeed(currentList.slice(0, shown));
  shown += 4;
  const more = document.getElementById("more");
  if (more) more.style.display = shown >= currentList.length ? "none" : "block";
}

Итоговый чек-лист блога

ℹ️
Готовый блог можно выложить на GitHub Pages уже сегодня — кстати, как раз урок о том, куда публиковать, у нас следующий и последний.