Гид по технологиям

Создание простой книги рецептов на HTML, CSS и JavaScript

5 min read Веб-разработка Обновлено 30 Nov 2025
Книга рецептов на HTML/CSS/JS — пошагово
Книга рецептов на HTML/CSS/JS — пошагово

Книга рецептов на столе

Этот проект — отличный учебный кейс для закрепления базовых навыков фронтенд‑разработки: работа с DOM, формами, событиями, простая вёрстка и логика на клиенте. Проект запускается в браузере (Chrome, Firefox и др.) и не требует сервера. Ниже — компактный план и полный набор примеров кода, чек‑листы и советы по расширению.

Что делает этот проект

  • Левая колонка: форма добавления рецепта (название, ингредиенты, метод).
  • Правая колонка: список рецептов, поиск и возможность удаления.
  • Данные хранятся в массиве внутри JavaScript; в разделе улучшений показано сохранение в localStorage.

Важно: в базовой реализации данные не сохраняются между перезагрузками страницы.

Структура проекта (файлы)

  • index.html — разметка страницы
  • styles.css — стили
  • script.js — логика клиента

Ниже — пошаговое создание каждого файла.

index.html — базовая разметка

Создайте файл index.html и вставьте базовую структуру. Этот HTML включает навигацию, контейнер с двумя колонками и подключение CSS/JS.



  
    
    
    Recipe App
    
  
  
    
    

Добавить рецепт

Список рецептов

У вас нет рецептов.

Форма добавления рецептов в книге

styles.css — простая вёрстка

Создайте styles.css в той же папке и добавьте базовые стили. Это минимальная стилизация для удобного чтения и адаптивной сетки.

body {
  font-family: sans-serif;
  margin: 0;
  padding: 0;
}

nav {
  background-color: #333;
  position: fixed;
  top: 0;
  width: 100%;
  padding: 20px;
  left: 0;
  color: white;
  text-align: center;
}

.container {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  margin: 140px 5%;
  gap: 20px;
}

.left-column {
  width: 25%;
  min-width: 260px;
}

.right-column {
  width: 65%;
}

form {
  display: flex;
  flex-direction: column;
}

label {
  margin-top: 10px;
  margin-bottom: 6px;
}

input[type="text"], textarea {
  padding: 10px;
  margin-bottom: 10px;
  border-radius: 5px;
  border: 1px solid #ccc;
  width: 100%;
  box-sizing: border-box;
}

button[type="submit"] {
  padding: 10px;
  background-color: #3338;
  color: #fff;
  border: none;
  border-radius: 5px;
  cursor: pointer;
}

#recipe-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 20px;
}

#no-recipes {
  display: flex;
  background-color: #FFCCCC;
  padding: 20px;
  border-radius: 8px;
  margin-top: 20px;
}

.recipe {
  border: 1px solid #ccc;
  padding: 10px;
  border-radius: 5px;
  box-shadow: 0 2px 4px rgba(0,0,0,.08);
}

.recipe h3 { margin-top: 0; margin-bottom: 10px; }
.recipe ul { margin: 0; padding-left: 18px; }
.recipe ul li { margin-bottom: 5px; }

