Понятное пошаговое руководство по созданию простого приложения to‑do: спланируйте функции, создайте экраны, добавьте логику, сохраните данные, протестируйте и опубликуйте.

Когда в этом руководстве я говорю «приложение», я имею в виду маленькое веб‑приложение: одну страницу, которую вы открываете в браузере, и которая реагирует на клики и ввод. Никаких установок, аккаунтов или тяжёлой настройки — просто простой проект, который можно запускать локально.
К концу у вас будет приложение‑to‑do, которое умеет:
localStorage (чтобы при закрытии вкладки всё не пропадало)Оно не будет идеальным или «корпоративным», и это нормально. Это проект для новичков, который учит базовым вещам без множества инструментов.
Вы будете строить приложение шаг за шагом и разберётесь с основами фронтенда:
Держите всё просто. Понадобится только:
Если вы умеете создавать папку и редактировать файлы — вы готовы.
Прежде чем писать код, решите, что для вас будет означать «успех». В этом руководстве мы создаём одно маленькое приложение с одной ясной задачей: помочь отслеживать дела.
Напишите одно предложение, которое будете видеть перед собой при разработке:
«Это приложение позволяет добавлять задачи в список, чтобы я их не забыл.»
И всё. Если захотите добавить календари, напоминания, теги или аккаунты — отложите это на потом.
Составьте два коротких списка:
Обязательное (для этого проекта):
Желательно (не нужно сегодня): сроки, приоритеты, категории, поиск, drag-and-drop, синхронизация с облаком.
Ограничивая «обязательное», вы действительно закончите проект.
Это приложение может быть на одной странице с:
Будьте конкретны, чтобы не застрять:
Когда это решено — можно создать файлы проекта.
Прежде чем писать код, создайте чистое «домашнее» пространство для приложения. Организация файлов с самого начала упростит дальнейшую работу.
Создайте папку с именем, например, todo-app. В ней будут все файлы проекта.
Внутри создайте три файла:
index.html (структура страницы)styles.css (внешний вид)app.js (поведение и интерактивность)Если система скрывает расширения, убедитесь, что файлы действительно имеют правильные расширения (частая ошибка — index.html.txt).
Откройте папку todo-app в редакторе (VS Code, Sublime и т. п.). Затем откройте index.html в браузере.
Возможно, страница пока пустая — это нормально. Добавим содержимое дальше.
По умолчанию браузер не обновляет страницу автоматически (если вы не используете специальные инструменты).
Базовый цикл работы:
Если что‑то «не работает», первое, что стоит попробовать — обновить страницу.
Можно открыть index.html двойным кликом, но локальный сервер иногда предотвращает странные проблемы (особенно при сохранении/загрузке файлов).
Простые опции:
python -m http.server
Затем откройте адрес, который напечатает сервер (часто http://localhost:8000).
Теперь создадим каркас приложения. Этот HTML пока не сделает всё интерактивным — это будет в JavaScript — но он даёт классные точки, куда обращаться из кода.
Нужно:
Понятные id/классы упрощают жизнь: JavaScript сможет без путаницы найти нужные элементы.
index.html<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>To‑Do App</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<main class="app" id="app">
<h1 class="app__title" id="appTitle">My To‑Do List</h1>
<form class="task-form" id="taskForm">
<label class="task-form__label" for="taskInput">New task</label>
<div class="task-form__row">
<input
id="taskInput"
class="task-form__input"
type="text"
placeholder="e.g., Buy milk"
autocomplete="off"
/>
<button id="addButton" class="task-form__button" type="submit">
Add
</button>
</div>
</form>
<ul class="task-list" id="taskList" aria-label="Task list"></ul>
</main>
<script src="app.js"></script>
</body>
</html>
Это всё по структуре. Обратите внимание: мы использовали id="taskInput" и id="taskList" — именно с ними будет работать JavaScript.
Сейчас страница существует, но выглядит как документ. Немного CSS сделает её удобнее: отступы, читаемый текст и кнопки, по которым приятно кликать.
Центрированный блок удерживает внимание и не позволяет содержимому растягиваться на всю ширину экрана.
/* Basic page setup */
body {
font-family: Arial, sans-serif;
background: #f6f7fb;
margin: 0;
padding: 24px;
}
/* Centered app container */
.container {
max-width: 520px;
margin: 0 auto;
background: #ffffff;
padding: 16px;
border-radius: 10px;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
}
Каждая задача должна выглядеть как отдельная «строка» с комфортными отступами.
ul { list-style: none; padding: 0; margin: 16px 0 0; }
li {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 10px 12px;
border: 1px solid #e7e7ee;
border-radius: 8px;
margin-bottom: 10px;
}
.task-text { flex: 1; }
Когда задача выполнена, её внешний вид должен меняться, чтобы это было видно сразу.
.done .task-text {
text-decoration: line-through;
color: #777;
opacity: 0.85;
}
Пусть кнопки выглядят однообразно и «как часть приложения».
button {
border: none;
border-radius: 8px;
padding: 8px 10px;
cursor: pointer;
}
button:hover { filter: brightness(0.95); }
Этого достаточно для приятного и простого интерфейса. Далее подключаем поведение через JavaScript.
Теперь, когда на странице есть поле ввода, кнопка и список, заставим их что‑то делать. Цель простая: при вводе текста и нажатии Add (или Enter) появится новая задача в списке.
В app.js сначала получаем нужные элементы, затем слушаем два события: клик по кнопке и нажатие Enter в поле ввода.
const taskInput = document.querySelector('#taskInput');
const addBtn = document.querySelector('#addBtn');
const taskList = document.querySelector('#taskList');
function addTask() {
const text = taskInput.value.trim();
// Block empty tasks (including ones that are just spaces)
if (text === '') return;
const li = document.createElement('li');
li.textContent = text;
taskList.appendChild(li);
// Clear the input and put the cursor back
taskInput.value = '';
taskInput.focus();
}
addBtn.addEventListener('click', addTask);
taskInput.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
addTask();
}
});
trim() чтобы убрать лишние пробелы в начале/конце.li, ставит в него текст и добавляет в список.Если ничего не происходит, проверьте, совпадают ли id в HTML с селекторами в JavaScript — это частая ошибка у новичков.
Теперь, когда задачи можно добавлять, сделаем их управляемыми: можно пометить как выполненные и удалить.
Вместо простых строк храните задачи в виде объектов. Это даёт каждой задаче идентичность и место для статуса «выполнено»:
text: текст задачиdone: true или falseid: уникальный номер, чтобы находить и удалять нужную задачуПример:
let tasks = [
{ id: 1, text: "Buy milk", done: false },
{ id: 2, text: "Email Sam", done: true }
];
При рендере каждой задачи на странице добавляйте либо чекбокс, либо кнопку «Выполнено», плюс кнопку «Удалить».
«Слушатели событий» — это способ реагировать на клики. Их можно повесить на каждую кнопку, но удобный и простой способ — делегировать событие: повесить один обработчик на контейнер списка и проверять, по чему кликнули.
Пример:
function toggleDone(id) {
tasks = tasks.map(t => t.id === id ? { ...t, done: !t.done } : t);
renderTasks();
}
function deleteTask(id) {
tasks = tasks.filter(t => t.id !== id);
renderTasks();
}
document.querySelector("#taskList").addEventListener("click", (e) => {
const id = Number(e.target.dataset.id);
if (e.target.matches(".toggle")) toggleDone(id);
if (e.target.matches(".delete")) deleteTask(id);
});
В renderTasks():
data-id="${task.id}" к нужным кнопкам..done).Пока что список потеряется при обновлении страницы — потому что задачи хранятся только в памяти JavaScript, которая очищается при перезагрузке.
localStorage встроен в браузер. Это место, где можно хранить текст под ключом. Подойдёт новичкам: не нужен сервер или аккаунты.
Мы будем сохранять весь список задач как JSON‑строку и загружать её при открытии страницы.
После каждого добавления, пометки или удаления вызывайте saveTasks().
const STORAGE_KEY = "todo.tasks";
function saveTasks(tasks) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(tasks));
}
Там, где вы обновляете массив tasks, вызывайте:
saveTasks(tasks);
renderTasks(tasks);
При загрузке страницы прочитайте сохранённое значение. Если его нет — используйте пустой список.
function loadTasks() {
const saved = localStorage.getItem(STORAGE_KEY);
return saved ? JSON.parse(saved) : [];
}
let tasks = loadTasks();
renderTasks(tasks);
Готово: теперь приложение запоминает задачи после обновления страницы.
Совет: localStorage хранит только строки, поэтому JSON.stringify() превращает массив в текст, а JSON.parse() возвращает массив обратно.
Тестирование — это самый быстрый способ превратить «работает у меня» в «работает всегда». Делаем быструю проверку после каждого небольшого изменения.
Пройдитесь по основному сценарию в таком порядке:
Если какой‑то шаг ломается — исправляйте прежде, чем добавлять новые функции. Мелкие баги быстро накладываются.
Реальные люди вводят всё подряд — посмотрите, как приложение реагирует:
Частое решение — блокировать пустые задачи:
const text = input.value.trim();
addButton.disabled = text === "";
(Вызывайте это при каждом input и снова перед добавлением.)
Если клики ничего не дают, чаще всего:
app.js не найден).Когда поведение непонятно, логируйте:
console.log("Adding:", text);
console.log("Current tasks:", tasks);
Смотрите вкладку Console в инструментах разработчика на наличие ошибок (красный текст). После исправления удаляйте лишние логи, чтобы не засорять проект.
Приложение можно считать «готовым», когда им удобно пользоваться реальным людям — на телефонах, с клавиатуры и с помощниками экрана (screen readers).
На маленьких экранах мелкие кнопки раздражают. Дайте кликабельным элементам достаточно места:
В CSS увеличение padding, font-size и gap даёт лучший эффект.
Экранные читалки нуждаются в понятных названиях для элементов управления.
<label> (лучший вариант). Если вы не хотите её показывать, можно визуально скрыть через CSS, но оставить в HTML.aria-label="Delete task", чтобы у пользователя был контекст.Это помогает людям понимать назначение элементов без визуального восприятия.
Убедитесь, что приложение можно использовать без мыши:
<form>, чтобы Enter работал по умолчанию)Используйте читаемый базовый размер (16px хорошее начало) и контрастные цвета (тёмный текст на светлом фоне либо наоборот). Не используйте только цвет для обозначения «выполнено» — добавьте зачёркивание или другой явный стиль.
Когда всё работает, потратьте 10–15 минут на «уборку». Это упростит будущие правки и поможет понять проект через некоторое время.
Держите всё небольшим и предсказуемым:
/index.html — структура страницы/styles.css — внешний вид/app.js — поведение (добавление, пометка, удаление, сохранение/загрузка)/README.md — заметки для «будущего вас»Если предпочитаете подкаталоги, можно:
/css/styles.css/js/app.jsТолько убедитесь, что пути в <link> и <script> совпадают с реальными файлами.
Несколько простых приёмов:
taskInput, taskList, saveTasks()Например, удобно читать:
renderTasks(tasks)addTask(text)toggleTask(id)deleteTask(id)В README.md можно кратко написать:
index.html в браузере)Хотя бы заархивируйте папку после важного шага (например, «localStorage работает»). Для истории версий подойдёт Git, но это опционально. Одна резервная копия спасёт от случайного удаления.
Опубликовать — значит поместить файлы (HTML, CSS, JS) в общий доступ, чтобы другие могли открыть ссылку и пользоваться вашим приложением. Поскольку это статический сайт (всё выполняется в браузере), проще всего разместить бесплатно.
Коротко:
Если у вас несколько файлов, проверьте, что имена совпадают (например, styles.css vs style.css).
Если нужен самый простой путь:
localStorage работает).Когда всё проходит — отправьте ссылку другу и попросите протестировать: взгляд со стороны быстро выявит проблемы.
Вы уже сделали рабочее приложение. Если хотите продолжать, эти улучшения дают реальные преимущества и учат новым паттернам.
Добавьте кнопку «Редактировать» рядом с каждой задачей. По клику заменяйте метку на поле ввода (заполненное текущим текстом) и добавляйте кнопки «Сохранить» и «Отмена».
Совет: храните задачи как массив объектов (id, text). Редактирование: найти задачу по id, обновить text, перерисовать и сохранить.
Добавьте три кнопки: All, Active, Done.
Храните фильтр в переменной, например currentFilter = 'all'. При рендере показывайте:
Небольшие расширения:
YYYY-MM-DD) и показывайте её рядом с задачейДаже одно дополнительное поле учит, как обновлять модель данных и UI.
Когда будете готовы, идея в том, чтобы вместо localStorage отправлять задачи на API (сервер) через fetch(). Сервер сохранит их в базе, и они будут синхронизироваться между устройствами.
Если хотите попробовать без полной переделки, платформы типа Koder.ai помогут прототипировать «следующую версию»: опишите эндпоинты, базу данных и изменения UI в чате, итеративно доработайте и экспортируйте исходники (React/Go/PostgreSQL) для изучения и кастомизации.
Попробуйте сделать заметки (с поиском) или трекер привычек (ежедневные отметки). Они используют те же навыки: рендер списка, редактирование, сохранение и простой дизайн UI.