Онлайн-собеседование фронтенд-разработчика: Серёжа Попов и Эйч
Сегодня мы разберём учебное собеседование джуниор-фронтенд-разработчика, в ходе которого кандидат Дима продемонстрировал широкий, хотя и поверхностный кругозор — от вёрстки и CSS-оптимизации до работы с фреймворками Vue и React, а также рассказал о реальных рабочих задачах и подходах к их решению. Интервьюеры отметили его способность рассуждать, наличие насмотренности и готовность учиться, но обратили внимание на пробелы в актуальных знаниях (HTTP/2, современные форматы изображений, вариативные шрифты) и на необходимость глубже погружаться в базовые технологии, а не распыляться на модные, но второстепенные направления. В итоге кандидат был условно принят на позицию при условии наличия ментора и чёткого плана развития, сфокусированного на закрытии фундаментальных пробелов.
Вопрос 1. Расскажи о своём опыте, почему ты пришёл во фронтенд и что тебя затянуло в веб-разработку.
Таймкод: 00:05:05
Ответ собеседника: Правильный. Пришёл из сферы детского технического образования (Arduino, ЧПУ). Выбрал курсы HTML-академии по React, параллельно прошёл собеседование в томскую компанию с тестовым на Express + шаблонизатор. Сейчас работает 4 месяца на Vue после миграции компании с React.
Правильный ответ:
Отличный пример осознанного перехода в разработку из смежной области. Такая история показывает, что кандидат принял взвешенное решение, а не просто следует трендам.
Ключевые сильные стороны в этом ответе:
- Преподавательский опыт — умение объяснять сложные концепции и работать с людьми ценно в командной разработке
- Быстрый вход в профессию — совмещение обучения с реальным трудоустройством говорит о высокой мотивации
- Работа с разными технологиями — опыт и с React, и с Vue даёт понимание различий между фреймворками
- Работа с полным стеком — даже на начальном этапе был контакт с backend (Express + шаблонизаторы), что расширяет кругозор
Что можно развить в этом направлении на собеседовании:
При обсуждении опыта миграции с React на Vue стоит обратить внимание на:
- Различия в реактивности (Virtual DOM vs Proxy-based reactivity)
- Подходы к управлению состоянием (Redux/Pinia, Vuex)
- Компонентная модель и lifecycle hooks
- Экосистру и доступные инструменты
Такой переход демонстрирует гибкость и способность быстро адаптироваться к новым технологиям — важное качество для современного разработчика.
Вопрос 2. Какие наиболее интересные или сложные задачи приходилось решать в рамках обучения и текущей работы?
Таймкод: 00:09:34
Ответ собеседника: Правильный. В обучении — настройка архитектуры MVP-приложений и курс по React. На работе — работа с анимациями через GSAP, слайдеры, переходы. Также вёрстка для PDF-версии с поддержкой IE11 без flexbox.
Правильный ответ:
Хороший набор задач, демонстрирующий разнообразие опыта. Разберём каждую из них подробнее.
Архитектура MVP-приложений
MVP (Model-View-Presenter) — это архитектурный паттерн, который разделяет приложение на три слоя:
- Model — бизнес-логика и данные
- View — отображение и пользовательский интерфейс
- Presenter — посредник, обрабатывающий пользовательские действия и обновляющий View
Самостоятельная настройка архитектуры — ценный опыт, так как учит думать о структуре приложения на уровне выше отдельных компонентов.
Анимации с GSAP (GreenSock Animation Platform)
GSAP — профессиональная библиотека анимаций, которая решает проблемы производительности и кроссбраузерности. Сложные задачи в этой области обычно включают:
- Координацию нескольких анимаций по таймлайну
- Scroll-triggered анимации
- Оптимизацию для слабых устройств
- Синхронизацию анимаций с состоянием приложения
Вёрстка для PDF с поддержкой IE11
Это классическая «боль» фронтенд-разработки. Ограничения включают:
- Отсутствие flexbox и grid
- Необходимость использования float или table-вёрстки
- Ограниченная поддержка CSS3
- Специфические требования к генерации PDF (page breaks, margins, headers/footers)
Для таких задач часто используют:
/* Вмест flexbox */
.container {
display: table;
width: 100%;
}
.item {
display: table-cell;
vertical-align: top;
}
/* Управление разрывами страниц для PDF */
.page-break {
page-break-before: always;
}
Рекомендации для развития:
При обсуждении сложных задач стоит акцентировать внимание на:
- Какую проблему решали и почему она была сложной
- Какие альтернативные подходы рассматривали
- Какой результат получили и чему научились
- Как это повлияло на качество продукта или процесс разработки
Вопрос 3. Какие пробелы в знаниях ты видишь и что планируешь изучить в ближайшее время?
Таймкод: 00:12:07
Ответ собеседника: Правильный. Пробелы в роутинге Vue (кастомная реализация из-за PHP бэкенда без SSR). Планирует углубиться в роутинг Vue, анимации и переходы, а также 3D-анимации в вебе.
Правильный ответ:
Честная и структурированная самооценка. Разберём каждое направление подробнее.
Кастомный роутинг Vue без SSR
Это нетипичный, но интересный кейс. Обычно Vue Router работает в двух режимах:
- History mode — использует History API, требует серверной поддержки
- Hash mode — использует хеш-фрагмент URL (
#/path), работает без серверной настройки
При использовании PHP вместо Node.js для SSR часто приходится реализовывать кастомные решения:
// Пример кастомного роутинга на основе hash
const routes = {
'/': HomeComponent,
'/about': AboutComponent,
'/products': ProductsComponent
};
function router() {
const path = window.location.hash.slice(1) || '/';
const component = routes[path] || NotFoundComponent;
// Рендеринг компонента
new Vue({
render: h => h(component)
}).$mount('#app');
}
window.addEventListener('hashchange', router);
window.addEventListener('load', router);
Для индексации поисковиками без SSR используют:
- Prerendering статических страниц
- Серверный rendering на PHP с передачей начального состояния
- Мета-теги и структурированные данные на клиенте
Анимации и переходы во Vue
Vue предоставляет встроенные компоненты для анимаций:
<Transition>— для одиночных элементов<TransitionGroup>— для списков- JavaScript hooks для интеграции с GSAP
<template>
<Transition
@before-enter="onBeforeEnter"
@enter="onEnter"
@after-enter="onAfterEnter"
@before-leave="onBeforeLeave"
@leave="onLeave"
:css="false"
>
<div v-if="show">Контент</div>
</Transition>
</template>
<script>
import gsap from 'gsap';
export default {
methods: {
onEnter(el, done) {
gsap.from(el, {
duration: 0.5,
opacity: 0,
y: 20,
onComplete: done
});
},
onLeave(el, done) {
gsap.to(el, {
duration: 0.3,
opacity: 0,
y: -20,
onComplete: done
});
}
}
}
</script>
3D-анимации в вебе
Основные технологии для 3D в браузере:
- Three.js — самая популярная библиотека для WebGL
- Babylon.js — альтернатива с акцентом на игры и интерактивность
- CSS 3D Transforms — для простых 3D-эффектов
- WebGPU — следующее поколение веб-графики
// Пример простой сцены на Three.js
import * as THREE from 'three';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight);
const renderer = new THREE.WebGLRenderer();
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
Рекомендации по развитию:
Приоритетность изучения может быть такой:
- Роутинг Vue — критично для текущей работы
- Анимации — углубление в уже имеющийся опыт
- 3D — перспективное направление для портфолио
Умение честно оценивать свои пробелы и формулировать план развития — признак зрелого разработчика.
Вопрос 4. Какие типы задач тебе больше всего нравится решать: архитектура, оптимизация, дебаг, вёрстка, бизнес-логика?
Таймкод: 00:15:01
Ответ собеседника: Правильный. Нравится архитектура, потому что видит как всё работает изнутри. Углубляется в CSS. Отмечает паттерн: сначала непонятное воспринимается как «магия», но после разбора появляется интерес. Привёл пример с вёрсткой без flexbox для PDF.
Правильный ответ:
Очень ценное самонаблюдение о паттерне обучения. Разберём подробнее.
Паттерн «магия → понимание → интерес»
Это классический путь освоения сложных тем в разработке. Многие технологии кажутся «магией» до тех пор, пока не поймёшь их устройство:
- React/Vue реактивность → Virtual DOM diffing, Proxy/defineProperty
- CSS Grid/Flexbox → алгоритмы layout engine браузера
- Webpack/Vite → граф зависимостей, tree shaking, code splitting
- TypeScript → система типов, структурная типизация
Умение признавать «магию» и целенаправленно её разбирать — ключевой навык для профессионального роста.
Интерес к архитектуре
Архитектура привлекает возможностью видеть систему целиком. Основные аспекты, которые стоит изучать:
Паттерны проектирования:
- MVVM (Model-View-ViewModel) — основа Vue
- Flux/Redux — однонаправленный поток данных
- Composition API — композиция вместо наследования
- Микрофронтенды — разделение больших приложений
Структура проекта:
src/
├── components/ # Переиспользуемые компоненты
│ ├── ui/ # Базовые UI-элементы
│ └── features/ # Компоненты с бизнес-логикой
├── composables/ # Переиспользуемая логика (Vue 3)
├── stores/ # Управление состоянием
├── services/ # API-слой
├── utils/ # Утилиты
├── types/ # TypeScript типы
└── views/ # Страницы/роуты
Углубление в CSS
CSS — обширная область, которая постоянно развивается. Современные возможности:
/* Container Queries */
.card-container {
container-type: inline-size;
}
@container (min-width: 400px) {
.card { display: grid; grid-template-columns: 1fr 2fr; }
}
/* Subgrid */
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.grid-item {
display: grid;
grid-template-rows: subgrid;
grid-row: span 3;
}
/* :has() селектор */
.card:has(.badge) {
border-color: gold;
}
/* Logical Properties */
margin-inline-start: 1rem; /* вместо margin-left */
padding-block: 0.5rem; /* вместо padding-top/bottom */
Рекомендации для развития:
При интересе к архитектуре стоит изучить:
- Принципы SOLID применительно к фронтенду
- Feature-Sliced Design — архитектурная методология для фронтенд-приложений
- Мониторинг и observability (Sentry, Lighthouse CI)
- Стратегии оптимизации (lazy loading, code splitting, prefetching)
Понимание собственного стиля обучения и предпочтений помогает выбирать задачи, которые будут и полезны, и интересны для развития.
Вопрос 5. Как ты обсуждаешь с командой задачи, которые тебе непонятны или вызывают трудности?
Таймкод: 00:17:33
Ответ своеседника: Правильный. Сначала пробует решить сам (15-60 минут), комментирует код для понимания. Затем обращается к ментору-коллеге. Был один случай снятия задачи — динамические формы из JSON с плагином, плохо адаптированным под Vue, и проблемой input type file в IE11.
Правильный ответ:
Зрелый подход к работе с трудными задачами. Разберём стратегию и кейс подробнее.
Стратегия «сам → ментор → эскалация»
Это правильная последовательность, которая балансирует между самостоятельностью и эффективностью:
1. Самостоятельное исследование (15-60 минут)
Техники для эффективного самостоятельного разбора:
- Комментирование кода — помогает понять поток выполнения
- Отладка через console.log / debugger
- Изоляция проблемы в отдельном компоненте
- Поиск в документации и исходном коде библиотеки
// Пример комментирования для понимания
function processFormData(jsonSchema) {
// 1. Парсим JSON-схему формы
const fields = parseSchema(jsonSchema);
// 2. Для каждого поля создаём компонент
// Проблема: плагин ожидает Options API, а мы используем Composition API
const components = fields.map(field => {
// TODO: разобраться с адаптером для Vue 3
return createFieldComponent(field);
});
return components;
}
2. Обращение к ментору
Правильный момент для обращения — когда потратил разумное время и может чётко сформулировать:
- Что пытаешься сделать
- Что уже попробовал
- Где именно застрял
- Какие гипотезы есть
3. Эскалация и передача задачи
Это нормальная практика, а не признак слабости. Критерии для передачи:
- Задача критична по срокам
- Требует специфических знаний, которые невозможно быстро получить
- Риск сломать существующий функционал слишком высок
Кейс: динамические формы из JSON + IE11
Это действительно сложная комбинация проблем:
// Типичные проблемы с динамическими формами из JSON
const schema = {
fields: [
{ type: 'text', name: 'email', required: true },
{ type: 'file', name: 'avatar', accept: 'image/*' } // Проблема в IE11
]
};
// IE11 не поддерживает:
// - input type="file" с некоторыми атрибутами
// - FormData API полностью
// - Promise, fetch и другие современные API
// Решения для IE11:
// 1. Полифиллы: core-js, whatwg-fetch, formdata-polyfill
// 2. Библиотеки: axios вместо fetch
// 3. Альтернативные подходы для file upload (iframe, Flash)
Рекомендации для развития:
Для улучшения процесса работы с трудными задачами:
- Вести «журнал проблем» — записывать сложные кейсы и их решения
- Проводить пост-мортемы по сложным задачам с командой
- Предлагать альтернативные решения, когда текущее не работает
- Не бояться говорить о рисках заранее, а не в последний момент
Умение вовремя попросить помощь — это не слабость, а профессионализм, который экономит время всей команды.
Вопрос 6. Знаком ли ты с методологиями разработки (Scrum, Kanban) и как организована работа с задачами на текущем месте?
Таймкод: 00:20:28
Ответ собеседника: Правильный. Знаком с Scrum и Kanban ещё из опыта в образовании. На текущем месте используется Kanban с бэклогом и двухнедельными спринтами. Приоритеты определяются для заказчика и команды.
Правильный ответ:
Хорошее понимание базовых принципов. Разберём методологии подробнее.
Scrum vs Kanban: ключевые различия
Scrum:
- Фиксированные итерации (спринты) обычно 1-4 недели
- Роли: Product Owner, Scrum Master, Development Team
- Церемонии: Sprint Planning, Daily Standup, Sprint Review, Retrospective
- Артефакты: Product Backlog, Sprint Backlog, Increment
- Фокус на обязательствах на спринт
Kanban:
- Непрерывный поток задач без фиксированных итераций
- Визуализация потока (доска с колонками)
- Ограничение Work In Progress (WIP)
- Фокус на потоке и времени выполнения (lead time)
- Гибкость в приоритизации
Гибридные подходы:
Многие команды используют комбинации:
- Scrumban — элементы Scrum с доской Kanban
- Kanban со спринтами — как в описанном случае кандидатом
Типичная структура доски Kanban:
Backlog | Ready | In Progress | Review | Testing | Done
--------|-------|-------------|--------|---------|-----
Task 1 | Task 4| Task 7 | Task 9 | Task 11 | Task 13
Task 2 | Task 5| Task 8 | Task 10| | Task 14
Task 3 | Task 6| | | | Task 15
WIP-лимиты:
Ограничение количества задач в каждой колонке помогает:
- Выявлять узкие места
- Снижать контекст-свитчинг
- Улучшать фокус команды
# Пример WIP-лимитов
In Progress: 3 # Максимум 3 задачи в работе
Review: 2 # Максимум 2 задачи на ревью
Testing: 2 # Максимум 2 задачи на тестировании
Метрики Kanban:
- Lead Time — время от создания задачи до завершения
- Cycle Time — время от начала работы до завершения
- Throughput — количество завершённых задач за период
- Cumulative Flow Diagram — визуализация потока
Рекомендации для развития:
Для углубления понимания процессов:
- Изучить принципы Lean и их применение в разработке
- Понять разницу между оценкой и прогнозированием
- Освоить техники декомпозиции задач
- Изучить практики code review и парного программирования
Понимание процессов разработки помогает лучше взаимодействовать с командой и предлагать улучшения рабочих процессов.
Вопрос 7. Назови все известные тебе способы оптимизации скорости загрузки страниц.
Таймкод: 00:22:25
Ответ собеседника: Неполный. Назвал минификацию, уменьшение HTTP-запросов, объединение в бандлы, несколько доменов, оптимизацию графики, размещение CSS в head и JS в конце, кэширование, WebP, SVG-спрайты, lazy load. Не упомянул Critical CSS, CDN, GZIP/Brotli, preload/prefetch, code splitting, tree shaking, оптимизацию шрифтов, SSR/SSG, HTTP/2, service workers.
Правильный ответ:
Хороший базовый набор, но для полной картины нужно охватить больше аспектов. Разберём все основные направления оптимизации.
1. Оптимизация ресурсов
Минификация и сжатие:
// Webpack конфигурация для минификации
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin(), // Минификация JS
new CssMinimizerPlugin() // Минификация CSS
]
}
};
Форматы изображений:
- WebP — на 25-35% меньше JPEG при том же качестве
- AVIF — следующее поколение, ещё лучше сжатие
- SVG — для иконок и простой графики
- Адаптивные изображения:
<picture>
<source srcset="image.avif" type="image/avif">
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="Описание">
</picture>
<!-- Адаптивные размеры -->
<img
srcset="small.jpg 480w, medium.jpg 800w, large.jpg 1200w"
sizes="(max-width: 600px) 480px, (max-width: 1000px) 800px, 1200px"
src="medium.jpg"
alt="Описание"
>
2. Оптимизация загрузки
Critical CSS — критические стили:
<head>
<!-- Инлайн критические стили -->
<style>
/* Только стили для первого экрана */
.header { ... }
.hero { ... }
</style>
<!-- Остальные стили загружаются асинхронно -->
<link rel="preload" href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
</head>
Preload и Prefetch:
<!-- Preload — для ресурсов, нужных на текущей странице -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="hero-image.jpg" as="image">
<!-- Prefetch — для ресурсов, которые понадобятся позже -->
<link rel="prefetch" href="next-page.js">
<link rel="prefetch" href="next-page-data.json">
<!-- Preconnect — заранее установить соединение -->
<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://cdn.example.com">
3. Code Splitting и Tree Shaking
// Динамический импорт для code splitting
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));
// Vue 3
const HeavyComponent = defineAsyncComponent(() => import('./HeavyComponent'));
// Маршрут-уровневый splitting
const routes = [
{
path: '/dashboard',
component: () => import('./views/Dashboard.vue') // Загружается только при переходе
}
];
Tree Shaking — удаление неиспользуемого кода:
// Плохо — импорт всей библиотеки
import _ from 'lodash';
_.get(obj, 'path.to.value');
// Хорошо — импорт только нужной функции
import get from 'lodash/get';
get(obj, 'path.to.value');
// Или использовать es-версию
import { get } from 'lodash-es';
4. Серверные оптимизации
GZIP и Brotli сжатие:
# Nginx конфигурация
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml;
gzip_min_length 1000;
# Brotli (лучше сжатие, но требует модуль)
brotli on;
brotli_types text/plain text/css application/json application/javascript text/xml;
CDN (Content Delivery Network):
- Географически распределённые серверы
- Кэширование статики на edge-серверах
- Снижение latency для пользователей
HTTP/2 и HTTP/3:
- Multiplexing — параллельные запросы через одно соединение
- Server Push — сервер может отправлять ресурсы заранее
- Header сжатие
5. Кэширование
HTTP-кэширование:
# Статические файлы с долгим кэшем
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# HTML с коротким кэшем
location ~* \.html$ {
expires 1h;
add_header Cache-Control "public, must-revalidate";
}
Service Workers для офлайн-кэширования:
// service-worker.js
const CACHE_NAME = 'app-v1';
const urlsToCache = [
'/',
'/styles.css',
'/app.js',
'/offline.html'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(urlsToCache))
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// Возвращаем из кэша или делаем запрос
return response || fetch(event.request);
})
);
});
6. Оптимизация шрифтов
/* Подключение шрифта с font-display */
@font-face {
font-family: 'CustomFont';
src: url('font.woff2') format('woff2');
font-display: swap; /* Показываем системный шрифт до загрузки */
}
/* Subsetting — только нужные символы */
/* Используйте pyftsubset или google-webfonts-helper */
7. SSR и SSG
Server-Side Rendering (SSR):
- HTML генерируется на сервере
- Быстрый First Contentful Paint
- Лучшая SEO
Static Site Generation (SSG):
- HTML генерируется при сборке
- Максимальная скорость
- Подходит для контент-сайтов
// Next.js пример
// SSG
export async function getStaticProps() {
const data = await fetchData();
return { props: { data }, revalidate: 60 }; // ISR
}
// SSR
export async function getServerSideProps() {
const data = await fetchData();
return { props: { data } };
}
8. Мониторинг производительности
Core Web Vitals:
- LCP (Largest Contentful Paint) < 2.5s
- FID (First Input Delay) < 100ms
- CLS (Cumulative Layout Shift) < 0.1
Инструменты:
- Lighthouse
- WebPageTest
- Chrome DevTools Performance
- Real User Monitoring (RUM)
Комплексный подход к оптимизации включает все эти направления, приоритеты зависят от конкретного проекта и его узких мест.
Вопрос 8. Какие инструменты можно использовать для проверки скорости загрузки сайта и выявления проблем?
Таймкод: 00:31:42
Ответ собеседника: Неполный. Упомянул инструменты разработчика в браузере, но не назвал конкретные инструменты: Google PageSpeed Insights, Lighthouse, WebPageTest, GTmetrix.
Правильный ответ:
Отсутствие конкретных названий инструментов — пробел, который стоит восполнить. Разберём основные инструменты подробнее.
1. Chrome DevTools
Встроенный инструмент в браузере Chrome с несколькими полезными панелями:
Network панель:
- Список всех запросов с таймингами
- Waterfall-диаграмма загрузки
- Фильтрация по типу ресурсов
- Throttling для эмуляции медленного соединения
Performance панель:
- Запись и анализ runtime-производительности
- Frames — визуализация FPS
- Main thread — активность JavaScript
- Rendering — repaints и reflows
Lighthouse панель:
- Встроенный аудит прямо в DevTools
- Генерация отчётов по категориям
2. Google PageSpeed Insights
Онлайн-инструмент от Google:
- Анализ реальных данных пользователей (CrUX)
- Лабораторные тесты
- Оценка от 0 до 100
- Конкретные рекомендации по улучшению
https://pagespeed.web.dev/
3. Lighthouse
Можно использовать несколькими способами:
В Chrome DevTools:
- Открыть DevTools → вкладка Lighthouse
- Выбрать категории: Performance, Accessibility, Best Practices, SEO
- Запустить аудит
CLI версия:
# Установка
npm install -g lighthouse
# Запуск аудита
lighthouse https://example.com --output html --output-path ./report.html
# С опциями
lighthouse https://example.com \
--only-categories=performance \
--throttling.cpuSlowdownMultiplier=4 \
--emulated-form-factor=mobile
Программное использование:
const lighthouse = require('lighthouse');
const chromeLauncher = require('chrome-launcher');
async function runLighthouse(url) {
const chrome = await chromeLauncher.launch({ chromeFlags: ['--headless'] });
const options = {
logLevel: 'info',
output: 'html',
onlyCategories: ['performance'],
port: chrome.port
};
const runnerResult = await lighthouse(url, options);
// Оценка производительности
console.log('Performance score:', runnerResult.lhr.categories.performance.score * 100);
// Рекомендации
runnerResult.lhr.audits['unused-javascript'].details.items.forEach(item => {
console.log('Unused JS:', item.url, 'Savings:', item.wastedBytes);
});
await chrome.kill();
}
4. WebPageTest
Продвинутый инструмент для глубокого анализа:
- Тестирование из разных локаций
- Различные браузеры и устройства
- Видеозапись загрузки
- Filmstrip view — визуальная загрузка по кадрам
- Web Vitals метрики
- Opportunities и Diagnostics
https://www.webpagetest.org/
5. GTmetrix
Комбинированный инструмент:
- Использует Lighthouse и WebPageTest
- Мониторинг с уведомлениями
- Исторические данные
- Рекомендации с приоритетами
https://gtmetrix.com/
6. Дополнительные инструменты
Web Vitals Chrome Extension:
- Показывает Core Web Vitals в реальном времени
- Не требует настройки
Bundle Analyzer:
// Webpack Bundle Analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
};
Source Map Explorer:
npx source-map-explorer dist/main.js
7. Real User Monitoring (RUM)
Для мониторинга реальных пользователей:
// Использование web-vitals библиотеки
import { getCLS, getFID, getLCP, getFCP, getTTFB } from 'web-vitals';
function sendToAnalytics(metric) {
const body = JSON.stringify(metric);
navigator.sendBeacon('/analytics', body);
}
getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getLCP(sendToAnalytics);
Сервисы RUM:
- Google Analytics 4
- SpeedCurve
- New Relic
- Datadog RUM
8. Автоматизация в CI/CD
# GitHub Actions пример
- name: Lighthouse CI
uses: treosh/lighthouse-ci-action@v9
with:
urls: |
https://example.com/
https://example.com/about
budgetPath: ./budget.json
uploadArtifacts: true
// budget.json
[
{
"path": "/*",
"timings": [
{ "metric": "interactive", "budget": 3000 },
{ "metric": "first-contentful-paint", "budget": 1500 }
],
"resourceSizes": [
{ "resourceType": "script", "budget": 300 },
{ "resourceType": "total", "budget": 500 }
]
}
]
Знание этих инструментов и умение ими пользоваться — важная часть работы фронтенд-разработчика, даже если в компании есть выделенный специалист по производительности.
Вопрос 9. Какие способы оптимизации шрифтов ты знаешь?
Таймкод: 00:32:58
Ответ собеседника: Неполный. Знает про woff2 и subsetting, но не знаком с вариативными шрифтами. Не упомянул font-display, preload, системные шрифты как фолбэк.
Правильный ответ:
Шрифты — один из главных «тяжеловесов» веб-страниц. Разберём все способы оптимизации подробно.
1. Форматы шрифтов
WOFF2 — лучший формат для веба:
- Сжатие Brotli
- На 30% меньше чем WOFF
- Поддержка всеми современными браузерами
@font-face {
font-family: 'CustomFont';
src: url('font.woff2') format('woff2'); /* Приоритетный формат */
font-weight: 400;
font-style: normal;
}
Иерартика форматов:
WOFF2 > WOFF > TTF/OTF > EOT (только IE)
2. Font Display
Свойство font-display управляет поведением при загрузке:
@font-face {
font-family: 'CustomFont';
src: url('font.woff2') format('woff2');
font-display: swap; /* Рекомендуемое значение */
}
Варианты font-display:
| Значение | Поведение | Когда использовать |
|---|---|---|
auto | По умолчанию (обычно block) | Не рекомендуется |
block | 3с ожидание, потом fallback | Иконочные шрифты |
swap | Сразу fallback, потом swap | Текстовые шрифты |
fallback | 100ms hidden, потом swap | Компромисс |
optional | 100ms hidden, решает браузер | Не критичные шрифты |
3. Preload для шрифтов
Загрузка шрифтов до того, как они понадобятся:
<head>
<!-- Preload основных шрифтов -->
<link rel="preload" href="/fonts/main-regular.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/fonts/main-bold.woff2" as="font" type="font/woff2" crossorigin>
<!-- Preconnect к CDN со шрифтами -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
</head>
Важно: Атрибут crossorigin обязателен для шрифтов, даже если они на том же домене.
4. Font Subsetting
Удаление неиспользуемых символов из файла шрифта:
Ручной subsetting:
# Используем pyftsubset из fonttools
pip install fonttools brotli
# Оставляем только латиницу и кириллицу
pyftsubset font.ttf \
--output-file=font-subset.woff2 \
--flavor=woff2 \
--unicodes=U+0000-007F,U+0400-U+04FF
Автоматический subsetting:
// Используем google-webfonts-helper
// https://google-webfonts-helper.herokuapp.com/
// Или subset-font для CSS
const subsetFont = require('subset-font');
async function createSubset() {
const fontBuffer = await fs.readFile('font.ttf');
const subset = await subsetFont(fontBuffer, 'Текст на сайте', {
targetFormat: 'woff2'
});
await fs.writeFile('font-subset.woff2', subset);
}
5. Вариативные шрифты (Variable Fonts)
Один файл вместо нескольких начертаний:
@font-face {
font-family: 'Inter';
src: url('Inter-Variable.woff2') format('woff2-variations');
font-weight: 100 900; /* Диапазон весов */
font-stretch: 75% 125%;
font-display: swap;
}
/* Использование */
.thin { font-weight: 100; }
.regular { font-weight: 400; }
.bold { font-weight: 700; }
.black { font-weight: 900; }
/* Промежуточные значения */
.semi-bold { font-weight: 650; }
Преимущества вариативных шрифтов:
- Один файл вместо 5-10
- Любые промежуточные значения веса
- Меньший общий размер
6. Системные шрифты как фолбэк
Оптимальный стек системных шрифтов:
body {
font-family:
'CustomFont', /* Основной шрифт */
-apple-system, /* macOS, iOS */
BlinkMacSystemFont, /* macOS Chrome */
'Segoe UI', /* Windows */
Roboto, /* Android */
Oxygen, /* Linux */
Ubuntu, /* Ubuntu */
Cantarell, /* GNOME */
sans-serif; /* Универсальный фолбэк */
}
Или полностью системные шрифты:
body {
font-family: system-ui, -apple-system, sans-serif;
}
7. Оптимизация рендеринга
font-size-adjust — сохранение читаемости при фолбэке:
body {
font-family: 'CustomFont', Arial, sans-serif;
font-size-adjust: 0.5; /* x-height ratio */
}
Уменьшение Cumulative Layout Shift:
/* Подгонка размеров фолбэк-шрифта */
@font-face {
font-family: 'CustomFont-Fallback';
src: local('Arial');
size-adjust: 105%; /* Масштаб */
ascent-override: 90%; /* Высота заглавных */
descent-override: 20%; /* Высота строчных */
line-gap-override: 0%;
}
body {
font-family: 'CustomFont', 'CustomFont-Fallback', sans-serif;
}
8. Ленивая загрузка шрифтов
Загрузка только при необходимости:
// Используем Font Loading API
if ('fonts' in document) {
const font = new FontFace('CustomFont', 'url(font.woff2)');
font.load().then(loadedFont => {
document.fonts.add(loadedFont);
document.body.classList.add('fonts-loaded');
});
}
/* Скрываем текст до загрузки шрифта */
body {
font-family: system-ui, sans-serif;
}
.fonts-loaded body {
font-family: 'CustomFont', system-ui, sans-serif;
}
9. Кэширование шрифтов
# Nginx конфигурация
location ~* \.(woff2?|ttf|otf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header Access-Control-Allow-Origin "*";
}
Чек-лист оптимизации шрифтов:
- Использовать WOFF2 формат
- Настроить font-display: swap
- Preload критических шрифтов
- Сделать subsetting для нестандартных шрифтов
- Рассмотреть вариативные шрифты
- Настроить правильный стек фолбэков
- Добавить долгий кэш
- Проверить CLS при загрузке
Вопрос 10. В чём отличие между CSS Grid и Flexbox и для каких задач каждый лучше подходит?
Таймкод: 00:33:24
Ответ собеседника: Правильный. Grid — двунаправленная система для крупных сеток страницы. Flexbox — однонаправленная для мелких элементов. Они дополняют друг друга.
Правильный ответ:
Отличный ответ, который показывает правильное понимание обоих инструментов. Разберём подробнее.
1. Фундаментальное отличие
CSS Grid — двумерная система:
- Управляет строками И столбцами одновременно
- Создаёт сетку на уровне контейнера
- Элементы размещаются в ячейках сетки
Flexbox — одномерная система:
- Управляет одним направлением (строка ИЛИ столбец)
- Распределяет элементы вдоль одной оси
- Контент определяет размеры
2. CSS Grid — для чего подходит
Макет страницы:
.page-layout {
display: grid;
grid-template-areas:
"header header header"
"sidebar main aside"
"footer footer footer";
grid-template-columns: 250px 1fr 200px;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.aside { grid-area: aside; }
.footer { grid-area: footer; }
Карточки в сетке:
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 24px;
}
Сложные перекрытия:
.gallery {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(3, 200px);
}
.featured {
grid-column: span 2;
grid-row: span 2;
}
Выравнивание по двум осям:
.centered {
display: grid;
place-items: center; /* И по горизонтали, и по вертикали */
}
3. Flexbox — для чего подходит
Навигация:
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
}
.nav-links {
display: flex;
gap: 24px;
}
Карточка с гибким содержимым:
.card {
display: flex;
flex-direction: column;
}
.card-content {
flex: 1; /* Занимает всё доступное пространство */
}
.card-footer {
margin-top: auto; /* Прижимает к низу */
}
Центрирование элемента:
.flex-center {
display: flex;
justify-content: center;
align-items: center;
}
Распределение элементов:
.toolbar {
display: flex;
gap: 8px;
}
.toolbar-item {
flex: 1; /* Равное распределение пространства */
}
4. Когда что выбирать
Используйте Grid, когда:
- Нужна сложная структура страницы
- Элементы должны выстраиваться в строки и столбцы одновременно
- Нужно точное позиционирование в сетке
- Важен контроль над пустыми пространствами
Используйте Flexbox, когда:
- Нужно распределить элементы в одном направлении
- Размеры элементов зависят от контента
- Нужно выравнивание элементов внутри контейнера
- Работа с компонентами (кнопки, карточки, формы)
5. Комбинирование Grid и Flexbox
Лучшая практика — использовать оба вместе:
/* Grid для макета страницы */
.page {
display: grid;
grid-template-columns: 250px 1fr;
grid-template-rows: 60px 1fr auto;
}
/* Flexbox для навигации внутри header */
.header {
display: flex;
justify-content: space-between;
align-items: center;
}
/* Grid для карточек */
.products {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
/* Flexbox для содержимого карточки */
.product-card {
display: flex;
flex-direction: column;
}
6. Сравнительная таблица
| Аспект | Grid | Flexbox |
|---|---|---|
| Размерность | 2D (строки + столбцы) | 1D (строка или столбец) |
| Модель размещения | Ячейки сетки | Главная ось |
| Контроль пустот | Да (gap, auto-fill) | Ограниченный |
| Перекрытия | Естественно | Требует position |
| Выравнивание | По двум осям | По главной + поперечной |
| Контент vs контейнер | Контейнер определяет | Контент влияет |
7. Современные возможности
Subgrid — Grid внутри Grid:
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.card {
display: grid;
grid-template-rows: subgrid;
grid-row: span 3;
}
Container Queries с Flexbox:
.card-container {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
flex-direction: row;
}
}
Понимание разницы между Grid и Flexbox и умение их комбинировать — важный навык для создания гибких и поддерживаемых макетов.
Вопрос 11. Назови все известные тебе способы центрирования одного элемента внутри другого.
Таймкод: 00:36:24
Ответ собеседника: Правильный. Назвал text-align, vertical-align, margin: 0 auto, position + transform, Flexbox, Grid, margin: auto в flex/grid, хак с псевдоэлементом.
Правильный ответ:
Отличный ответ, охватывающий практически все основные способы. Разберём каждый подробнее с примерами.
1. Text-align (горизонтальное центрирование)
Работает для inline и inline-block элементов:
.parent {
text-align: center;
}
.child {
display: inline-block; /* или inline */
}
Ограничения:
- Только горизонтальное выравнивание
- Требует изменения display дочернего элемента
- Влияет на весь текстовый контент внутри
2. Vertical-align
Работает в контексте inline элементов и table-cell:
/* Способ 1: с table-cell */
.parent {
display: table-cell;
vertical-align: middle;
height: 300px;
}
/* Способ 2: хак с псевдоэлементом */
.parent {
height: 300px;
text-align: center;
}
.parent::before {
content: '';
display: inline-block;
height: 100%;
vertical-align: middle;
}
.child {
display: inline-block;
vertical-align: middle;
}
Ограничения:
- Не работает для block элементов напрямую
- Поведение зависит от line-height
- Может влиять на соседние inline элементы
3. Margin auto (горизонтальное центрирование)
.child {
width: 300px; /* Обязательно задана ширина */
margin: 0 auto;
}
Ограничения:
- Только горизонтальное
- Требует явную ширину
- Не работает для position: absolute
4. Position + Transform
Универсальный способ для абсолютного позиционирования:
.parent {
position: relative;
height: 300px;
}
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
Вариации:
/* Только горизонтальное */
.child {
position: absolute;
left: 50%;
transform: translateX(-50%);
}
/* Только вертикальное */
.child {
position: absolute;
top: 50%;
transform: translateY(-50%);
}
Ограничения:
- Элемент выпадает из потока
- Может перекрывать другие элементы
- Требует position: relative у родителя
5. Flexbox
Самый популярный современный способ:
/* Центрирование одного элемента */
.parent {
display: flex;
justify-content: center; /* Горизонталь */
align-items: center; /* Вертикаль */
}
/* Центрирование нескольких элементов */
.parent {
display: flex;
justify-content: center;
align-items: center;
gap: 16px;
}
Вариации:
/* Только горизонтальное */
.parent {
display: flex;
justify-content: center;
}
/* Только вертикальное */
.parent {
display: flex;
align-items: center;
}
/* Центрирование одного элемента в группе */
.parent {
display: flex;
}
.child-to-center {
margin: auto; /* Центрирует по обеим осям */
}
6. Grid
Несколько способов с CSS Grid:
/* Способ 1: place-items */
.parent {
display: grid;
place-items: center; /* Сокращение для justify + align */
}
/* Способ 2: отдельные свойства */
.parent {
display: grid;
justify-items: center;
align-items: center;
}
/* Способ 3: для конкретного элемента */
.parent {
display: grid;
}
.child {
justify-self: center;
align-self: center;
}
/* Способ 4: place-self */
.parent {
display: grid;
}
.child {
place-self: center;
}
Margin auto в Grid/Flexbox:
.parent {
display: grid; /* или flex */
}
.child {
margin: auto; /* Центрирует по обеим осям */
}
7. Table-cell
Старый способ, всё ещё работающий:
.parent {
display: table-cell;
vertical-align: middle;
text-align: center;
height: 300px;
width: 500px;
}
.child {
display: inline-block;
}
Ограничения:
- Родитель теряет свойства block элемента
- Нужен обёртывающий элемент с display: table
- Не подходит для сложных макетов
8. Line-height (для одной строки текста)
.parent {
height: 100px;
line-height: 100px; /* Равна высоте контейнера */
text-align: center;
}
Ограничения:
- Только для одной строки текста
- Текст должен быть inline или inline-block
- Не подходит для многострочного контента
9. Writing-mode (экзотический способ)
.parent {
writing-mode: vertical-lr;
text-align: center;
}
.child {
writing-mode: horizontal-tb;
}
10. Сравнительная таблица
| Способ | Горизонталь | Вертикаль | Размер дочернего | Совместимость |
|---|---|---|---|---|
| text-align | ✅ | ❌ | Любой | Все браузеры |
| vertical-align | ❌ | ✅ | inline/table-cell | Все браузеры |
| margin: 0 auto | ✅ | ❌ | Нужна ширина | Все браузеры |
| position + transform | ✅ | ✅ | Любой | IE9+ |
| Flexbox | ✅ | ✅ | Любой | Современные |
| Grid | ✅ | ✅ | Любой | Современные |
| table-cell | ✅ | ✅ | inline-block | Все браузеры |
Рекомендации:
Для современных проектов:
- Flexbox — лучший выбор для большинства случаев
- Grid — когда уже используете Grid для макета
- position + transform — когда нужен overlay или модальное окно
Для поддержки старых браузеров:
- margin: 0 auto — горизонтальное центрирование
- position + transform — полное центрирование
- table-cell — если нужна поддержка очень старых браузеров
Вопрос 12. Какую главную проблему CSS решают CSS Modules и подходы CSS-in-JS?
Таймкод: 00:41:12
Ответ собеседника: Правильный. Главная проблема — коллизии стилей из-за глобальной области видимости. CSS Modules создают локальную область через уникальные имена классов. Также упомянул проблему лишних стилей — компонентный подход позволяет подключать только необходимые стили.
Правильный ответ:
Отличный ответ, который точно определяет ключевую проблему. Разберём тему глубже.
1. Проблема глобальной области видимости
В традиционном CSS все стили глобальны:
/* header.css */
.title {
color: blue;
font-size: 24px;
}
/* sidebar.css */
.title {
color: red; /* Конфликт! */
font-size: 18px;
}
Последствия:
- Стили из разных файлов могут перезаписывать друг друга
- Невозможно удалить неиспользуемые стили безопасно
- Сложно отследить, где используется тот или иной класс
- При рефакторинге высокий риск сломать другие компоненты
2. CSS Modules
Принцип работы:
/* Button.module.css */
.button {
padding: 8px 16px;
border-radius: 4px;
}
.primary {
background: blue;
color: white;
}
// Button.jsx
import styles from './Button.module.css';
function Button({ variant, children }) {
return (
<button className={`${styles.button} ${styles[variant]}`}>
{children}
</button>
);
}
Результат сборки:
<button class="Button_button_x7ks2 Button_primary_a3f9d">
Нажми меня
</button>
.Button_button_x7ks2 {
padding: 8px 16px;
border-radius: 4px;
}
.Button_primary_a3f9d {
background: blue;
color: white;
}
Конфигурация Webpack:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.module\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]__[local]--[hash:base64:5]'
}
}
}
]
}
]
}
};
3. CSS-in-JS подходы
Styled Components:
import styled from 'styled-components';
const Button = styled.button`
padding: 8px 16px;
border-radius: 4px;
background: ${props => props.primary ? 'blue' : 'gray'};
color: white;
&:hover {
opacity: 0.9;
}
`;
// Использование
<Button primary>Нажми меня</Button>
Emotion:
/** @jsx jsx */
import { jsx, css } from '@emotion/react';
const buttonStyle = css`
padding: 8px 16px;
border-radius: 4px;
background: blue;
color: white;
`;
function Button({ children }) {
return <button css={buttonStyle}>{children}</button>;
}
4. Дополнительные преимущества
Динамические стили:
// CSS-in-JS позволяет легко использовать пропсы
const Card = styled.div`
background: ${props => props.theme.background};
padding: ${props => props.compact ? '8px' : '16px'};
display: ${props => props.hidden ? 'none' : 'block'};
`;
Автоматическое удаление неиспользуемого кода:
// Стили создаются только когда компонент рендерится
// Если компонент не используется — стили не попадают в бандл
Типизация с TypeScript:
// CSS Modules с типами
import styles from './Button.module.css';
// TypeScript проверяет существование классов
const className = styles.nonExistent; // Ошибка компиляции
5. Сравнение подходов
| Аспект | CSS Modules | CSS-in-JS | Обычный CSS |
|---|---|---|---|
| Локальность | ✅ | ✅ | ❌ |
| Динамические стили | ❌ | ✅ | ❌ |
| Runtime overhead | Нет | Да | Нет |
| Размер бандла | Минимальный | Больше | Зависит |
| Кривая обучения | Низкая | Средняя | Низкая |
| SSR поддержка | ✅ | С настройкой | ✅ |
6. Проблемы CSS-in-JS
Runtime производительность:
// Стили генерируются в runtime
// Это добавляет нагрузку на каждый рендер
const DynamicButton = styled.button`
color: ${props => props.color}; // Вычисляется каждый раз
`;
Размер бандла:
// Библиотека styled-components ~17kB gzipped
// emotion ~7kB gzipped
// CSS Modules — только стили, без runtime
Решения:
// 1. Использовать zero-runtime решения
import { css } from '@emotion/css'; // Без styled
// 2. Использовать Linaria — CSS-in-JS с извлечением в CSS
import { css } from '@linaria/core';
const title = css`
color: blue;
font-size: 24px;
`;
// Компилируется в обычный CSS при сборке
7. Альтернативные подходы
BEM (Без инструментов):
/* Block Element Modifier */
.button { }
.button--primary { }
.button__icon { }
Utility-first CSS (Tailwind):
<button class="px-4 py-2 bg-blue-500 text-white rounded hover:opacity-90">
Нажми меня
</button>
Shadow DOM (Web Components):
class MyComponent extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
/* Стили изолированы автоматически */
.button { color: blue; }
</style>
<button class="button">Кнопка</button>
`;
}
}
Рекомендации:
- CSS Modules — хороший баланс между простотой и изоляцией
- CSS-in-JS — когда нужна динамика и темизация
- Tailwind — для быстрой разработки и консистентного дизайна
- Обычный CSS + BEM — для простых проектов без сборки
Выбор подхода зависит от требований проекта, размера команды и предпочтений разработчиков.
Вопрос 13. Можно ли с помощью JavaScript менять значения псевдоэлементов (::before, ::after) на лету?
Таймкод: 00:44:05
Ответ собеседника: Неполный. Предположил, что нативно это невозможно. Не знал про CSS-переменные и data-атрибуты как способы управления псевдоэлементами.
Правильный ответ:
Псевдоэлементы нельзя выбрать напрямую через JavaScript, но есть несколько способов управлять их содержимым и стилями.
1. CSS Custom Properties (рекомендуемый способ)
Самый удобный и современный подход:
.element {
--before-content: "По умолчанию";
--before-color: blue;
}
.element::before {
content: var(--before-content);
color: var(--before-color);
}
// Изменение через JavaScript
const element = document.querySelector('.element');
// Изменяем содержимое
element.style.setProperty('--before-content', '"Новый текст"');
// Изменяем цвет
element.style.setProperty('--before-color', 'red');
Пример с кнопкой:
<button class="btn" data-badge="3">Уведомления</button>
.btn {
position: relative;
}
.btn::after {
content: attr(data-badge);
position: absolute;
top: -8px;
right: -8px;
background: red;
color: white;
border-radius: 50%;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
}
// Обновление значения
const btn = document.querySelector('.btn');
btn.dataset.badge = '5'; // Автоматически обновится ::after
2. Data-атрибуты с content: attr()
.tooltip::after {
content: attr(data-tooltip);
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
background: #333;
color: white;
padding: 8px 12px;
border-radius: 4px;
white-space: nowrap;
opacity: 0;
transition: opacity 0.3s;
}
.tooltip:hover::after {
opacity: 1;
}
<button class="tooltip" data-tooltip="Это подсказка">
Наведи на меня
</button>
// Динамическое изменение подсказки
const tooltip = document.querySelector('.tooltip');
tooltip.dataset.tooltip = 'Новая подсказка';
3. Динамическое создание стилей
function setPseudoElementStyle(selector, pseudoElement, styles) {
// Удаляем предыдущие стили если есть
const styleId = `pseudo-${selector.replace(/[^a-z0-9]/gi, '')}`;
let styleEl = document.getElementById(styleId);
if (!styleEl) {
styleEl = document.createElement('style');
styleEl.id = styleId;
document.head.appendChild(styleEl);
}
const cssText = Object.entries(styles)
.map(([prop, value]) => `${prop}: ${value}`)
.join('; ');
styleEl.textContent = `${selector}::${pseudoElement} { ${cssText} }`;
}
// Использование
setPseudoElementStyle('.my-element', 'before', {
content: '"✓"',
color: 'green',
'font-size': '20px'
});
4. CSSOM (CSS Object Model)
Работа с правилами стилей напрямую:
function updatePseudoElementRule(selector, pseudo, property, value) {
// Ищем существующее правило
for (const sheet of document.styleSheets) {
try {
for (const rule of sheet.cssRules) {
if (rule.selectorText === `${selector}::${pseudo}`) {
rule.style[property] = value;
return;
}
}
} catch (e) {
// Пропускаем внешние стили с CORS
}
}
// Если правило не найдено — создаём новое
const style = document.createElement('style');
document.head.appendChild(style);
style.sheet.insertRule(
`${selector}::${pseudo} { ${property}: ${value} }`,
0
);
}
5. Использование CSS-классов
Простой и надёжный способ:
.badge::after {
content: attr(data-count);
/* базовые стили */
}
.badge--hidden::after {
display: none;
}
.badge--large::after {
width: 30px;
height: 30px;
font-size: 16px;
}
.badge--success::after {
background: green;
}
const badge = document.querySelector('.badge');
// Переключаем классы
badge.classList.add('badge--large');
badge.classList.remove('badge--hidden');
6. Практические примеры
Прогресс-бар:
.progress {
--progress: 0%;
height: 8px;
background: #eee;
border-radius: 4px;
position: relative;
}
.progress::after {
content: '';
position: absolute;
left: 0;
top: 0;
height: 100%;
width: var(--progress);
background: linear-gradient(90deg, #4facfe, #00f2fe);
border-radius: 4px;
transition: width 0.3s ease;
}
function setProgress(element, percent) {
element.style.setProperty('--progress', `${percent}%`);
}
const progressBar = document.querySelector('.progress');
setProgress(progressBar, 75);
Анимированная загрузка:
.loading {
--dots: '.';
}
.loading::after {
content: var(--dots);
animation: dots 1.5s steps(4, end) infinite;
}
@keyframes dots {
0% { content: '.'; }
33% { content: '..'; }
66% { content: '...'; }
100% { content: ''; }
}
7. Сравнение способов
| Способ | Плюсы | Минусы |
|---|---|---|
| CSS-переменные | Простота, производительность | Только для значений, не для структуры |
| Data-атрибуты | Нативно, без JS для простых случаев | Только для content |
| Динамические стили | Полный контроль | Сложнее, возможны утечки |
| CSS-классы | Простота, предсказуемость | Нужно заранее определить варианты |
Рекомендации:
- CSS-переменные — лучший выбор для большинства случаев
- Data-атрибуты — для простого изменения текста
- CSS-классы — когда есть конечный набор состояний
- Динамические стили — только когда другие способы не подходят
Псевдоэлементы — мощный инструмент CSS, и умение управлять ими через JavaScript расширяет возможности для создания динамических интерфейсов.
Вопрос 14. Чем отличаются подходы Mobile First и Desktop First, и какой ты предпочитаешь?
Таймкод: 00:45:50
Ответ собеседника: Правильный. Mobile First — сначала мобильная версия, потом расширение. Desktop First — обратный подход, ему удобнее. Отметил минус Mobile First: не видно что будет свёрнуто. Готов делать Mobile First если требуется.
Правильный ответ:
Хорошее понимание обоих подходов. Разберём подробнее.
1. Mobile First
Принцип:
- Базовые стили для мобильных устройств
- Медиа-запросы с
min-widthдля расширения
/* Базовые стили — мобильная версия */
.container {
padding: 16px;
}
.nav {
display: none; /* Скрыта на мобильных */
}
.menu-toggle {
display: block; /* Гамбургер-меню */
}
/* Планшет */
@media (min-width: 768px) {
.container {
padding: 24px;
}
.nav {
display: flex;
}
.menu-toggle {
display: none;
}
}
/* Десктоп */
@media (min-width: 1024px) {
.container {
max-width: 1200px;
margin: 0 auto;
padding: 32px;
}
}
Преимущества Mobile First:
- Приоритизация контента — нужно уместить главное в маленький экран
- Прогрессивное улучшение — базовая функциональность работает везде
- Меньше кода для мобильных (не нужно переопределять стили)
- Лучшая производительность на мобильных (меньше CSS)
- Рост мобильного трафика (более 60% в мире)
Недостатки Mobile First:
- Сложнее представить финальный результат
- Может потребоваться больше кода для десктопа
- Не подходит если основная аудитория — десктоп
2. Desktop First
Принцип:
- Базовые стили для десктопа
- Медиа-запросы с
max-widthдля адаптации
/* Базовые стили — десктоп */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 32px;
}
.nav {
display: flex;
}
.menu-toggle {
display: none;
}
/* Планшет */
@media (max-width: 1023px) {
.container {
padding: 24px;
}
}
/* Мобильный */
@media (max-width: 767px) {
.container {
padding: 16px;
}
.nav {
display: none;
}
.menu-toggle {
display: block;
}
}
Преимущества Desktop First:
- Легче начать — больше пространства для работы
- Полный дизайн виден сразу
- Подходит для B2B и enterprise-приложений
Недостатки Desktop First:
- Регрессивное ухудшение — функциональность может ломаться
- Больше переопределений стилей
- Хуже для производительности на мобильных
3. Сравнение подходов
| Аспект | Mobile First | Desktop First |
|---|---|---|
| Медиа-запросы | min-width | max-width |
| Философия | Прогрессивное улучшение | Регрессивное ухудшение |
| Приоритет контента | Высокий | Средний |
| Размер CSS | Меньше | Больше |
| Сложность старта | Выше | Ниже |
| Производительность | Лучше на мобильных | Хуже на мобильных |
4. Практические рекомендации
Когда выбирать Mobile First:
- Потребительские приложения
- E-commerce с мобильной аудиторией
- Контентные сайты
- Стартапы и MVP
Когда выбирать Desktop First:
- B2B и enterprise-приложения
- Сложные дашборды и админки
- Инструменты для профессионалов
- Проекты где мобильная версия вторична
5. Гибридный подход
Можно комбинировать оба подхода:
/* Mobile First для layout */
.grid {
display: grid;
grid-template-columns: 1fr;
}
@media (min-width: 768px) {
.grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* Desktop First для сложных компонентов */
.complex-widget {
/* Десктоп по умолчанию */
display: grid;
grid-template-columns: 200px 1fr 300px;
}
@media (max-width: 1023px) {
.complex-widget {
grid-template-columns: 1fr;
}
}
6. Container Queries — будущее
Современная альтернатива:
.card-container {
container-type: inline-size;
}
.card {
display: flex;
flex-direction: column;
}
@container (min-width: 400px) {
.card {
flex-direction: row;
}
}
Рекомендации:
- Изучить статистику аудитории проекта
- Начинать с контента, а не с дизайна
- Использовать относительные единицы (rem, em, %)
- Тестировать на реальных устройствах
- Не забывать о touch-взаимодействии (размер тап-зон)
Выбор подхода должен основываться на потребностях пользователей и бизнес-целях проекта, а не на личных предпочтениях разработчика.
