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

Рецептная книга: проект на HTML, CSS и JavaScript

5 min read Веб-разработка Обновлено 09 Jan 2026
Рецептная книга на HTML/CSS/JS — шаги и код
Рецептная книга на HTML/CSS/JS — шаги и код

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

О чём эта статья

Задача — сделать фронтенд-приложение, в котором слева пользователь добавляет рецепт, а справа просматривает, удаляет и ищет сохранённые рецепты. Приложение работает в браузере без бэкенда и не сохраняет данные после перезагрузки (в разделе «Альтернативы» есть варианты хранения).

Важно

  • Приведённые примеры ориентированы на учебный проект. Они подходят для изучения DOM, событий и базовой валидации форм.
  • Код в блоках можно скопировать и запускать локально в любом современном браузере (Chrome, Firefox, Edge, Safari).

Подготовка файлов и базовая разметка

Создайте в одной папке три файла: index.html, styles.css и script.js. Ниже — минимальная HTML-структура с локализованными текстами интерфейса.



  
    
    Приложение рецептов
    
  
  
    
    

Разделите страницу на левую и правую колонку внутри контейнера:

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

В левой колонке разместите форму, где пользователь вводит название, список ингредиентов и способ приготовления. Тексты в примере переведены на русский.

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




Совет по UX: при вводе ингредиентов предложите пользователю разделять элементы запятой или переносом строки и укажите это в подсказке.

Базовые стили

Добавьте в styles.css простое оформление страницы и колонок:

body {
  font-family: sans-serif;
}

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: 150px 5%;
}

.left-column {
  width: 25%;
}

.right-column {
  width: 65%;
}

И стили для самой формы:

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

label {
  margin-bottom: 10px;
}

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;
}

Логика на JavaScript: добавление рецептов

В script.js начните с поиска формы и создания массива для рецептов:

const form = document.querySelector('form');
let recipes = [];

Функция обработки отправки формы получает значения, валидирует их и добавляет в массив. В примере ингредиенты разбиваются по запятой и очищаются от пробелов.

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 && ingredients.length > 0 && method) {
    const newRecipe = { name, ingredients, method };
    recipes.push(newRecipe);

    // Очистить форму
    nameInput.value = '';
    ingrInput.value = '';
    methodInput.value = '';

    // Обновить отображение
    displayRecipes();
  } else {
    // Можно показывать пользовательское уведомление об ошибке
    alert('Пожалуйста, заполните все поля и укажите хотя бы один ингредиент.');
  }
}

form.addEventListener('submit', handleSubmit);

Пояснение терминов

  • DOM: модель документа, с которой работает JavaScript для поиска и изменения элементов страницы.

Отображение списка рецептов

В правой колонке добавьте контейнер для списка рецептов и сообщение, если список пуст:

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

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

CSS для списка и сообщения об отсутствии записей:

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

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

Функция отображения итеративно создаёт элементы для каждого рецепта:

const recipeList = document.querySelector('#recipe-list');
const noRecipes = document.getElementById('no-recipes');

function displayRecipes() {
  recipeList.innerHTML = '';
  recipes.forEach((recipe, index) => {
    const recipeDiv = document.createElement('div');
    recipeDiv.classList.add('recipe');
    recipeDiv.innerHTML = `
      

${recipe.name}

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

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

Способ:

${recipe.method}

`; recipeList.appendChild(recipeDiv); }); noRecipes.style.display = recipes.length > 0 ? 'none' : 'flex'; }

Стили для карточки рецепта:

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

.recipe h3 {
  margin-top: 0;
  margin-bottom: 10px;
}

.recipe ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

.recipe ul li {
  margin-bottom: 5px;
}

Удаление рецептов

Добавьте стили для кнопки удаления:

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

.delete-button:hover {
  background-color: #c82333;
}

Логика удаления опирается на делегирование событий: один обработчик на контейнере списка удаляет элемент по его индексу.

function handleDelete(event) {
  if (event.target.classList.contains('delete-button')) {
    const index = parseInt(event.target.dataset.index, 10);
    if (!Number.isNaN(index)) {
      recipes.splice(index, 1);
      // Очистим поле поиска, чтобы вернуть полный список
      const searchBox = document.getElementById('search-box');
      if (searchBox) searchBox.value = '';
      displayRecipes();
    }
  }
}

recipeList.addEventListener('click', handleDelete);

Важно: при удалении по индексу, если вы используете фильтр (search), индекс может не совпадать с исходным положением в массиве. В следующем разделе показано, как корректно обрабатывать удаление в режиме поиска.

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

В элементе поиска мы фильтруем массив recipes по названию и рисуем отфильтрованные карточки. Элемент поиска можно добавить в начало правой колонки (см. выше).

const searchBox = document.getElementById('search-box');

function search(query) {
  const filteredRecipes = recipes.filter(recipe => {
    return recipe.name.toLowerCase().includes(query.toLowerCase());
  });

  recipeList.innerHTML = '';
  filteredRecipes.forEach(recipe => {
    const recipeEl = document.createElement('div');
    recipeEl.classList.add('recipe');
    recipeEl.innerHTML = `
      

${recipe.name}

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

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

Способ:

${recipe.method}

`; recipeList.appendChild(recipeEl); }); noRecipes.style.display = (filteredRecipes.length > 0) ? 'none' : 'flex'; } searchBox.addEventListener('input', event => search(event.target.value));

