Модуль 10: Практические проекты
Применяем все знания на практике! 6 проектов разной сложности для портфолио.
Введение
Как работать с проектами
- Прочитайте требования — поймите что нужно создать
- Спланируйте структуру — HTML разметка, компоненты
- Начните с Mobile-First — сначала мобильная версия
- Итерируйте — MVP → улучшения → полировка
- Тестируйте — разные браузеры, устройства
- Деплойте — GitHub Pages, Vercel, Netlify
Уровни сложности
| Проект | Сложность | Технологии | Время |
|---|---|---|---|
| Портфолио | ★ | HTML, CSS, базовый JS | 4-8 часов |
| Todo App | ★★ | DOM, localStorage, события | 6-12 часов |
| Weather App | ★★ | Fetch API, async/await | 8-16 часов |
| E-commerce | ★★★ | Всё вышеперечисленное | 20-40 часов |
| Dashboard | ★★★ | Графики, фильтры, данные | 20-40 часов |
| Chat App | ★★★★ | WebSocket, real-time | 30-50 часов |
Проект 1: Сайт-портфолио
Описание
Создайте личный сайт-портфолио для демонстрации своих проектов и навыков.
Требования
Обязательно
- Главная страница с hero-секцией
- Секция "Обо мне"
- Секция "Навыки" (с иконками/прогресс-барами)
- Галерея проектов (минимум 3)
- Форма обратной связи
- Адаптивность (mobile-first)
- Плавные анимации при скролле
Бонус
- Тёмная/светлая тема
- Многоязычность
- Блог с статьями
- Интерактивные элементы
Структура
portfolio/
├── index.html
├── css/
│ ├── styles.css
│ └── animations.css
├── js/
│ └── main.js
├── images/
│ ├── projects/
│ └── icons/
└── favicon.ico
HTML каркас
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Иван Петров — Frontend Developer</title>
<meta name="description" content="Портфолио веб-разработчика">
<link rel="stylesheet" href="css/styles.css">
</head>
<body>
<header class="header">
<nav class="nav">
<a href="#" class="logo">IP</a>
<ul class="nav-links">
<li><a href="#about">Обо мне</a></li>
<li><a href="#skills">Навыки</a></li>
<li><a href="#projects">Проекты</a></li>
<li><a href="#contact">Контакты</a></li>
</ul>
<button class="theme-toggle">🌙</button>
<button class="menu-toggle">☰</button>
</nav>
</header>
<main>
<section id="hero" class="hero">
<h1>Привет, я <span class="highlight">Иван</span></h1>
<p class="subtitle">Frontend Developer</p>
<a href="#projects" class="btn">Смотреть проекты</a>
</section>
<section id="about" class="about">
<h2>Обо мне</h2>
<!-- ... -->
</section>
<section id="skills" class="skills">
<h2>Навыки</h2>
<div class="skills-grid">
<div class="skill-card">
<img src="icons/html.svg" alt="HTML">
<h3>HTML5</h3>
<div class="progress" style="--progress: 95%"></div>
</div>
<!-- ... -->
</div>
</section>
<section id="projects" class="projects">
<h2>Проекты</h2>
<div class="projects-grid">
<article class="project-card">
<img src="projects/project1.jpg" alt="Проект 1">
<div class="project-info">
<h3>Todo App</h3>
<p>Приложение для управления задачами</p>
<div class="project-tags">
<span>HTML</span>
<span>CSS</span>
<span>JavaScript</span>
</div>
<div class="project-links">
<a href="#">Demo</a>
<a href="#">GitHub</a>
</div>
</div>
</article>
<!-- ... -->
</div>
</section>
<section id="contact" class="contact">
<h2>Связаться</h2>
<form class="contact-form">
<input type="text" name="name" placeholder="Имя" required>
<input type="email" name="email" placeholder="Email" required>
<textarea name="message" placeholder="Сообщение" required></textarea>
<button type="submit">Отправить</button>
</form>
</section>
</main>
<footer class="footer">
<p>© 2025 Иван Петров</p>
<div class="social-links">
<a href="#">GitHub</a>
<a href="#">LinkedIn</a>
<a href="#">Telegram</a>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>
CSS стили (основа)
:root {
--primary: #6c63ff;
--bg: #ffffff;
--text: #1a1a2e;
--card-bg: #f8f9fa;
}
[data-theme="dark"] {
--bg: #1a1a2e;
--text: #e0e0e0;
--card-bg: #2d2d44;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', system-ui, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.6;
}
/* Hero с анимацией */
.hero {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding: 2rem;
}
.hero h1 {
font-size: clamp(2rem, 8vw, 4rem);
animation: fadeInUp 0.8s ease;
}
.highlight {
color: var(--primary);
}
/* Анимации при скролле */
.animate-on-scroll {
opacity: 0;
transform: translateY(30px);
transition: opacity 0.6s, transform 0.6s;
}
.animate-on-scroll.visible {
opacity: 1;
transform: translateY(0);
}
/* Сетка проектов */
.projects-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
}
JavaScript (интерактивность)
// Анимация при скролле
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
}
});
}, { threshold: 0.1 });
document.querySelectorAll('.animate-on-scroll').forEach(el => {
observer.observe(el);
});
// Переключение темы
const themeToggle = document.querySelector('.theme-toggle');
themeToggle.addEventListener('click', () => {
document.body.dataset.theme =
document.body.dataset.theme === 'dark' ? 'light' : 'dark';
localStorage.setItem('theme', document.body.dataset.theme);
});
// Мобильное меню
const menuToggle = document.querySelector('.menu-toggle');
const navLinks = document.querySelector('.nav-links');
menuToggle.addEventListener('click', () => {
navLinks.classList.toggle('open');
});
Проект 2: Todo App
Описание
Приложение для управления задачами с фильтрацией, поиском и сохранением в localStorage.
Функционал
- Добавление/удаление/редактирование задач
- Отметка выполнения
- Фильтрация: Все / Активные / Выполненные
- Поиск по названию
- Drag & Drop сортировка
- Сохранение в localStorage
- Очистка выполненных
Структура данных
// Структура задачи
const task = {
id: crypto.randomUUID(),
text: 'Выучить JavaScript',
completed: false,
createdAt: Date.now(),
priority: 'medium' // low, medium, high
};
// Состояние приложения
const state = {
tasks: [],
filter: 'all', // all, active, completed
searchQuery: ''
};
// Рендеринг
function render() {
const filtered = state.tasks
.filter(task => {
if (state.filter === 'active') return !task.completed;
if (state.filter === 'completed') return task.completed;
return true;
})
.filter(task =>
task.text.toLowerCase().includes(state.searchQuery.toLowerCase())
);
tasksList.innerHTML = filtered.map(task => `
<li class="task ${task.completed ? 'completed' : ''}" data-id="${task.id}">
<input type="checkbox" ${task.completed ? 'checked' : ''}>
<span class="task-text">${escapeHtml(task.text)}</span>
<button class="delete-btn">🗑</button>
</li>
`).join('');
}
// Сохранение
function save() {
localStorage.setItem('todos', JSON.stringify(state.tasks));
}
// Загрузка
function load() {
const saved = localStorage.getItem('todos');
if (saved) {
state.tasks = JSON.parse(saved);
render();
}
}
🌤️ Проект 3: Weather App
Описание
Приложение погоды с геолокацией, поиском городов и прогнозом на неделю.
API
- OpenWeatherMap — бесплатный API погоды
- Geolocation API — определение местоположения
Код
const API_KEY = 'YOUR_API_KEY';
const API_URL = 'https://api.openweathermap.org/data/2.5';
// Получить погоду по координатам
async function getWeather(lat, lon) {
const response = await fetch(
`${API_URL}/weather?lat=${lat}&lon=${lon}&units=metric&lang=ru&appid=${API_KEY}`
);
return response.json();
}
// Получить прогноз
async function getForecast(lat, lon) {
const response = await fetch(
`${API_URL}/forecast?lat=${lat}&lon=${lon}&units=metric&lang=ru&appid=${API_KEY}`
);
return response.json();
}
// Геолокация
async function getCurrentLocation() {
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(
pos => resolve({ lat: pos.coords.latitude, lon: pos.coords.longitude }),
err => reject(err)
);
});
}
// Поиск города
async function searchCity(query) {
const response = await fetch(
`${API_URL}/geo/1.0/direct?q=${query}&limit=5&appid=${API_KEY}`
);
return response.json();
}
// Инициализация
async function init() {
try {
const { lat, lon } = await getCurrentLocation();
const weather = await getWeather(lat, lon);
const forecast = await getForecast(lat, lon);
renderWeather(weather);
renderForecast(forecast);
} catch (error) {
showError('Не удалось определить местоположение');
}
}
Проект 4: E-commerce
Описание
Интернет-магазин с каталогом товаров, корзиной и оформлением заказа.
Функционал
- Каталог товаров с фильтрацией
- Поиск и сортировка
- Страница товара
- Корзина (localStorage)
- Оформление заказа
- Адаптивный дизайн
Структура
// Состояние
const store = {
products: [],
cart: [],
filters: {
category: 'all',
priceRange: [0, 10000],
sortBy: 'popular'
}
};
// Корзина
const cart = {
items: JSON.parse(localStorage.getItem('cart')) || [],
add(product, quantity = 1) {
const existing = this.items.find(i => i.id === product.id);
if (existing) {
existing.quantity += quantity;
} else {
this.items.push({ ...product, quantity });
}
this.save();
this.updateUI();
},
remove(productId) {
this.items = this.items.filter(i => i.id !== productId);
this.save();
this.updateUI();
},
getTotal() {
return this.items.reduce((sum, item) =>
sum + item.price * item.quantity, 0
);
},
save() {
localStorage.setItem('cart', JSON.stringify(this.items));
},
updateUI() {
const badge = document.querySelector('.cart-badge');
badge.textContent = this.items.length;
badge.hidden = this.items.length === 0;
}
};
// Фильтрация
function filterProducts(products, filters) {
return products
.filter(p => filters.category === 'all' || p.category === filters.category)
.filter(p => p.price >= filters.priceRange[0] && p.price <= filters.priceRange[1])
.sort((a, b) => {
if (filters.sortBy === 'price-asc') return a.price - b.price;
if (filters.sortBy === 'price-desc') return b.price - a.price;
return b.rating - a.rating;
});
}
Проект 5: Dashboard
Описание
Аналитическая панель с графиками, таблицами и фильтрами.
Функционал
- Графики (Chart.js / ApexCharts)
- Таблицы с сортировкой и пагинацией
- Фильтры по датам
- Карточки с KPI
- Экспорт в CSV/PDF
- Темная тема
Графики с Chart.js
import Chart from 'chart.js/auto';
// Линейный график
const salesChart = new Chart(document.getElementById('salesChart'), {
type: 'line',
data: {
labels: ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн'],
datasets: [{
label: 'Продажи',
data: [12000, 19000, 15000, 25000, 22000, 30000],
borderColor: '#6c63ff',
backgroundColor: 'rgba(108, 99, 255, 0.1)',
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
plugins: {
legend: { display: false }
},
scales: {
y: { beginAtZero: true }
}
}
});
// Обновление данных
async function updateCharts(dateRange) {
const data = await fetchAnalytics(dateRange);
salesChart.data.labels = data.labels;
salesChart.data.datasets[0].data = data.values;
salesChart.update();
}
Проект 6: Chat App
Описание
Real-time чат с WebSocket, комнатами и историей сообщений.
Функционал
- Регистрация/авторизация
- Список комнат
- Real-time сообщения
- Индикатор "печатает..."
- Отправка изображений
- Уведомления
WebSocket клиент
class ChatClient {
constructor(url) {
this.url = url;
this.ws = null;
this.handlers = new Map();
this.connect();
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log('Connected');
this.emit('connected');
};
this.ws.onmessage = (event) => {
const { type, payload } = JSON.parse(event.data);
this.emit(type, payload);
};
this.ws.onclose = () => {
console.log('Disconnected, reconnecting...');
setTimeout(() => this.connect(), 3000);
};
}
send(type, payload) {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({ type, payload }));
}
}
on(event, handler) {
if (!this.handlers.has(event)) {
this.handlers.set(event, []);
}
this.handlers.get(event).push(handler);
}
emit(event, data) {
const handlers = this.handlers.get(event) || [];
handlers.forEach(h => h(data));
}
// API методы
joinRoom(roomId) {
this.send('join', { roomId });
}
sendMessage(roomId, text) {
this.send('message', { roomId, text, timestamp: Date.now() });
}
startTyping(roomId) {
this.send('typing', { roomId, typing: true });
}
stopTyping(roomId) {
this.send('typing', { roomId, typing: false });
}
}
// Использование
const chat = new ChatClient('wss://chat.example.com');
chat.on('message', (msg) => {
appendMessage(msg);
});
chat.on('typing', ({ userId, typing }) => {
showTypingIndicator(userId, typing);
});
Деплой проектов
GitHub Pages
# 1. Создать репозиторий на GitHub
# 2. Push проекта
git init
git add .
git commit -m "Initial commit"
git remote add origin https://github.com/username/repo.git
git push -u origin main
# 3. Settings → Pages → Source: main branch
# Сайт будет на: https://username.github.io/repo/
Vercel
# CLI
npm i -g vercel
vercel
# Или через vercel.com — подключить GitHub репозиторий
Netlify
# Drag & drop папку на netlify.com
# Или подключить GitHub репозиторий
# netlify.toml для настройки
[build]
publish = "dist"
command = "npm run build"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
Что дальше?
Продолжение обучения
Frontend
- React / Vue / Svelte — современные фреймворки
- TypeScript — типизация
- Next.js / Nuxt — SSR фреймворки
- State Management — Redux, Zustand, Pinia
Backend
- Node.js + Express
- Базы данных — PostgreSQL, MongoDB
- REST API / GraphQL
- Authentication — JWT, OAuth
DevOps
- Docker
- CI/CD — GitHub Actions
- Мониторинг — Sentry, Analytics
Ресурсы
🎉 Поздравляем!
Вы прошли весь курс по веб-разработке! Теперь у вас есть фундамент для создания любых веб-приложений. Практикуйтесь, создавайте проекты, и вперёд к новым вершинам!