Перейти к основному содержимому

Онлайн-собеседование фронтенд-разработчика: Серёжа Попов и Эйч

· 33 мин. чтения

Сегодня мы разберём учебное собеседование джуниор-фронтенд-разработчика, в ходе которого кандидат Дима продемонстрировал широкий, хотя и поверхностный кругозор — от вёрстки и 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();

Рекомендации по развитию:

Приоритетность изучения может быть такой:

  1. Роутинг Vue — критично для текущей работы
  2. Анимации — углубление в уже имеющийся опыт
  3. 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)Не рекомендуется
block3с ожидание, потом fallbackИконочные шрифты
swapСразу fallback, потом swapТекстовые шрифты
fallback100ms hidden, потом swapКомпромисс
optional100ms 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. Сравнительная таблица

АспектGridFlexbox
Размерность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-aligninline/table-cellВсе браузеры
margin: 0 autoНужна ширинаВсе браузеры
position + transformЛюбойIE9+
FlexboxЛюбойСовременные
GridЛюбойСовременные
table-cellinline-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 ModulesCSS-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 FirstDesktop First
Медиа-запросыmin-widthmax-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-взаимодействии (размер тап-зон)

Выбор подхода должен основываться на потребностях пользователей и бизнес-целях проекта, а не на личных предпочтениях разработчика.