Замечание: в этом простом примере при отображении отфильтрованных результатов мы используем recipes.indexOf(recipe) для установки data-index кнопки удаления. Это работает при уникальных объектах, но если в массиве одинаковые объекты или идентичные строки — лучше хранить уникальный id для каждого рецепта.

Улучшения и альтернативы

Когда этот подход не работает

  • Если нужно сохранять данные между перезагрузками — текущий массив в памяти не подойдёт.
  • Если много данных или нужно совместное использование — потребуется бэкенд и база данных.

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

  • localStorage: простой способ сохранить рецепты в браузере между сессиями. Подходит для одиночного пользователя.
  • IndexedDB: для более сложных локальных хранилищ и больших наборов данных.
  • Бэкенд (REST/GraphQL): для мультипользовательских приложений с авторизацией и хранением на сервере.

Мини-методология разработки

  1. Прототип на бумаге: определите поля рецепта и базовую навигацию.
  2. Верстка: статический HTML/CSS без логики.
  3. JavaScript: добавить обработку форм и отображение.
  4. Тесты UX: проверка добавления, поиска, удаления.
  5. Улучшение: localStorage, валидация, уникальные id.

Чек-листы по ролям

  • Разработчик интерфейса:

    • Верстка адаптивна
    • Поля формы валидируются
    • Кнопки имеют состояния hover/focus
  • Разработчик логики:

    • Корректно добавляются объекты в массив
    • Удаление не ломает индексы
    • Поиск нечувствителен к регистру
  • Тестировщик:

    • Добавить рецепт с пустыми полями — ожидание ошибки
    • Удалить рецепт при активном поиске — список обновляется

Шаблоны и сниппеты

  • Генерация уникального id (простая функция):
function uid() {
  return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
}

Использование id при добавлении рецепта:

const newRecipe = { id: uid(), name, ingredients, method };
  • Сохранение и загрузка из localStorage:
function saveRecipes() {
  localStorage.setItem('recipes', JSON.stringify(recipes));
}

function loadRecipes() {
  const raw = localStorage.getItem('recipes');
  if (raw) recipes = JSON.parse(raw);
}

// Вызывать loadRecipes() при инициализации и saveRecipes() после изменений

Совместимость и миграция

  • Код ориентирован на современные браузеры с поддержкой ES6. Для поддержки старых браузеров потребуется транспиляция (Babel) и полифилы.
  • localStorage доступен в большинстве десктопных и мобильных браузеров, но не в режимах приватного просмотра некоторых браузеров.

Локальные особенности для русскоязычных пользователей

  • Формат ингредиентов и чисел: используйте запятые и точки по принятому в вашей целевой аудитории стилю.
  • Подсказки и placeholder-ы делайте на русском, чтобы пользователи понимали способ ввода.

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

  • Пользователь может добавить рецепт с названием, списком ингредиентов и способом.
  • После добавления рецепт отображается в правой колонке.
  • Пользователь может удалить рецепт, и список обновляется.
  • Поиск фильтрует список по названию (без учёта регистра).
  • Интерфейс корректно отображается на экранах от 320px и выше (адаптивность).

Безопасность и приватность

  • Приложение не отправляет данные на сервер по умолчанию. Если вы добавляете синхронизацию с сервером, обязательно используйте HTTPS.
  • При использовании localStorage помните, что данные хранятся на устройстве и доступны другим скриптам в том же домене.

Однострочный глоссарий

  • localStorage: браузерное key/value-хранилище для сохранения данных между сессиями.
  • DOM: объектная модель документа, доступная для изменения через JavaScript.

Примеры ошибок и как их исправлять

  • Проблема: при фильтрации удаление удаляет не тот элемент. Решение: присваивайте каждому рецепту уникальный id и используйте его для удаления.

  • Проблема: после перезагрузки данные исчезают. Решение: сохраняйте recipes в localStorage и загружайте при старте.

Краткое резюме

  • Проект «Рецептная книга» — отличный способ попрактиковаться в HTML, CSS и JavaScript.
  • Базовый функционал: добавление, отображение, удаление и поиск рецептов.
  • Рекомендации по развитию: добавить уникальные id, сохранение в localStorage или бэкенд, а также улучшить валидацию и UX.

Дополнительные изображения

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

Пустой список рецептов

Список рецептов с карточками

Кнопка удалить на карточке

Поиск рецептов и фильтры

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

Итог

Этот проект даёт практику работы с формами, событиями и динамическим отображением данных в DOM. После реализации базовой версии вы можете расширять функционал: добавлять редактирование рецептов, теги, сортировку и облачную синхронизацию.

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

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

RDP: полный гид по настройке и безопасности
Инфраструктура

RDP: полный гид по настройке и безопасности

Android как клавиатура и трекпад для Windows
Гайды

Android как клавиатура и трекпад для Windows

Советы и приёмы для работы с PDF
Документы

Советы и приёмы для работы с PDF

Calibration в Lightroom Classic: как и когда использовать
Фото

Calibration в Lightroom Classic: как и когда использовать

Отключить Siri Suggestions на iPhone
iOS

Отключить Siri Suggestions на iPhone

Рисование таблиц в Microsoft Word — руководство
Office

Рисование таблиц в Microsoft Word — руководство