.delete-button {
  background-color: #dc3545;
  color: #fff;
  border: none;
  border-radius: 5px;
  padding: 6px 10px;
  cursor: pointer;
}
.delete-button:hover { background-color: #c82333; }

@media (max-width: 900px) {
  .container { flex-direction: column; margin: 100px 4%; }
  .left-column, .right-column { width: 100%; }
}

script.js — логика работы (добавление, отображение, удаление, поиск)

Пример базовой реализации поведения приложения. Создавайте файл script.js и вставляйте код ниже.

// Элементы DOM
const form = document.querySelector('#add-recipe-form');
const recipeList = document.querySelector('#recipe-list');
const noRecipes = document.getElementById('no-recipes');
const searchBox = document.getElementById('search-box');

// Хранилище рецептов
let recipes = []; // Можно заменить на загрузку из localStorage при улучшении

// Обработчик отправки формы
function handleSubmit(event) {
  event.preventDefault();

  const nameInput = document.querySelector('#recipe-name');
  const ingrInput = document.querySelector('#recipe-ingredients');
  const methodInput = document.querySelector('#recipe-method');

  const name = nameInput.value.trim();
  const ingredients = ingrInput.value.trim().split(',').map(i => i.trim()).filter(Boolean);
  const method = methodInput.value.trim();

  // Простая валидация
  if (!name) { alert('Введите название рецепта.'); return; }
  if (ingredients.length === 0) { alert('Добавьте минимум один ингредиент.'); return; }
  if (!method) { alert('Опишите метод приготовления.'); return; }

  // Проверяем, есть ли рецепт с тем же названием
  const exists = recipes.some(r => r.name.toLowerCase() === name.toLowerCase());
  if (exists) { if (!confirm('Рецепт с таким названием уже существует. Добавить ещё один?')) { return; } }

  const newRecipe = { name, ingredients, method };
  recipes.push(newRecipe);

  // Сброс формы
  nameInput.value = '';
  ingrInput.value = '';
  methodInput.value = '';

  // Обновление интерфейса
  displayRecipes();
}

// Отображение списка рецептов
function displayRecipes(list = recipes) {
  recipeList.innerHTML = '';
  list.forEach((recipe, index) => {
    const recipeDiv = document.createElement('div');
    recipeDiv.classList.add('recipe');

    recipeDiv.innerHTML = `
      

${escapeHtml(recipe.name)}

Ингредиенты:

    ${recipe.ingredients.map(ingr => `
  • ${escapeHtml(ingr)}
  • `).join('')}

Метод:

${escapeHtml(recipe.method)}

`; recipeList.appendChild(recipeDiv); }); noRecipes.style.display = recipes.length > 0 ? 'none' : 'flex'; } // Экранирование HTML для безопасного вывода function escapeHtml(text) { if (!text) return ''; return text.replace(/[&<>\"']/g, function (c) { return {'&':'&','<':'<','>':'>','"':'"','\'':'''}[c]; }); } // Удаление рецепта function handleDelete(event) { if (event.target.classList.contains('delete-button')) { const index = Number(event.target.dataset.index); if (!Number.isNaN(index)) { recipes.splice(index, 1); // Очистка строки поиска, чтобы показ был свежим searchBox.value = ''; displayRecipes(); } } } // Поиск по названию function search(query) { const q = query.trim().toLowerCase(); if (!q) { displayRecipes(); return; } const filteredRecipes = recipes.filter(recipe => recipe.name.toLowerCase().includes(q)); displayRecipes(filteredRecipes); } // События form.addEventListener('submit', handleSubmit); recipeList.addEventListener('click', handleDelete); searchBox.addEventListener('input', event => search(event.target.value)); // Инициализация (если нужно предварительно отрисовать) displayRecipes();

Книга рецептов: список пуст

Книга рецептов: добавленные рецепты

Кнопка удаления под рецептом

Улучшение: сохранение в localStorage (опционально)

Чтобы данные не терялись при перезагрузке, сохраняйте массив recipes в localStorage. Пример расширения script.js:

// Загрузка из localStorage при старте
const STORAGE_KEY = 'my_recipe_app_v1';
function saveRecipes() {
  try { localStorage.setItem(STORAGE_KEY, JSON.stringify(recipes)); } catch (e) { console.warn('Не удалось сохранить в localStorage', e); }
}
function loadRecipes() {
  try {
    const raw = localStorage.getItem(STORAGE_KEY);
    if (raw) {
      recipes = JSON.parse(raw);
    }
  } catch (e) { console.warn('Ошибка чтения localStorage', e); }
}

// Загрузить при инициализации
loadRecipes();
displayRecipes();

// Вызывать saveRecipes() после добавления и удаления
// Например: после recipes.push(newRecipe) — вызвать saveRecipes();
// и после recipes.splice(...) также вызвать saveRecipes();

Совет: используйте версии ключа в STORAGE_KEY при изменении формата данных.

Поиск: UX и нюансы

  • Поиск сейчас фильтрует строго по названию. Если нужно, можно расширить фильтрацию по ингредиентам и методу.
  • Для больших списков используйте debounce (например, 200–300 мс) при обработке input, чтобы снизить количество вызовов фильтрации.

Критерии приёмки

  • Форма добавляет рецепт в массив и очищается после отправки.
  • Добавленный рецепт отображается в правой колонке.
  • При отсутствии рецептов отображается сообщение об отсутствии.
  • Кнопка Delete удаляет соответствующий рецепт и интерфейс обновляется.
  • Поиск фильтрует список по введённой строке.
  • (Опционально) Данные сохраняются в localStorage и восстанавливаются после перезагрузки.

Тест-кейсы и приёмка

  • TC1: Добавление валидного рецепта — проверьте, что рецепт отображается в списке.
  • TC2: Попытка добавить пустое название — ожидается сообщение об ошибке/блокировка.
  • TC3: Удаление рецепта — рецепт исчез из списка.
  • TC4: Поиск по части названия — отображаются только совпадающие рецепты.
  • TC5: Перезагрузка страницы без localStorage — список очищен.
  • TC6: Сохранение в localStorage — после перезагрузки рецепты остаются.

Чек-листы для ролей

Разработчик:

  • Создать index.html, styles.css, script.js
  • Реализовать добавление/удаление/отображение/поиск
  • Сделать простую валидацию формы
  • Подумать о сохранении (localStorage)

Код‑ревьюер / тестировщик:

  • Проверить сценарии TC1–TC6
  • Проверить кросс‑браузерность (Chrome, Firefox)
  • Протестировать поведение при пустых/специальных символах

Когда это решение не подойдёт

  • Если нужно централизованное хранение и многопользовательский доступ — требуется сервер (API + база данных).
  • Для больших объёмов данных (>1000 записей) клиентская фильтрация и рендеринг потребуют оптимизации (виртуализация, серверная фильтрация).

Альтернативные подходы

  • Использовать фреймворки (React, Vue, Svelte) для управления состоянием и компонентным рендерингом.
  • Хранить данные на сервере через REST/GraphQL и реализовать авторизацию.
  • Использовать IndexedDB, если нужно хранить большие структуры на клиенте.

Мини‑методология: как расширять проект по шагам

  1. Сделать базовую форму и отображение (MVP).
  2. Добавить валидацию и UX‑подсказки.
  3. Реализовать удаление и подтверждения действий.
  4. Добавить поиск и фильтры (по ингредиентам, тегам).
  5. Внедрить сохранение (localStorage → IndexedDB → сервер).
  6. Переписать на компонентный фреймворк при необходимости масштабирования.

Decision flow (простое дерево принятия решений)

flowchart TD
  A[Нужен ли доступ с разных устройств?] -->|Да| B[Нужен сервер + API]
  A -->|Нет| C[Можно хранить в localStorage]
  B --> D[Разработать бэкенд и авторизацию]
  C --> E[Добавить export/import или синхронизацию]

Безопасность и надежность

  • Экранируйте ввод при выводе в HTML (в примере есть escapeHtml).
  • Не храните чувствительные данные в localStorage.
  • При добавлении серверного API — проверяйте и валидайте данные на сервере.

Шаблон проверки качества UI/UX (коротко)

  • Интуитивность: легко найти форму и кнопку отправки.
  • Доступность: метки у полей, понятные плейсхолдеры.
  • Ошибки: пользователь видит понятные сообщения при неверных данных.

Полезные подсказки и паттерны

  • Держите логику маленькими: отдельные функции для валидации, рендера, сохранения.
  • Используйте data-* атрибуты для привязки индексов/ID.
  • При изменении данных всегда обновляйте и сохранение (если оно есть) и UI.

Поиск по книге рецептов

Результат поиска рецептов

Резюме

Книга рецептов — компактный проект, который помогает отработать работу с DOM, формами и простой бизнес‑логикой в JavaScript. Начните с базовой реализации, затем улучшайте: добавьте хранение в localStorage, расширьте поиск и реализуйте экспорт/импорт.

Important: сохраняйте код в репозитории и делайте маленькие коммиты — так проще откатываться и тестировать изменения.

Короткие шаги для старта:

  1. Создать три файла: index.html, styles.css, script.js.
  2. Вставить базовую разметку, стили и скрипт из этой инструкции.
  3. Открыть index.html в браузере и проверить добавление/удаление/поиск.

Спасибо — теперь у вас есть рабочая отправная точка для дальнейших экспериментов и улучшений.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Восстановление кэша значков в Windows
Windows

Восстановление кэша значков в Windows

Стрелки не работают в Excel — быстрое решение
Excel

Стрелки не работают в Excel — быстрое решение

Шифрование USB‑накопителя с VeraCrypt
Безопасность

Шифрование USB‑накопителя с VeraCrypt

PowerShell: история команд — просмотр и сохранение
PowerShell

PowerShell: история команд — просмотр и сохранение

Nandroid — полная резервная копия Android
Android.

Nandroid — полная резервная копия Android

Ошибка 0x800f0806 в Windows 11 22H2
Windows 11

Ошибка 0x800f0806 в Windows 11 22H2