Проекты

🏠

Модуль 10: Практические проекты

Применяем все знания на практике! 6 проектов разной сложности для портфолио.

Введение

Как работать с проектами

  1. Прочитайте требования — поймите что нужно создать
  2. Спланируйте структуру — HTML разметка, компоненты
  3. Начните с Mobile-First — сначала мобильная версия
  4. Итерируйте — MVP → улучшения → полировка
  5. Тестируйте — разные браузеры, устройства
  6. Деплойте — 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

Ресурсы

🎉 Поздравляем!

Вы прошли весь курс по веб-разработке! Теперь у вас есть фундамент для создания любых веб-приложений. Практикуйтесь, создавайте проекты, и вперёд к новым вершинам!

Настройки