Tailwind v4.0⚡️
Четвертая версия Tailwind окончательно захватила продакшен, и если вы еще не обновились — самое время. Главная фишка релиза — тотальный уход от JavaScript в сторону нативного CSS.
Больше никаких портянок на JS.
Движок Oxide на Rust: Скорость сборки выросла в 10 раз. Стили компилируются буквально за миллисекунды.
Tailwind теперь сам умеет в вендорные префиксы и нативную вложенность.
Четвертая версия Tailwind окончательно захватила продакшен, и если вы еще не обновились — самое время. Главная фишка релиза — тотальный уход от JavaScript в сторону нативного CSS.
Больше никаких портянок на JS.
Движок Oxide на Rust: Скорость сборки выросла в 10 раз. Стили компилируются буквально за миллисекунды.
Tailwind теперь сам умеет в вендорные префиксы и нативную вложенность.
❤1
Облегченные JS-фреймворки
Пока React 19 и Next.js усложняют архитектуру, набирает обороты ренессанс легковесных инструментов (Arrow.js, Kasper). Их главная фишка — отсутствие этапа сборки, простая реактивность на прокси/сигналах и файлы llms.txt в корне.
Разработчики этих фреймворков прямо заявляют: архитектура спроектирована так, чтобы ИИ-ассистенты (вроде Cursor или Copilot) могли писать код без галлюцинаций.
Пока React 19 и Next.js усложняют архитектуру, набирает обороты ренессанс легковесных инструментов (Arrow.js, Kasper). Их главная фишка — отсутствие этапа сборки, простая реактивность на прокси/сигналах и файлы llms.txt в корне.
Разработчики этих фреймворков прямо заявляют: архитектура спроектирована так, чтобы ИИ-ассистенты (вроде Cursor или Copilot) могли писать код без галлюцинаций.
💡 CSS-магия: Свойство field-sizing для адаптивных инпутов
Раньше, чтобы текстовое поле автоматически растягивалось по высоте или ширине в зависимости от введенного пользователем текста, приходилось писать костыли на JavaScript.
Теперь в CSS появилось нативное решение — field-sizing: content;!
Поле изначально имеет минимальную высоту, но как только пользователь начинает печатать и текст не помещается, инпут автоматически растет вниз, пока не упрется в max-height. Без единой строчки JS
Раньше, чтобы текстовое поле автоматически растягивалось по высоте или ширине в зависимости от введенного пользователем текста, приходилось писать костыли на JavaScript.
Теперь в CSS появилось нативное решение — field-sizing: content;!
textarea {
field-sizing: content;
min-height: 40px;
max-height: 200px;
}Поле изначально имеет минимальную высоту, но как только пользователь начинает печатать и текст не помещается, инпут автоматически растет вниз, пока не упрется в max-height. Без единой строчки JS
🔥 CSS Anchor Positioning: Забудьте про тяжелые JS-библиотеки для поповеров и дропдаунов
Раньше для создания всплывающих подсказок, дропдаунов или контекстных меню, которые привязаны к определенной кнопке и не ломаются при скролле, приходилось тащить тяжелые JS-библиотеки вроде
Теперь CSS Anchor Positioning полностью поддерживается всеми современными браузерами и позволяет делать это нативно
Раньше для создания всплывающих подсказок, дропдаунов или контекстных меню, которые привязаны к определенной кнопке и не ломаются при скролле, приходилось тащить тяжелые JS-библиотеки вроде
Popper.js или Floating UI.Теперь CSS Anchor Positioning полностью поддерживается всеми современными браузерами и позволяет делать это нативно
/* 1. Элемент-источник (кнопка) */
.anchor-button {
anchor-name: --my-anchor;
}
/* 2. Всплывающее меню */
.dropdown-menu {
position: absolute;
/* Привязываем топ меню к низу кнопки */
top: anchor(--my-anchor bottom);
/* Выравниваем по левому краю кнопки */
left: anchor(--my-anchor left);
}
💡 Как правильно использовать
Новый хук
Минимум бойлерплейта, максимум производительности.
useActionState в React 19Новый хук
useActionState — лучший способ обрабатывать отправку форм без ручного контроля.// React 19+
const [state, formAction, isPending] = useActionState(
async (prevState, formData) => {
const result = await updateProfile(formData.get("name"));
return result; // Новый state
},
{ success: null } // Дефолтный state
);
return (
<form action={formAction}>
<input name="name" />
<button disabled={isPending}>
{isPending ? 'Сохранение...' : 'Обновить'}
</button>
</form>
);
Минимум бойлерплейта, максимум производительности.
🧹 Удаляем дубликаты из массива в одну строчку
Забудьте про циклы
🛠 Код:
Работает максимально быстро и выглядит очень лаконично.
Забудьте про циклы
for или сложные конструкции с .filter(), если вам нужно просто очистить массив от повторяющихся значений. Для этого есть встроенный объект Set, который хранит только уникальные элементы.🛠 Код:
const numbers = [1, 2, 2, 3, 4, 4, 4, 5];
// Используем Set и спред-оператор (...)
const uniqueNumbers = [...new Set(numbers)];
console.log(uniqueNumbers);
// Вывод: [1, 2, 3, 4, 5]
Работает максимально быстро и выглядит очень лаконично.
👍1
🔍 Как поймать исчезающий
Вы хотите настроить стили для всплывающей подсказки или меню, которое появляется только при наведении мыши. Но стоит увести курсор в панель разработчика, как элемент исчезает
Как зафиксировать это состояние в Chrome/Firefox:
1.Откройте DevTools (F12) и выберите нужный элемент в инспекторе.
2.В правой панели со стилями найдите маленькую кнопку :hov (Toggle Element State).
3.Поставьте галочку напротив
Теперь элемент намертво застынет в этом состоянии
:hoverВы хотите настроить стили для всплывающей подсказки или меню, которое появляется только при наведении мыши. Но стоит увести курсор в панель разработчика, как элемент исчезает
Как зафиксировать это состояние в Chrome/Firefox:
1.Откройте DevTools (F12) и выберите нужный элемент в инспекторе.
2.В правой панели со стилями найдите маленькую кнопку :hov (Toggle Element State).
3.Поставьте галочку напротив
:hover, :focus или :active.Теперь элемент намертво застынет в этом состоянии
⚖️ JS: В чем разница между || и ???
До появления оператора (
Оператор (??) заменяет значение только в том случае, если слева стоит
Используйте (||) для строк или булевых значений, а (??) — когда работаете с числами, где ноль имеет значение.
До появления оператора (
??) все использовали логическое ИЛИ для задания дефолтных значений. Но у ИЛИ есть скрытая проблема: он считает ложными числа 0 и пустые строки:const config = {
volume: 0 // Пользователь выкрутил звук в ноль
};
const currentVolume = config.volume || 50;
console.log(currentVolume); // Выведет 50! Ноль проигнорировался.Оператор (??) заменяет значение только в том случае, если слева стоит
null или undefined.const currentVolume = config.volume ?? 50;
console.log(currentVolume); // Выведет 0. Всё работает корректно!
Используйте (||) для строк или булевых значений, а (??) — когда работаете с числами, где ноль имеет значение.
❤1
🔥 CSS-киллер: Забудь про JavaScript там, где он больше не нужен
Раньше, чтобы изменить стиль родительского элемента при клике или наведении на дочерний, нам приходилось подключать JS и навешивать классы вроде .is-active. Теперь CSS умеет делать это сам благодаря псевдоклассу :has().
JS
CSS
Раньше, чтобы изменить стиль родительского элемента при клике или наведении на дочерний, нам приходилось подключать JS и навешивать классы вроде .is-active. Теперь CSS умеет делать это сам благодаря псевдоклассу :has().
JS
const input = document.querySelector('.input-field');
input.addEventListener('focus', () => {
input.parentElement.classList.add('highlight');
});
CSS
/* Выбираем карточку (.card), ТОЛЬКО если внутри неё есть фокус на инпуте */
.card:has(.input-field:focus) {
border-color: #007bff;
box-shadow: 0 0 10px rgba(0, 123, 255, 0.5);
}
🖼️ Адаптивные картинки без CSS-медиазапросов
Загружать огромную десктопную картинку на мобильном телефоне — грех против оптимизации и скорости загрузки. Вместо кучи классов в CSS, используй правильный HTML-тег <picture>.
⚡ Идеальное решение для адаптивности:
Браузер скачает только один нужный файл, экономя трафик пользователя. А формат
Загружать огромную десктопную картинку на мобильном телефоне — грех против оптимизации и скорости загрузки. Вместо кучи классов в CSS, используй правильный HTML-тег <picture>.
⚡ Идеальное решение для адаптивности:
<picture>
<!-- Если экран смартфона (до 768px), грузим легкую вертикальную фотку -->
<source media="(max-width: 768px)" srcset="mobile-hero.webp">
<!-- Для больших экранов — полноценный баннер -->
<source media="(min-width: 769px)" srcset="desktop-hero.webp">
<!-- Запасной вариант для старых браузеров -->
<img src="desktop-hero.jpg" alt="Главный баннер" loading="lazy">
</picture>
Браузер скачает только один нужный файл, экономя трафик пользователя. А формат
.webp уменьшит вес картинки ещё в пару раз.Куда ты вставляешь тег <script>? Если до сих пор по старинке бросаешь его в самый низ тега <body>, пора это менять. Современный стандарт — подключать скрипты в <head>, но с правильными атрибутами.
💡 Коротко разница:
1️⃣ Обычный <script src="...">
Блокирует чтение HTML. Браузер видит скрипт, останавливает загрузку страницы, скачивает его, выполняет, и только потом строит сайт дальше. Привет, тормоза.
2️⃣ <script async src="...">
Загружается параллельно с HTML. Как только скачался — HTML замирает, и скрипт выполняется. Отлично подходит для независимых вещей: метрики, аналитика, пиксели соцсетей.
3️⃣ <script defer src="...">
Загружается в фоновом режиме, вообще не мешая HTML. Выполняется строго после того, как вся страница полностью загрузилась. Идеально для основного кода твоего приложения.
⚡ Как делать правильно:
💡 Коротко разница:
1️⃣ Обычный <script src="...">
Блокирует чтение HTML. Браузер видит скрипт, останавливает загрузку страницы, скачивает его, выполняет, и только потом строит сайт дальше. Привет, тормоза.
2️⃣ <script async src="...">
Загружается параллельно с HTML. Как только скачался — HTML замирает, и скрипт выполняется. Отлично подходит для независимых вещей: метрики, аналитика, пиксели соцсетей.
3️⃣ <script defer src="...">
Загружается в фоновом режиме, вообще не мешая HTML. Выполняется строго после того, как вся страница полностью загрузилась. Идеально для основного кода твоего приложения.
⚡ Как делать правильно:
<head>
<!-- Основная логика сайта: загрузится в фоне, выполнится вовремя -->
<script defer src="app.js"></script>
<!-- Яндекс.Метрика или Google Analytics: не ждет никого -->
<script async src="analytics.js"></script>
</head>
❤1
⚡️ Как победить «прыгающий» скроллбар в CSS?
Бывало такое: переходишь между страницами, на одной контента мало (скроллбара нет), на другой много (скроллбар появляется), и весь интерфейс неприятно дергается в сторону?
Бывало такое: переходишь между страницами, на одной контента мало (скроллбара нет), на другой много (скроллбар появляется), и весь интерфейс неприятно дергается в сторону?
html {
/* Резервирует место под скроллбар заранее, предотвращая сдвиг контента */
scrollbar-gutter: stable;
}
Браузер сразу оставляет пустой трек под полосу прокрутки. Когда она появляется, контент никуда не смещается.🧬 Хватит использовать JSON.parse(JSON.stringify()) для глубокого клонирования
Когда нужно сделать полную копию сложного объекта (со всеми вложенностями), старый трюк с JSON часто ломает данные: он не умеет копировать даты (Date), регулярные выражения (RegExp), карты (Map/Set) и циклические ссылки.
В современном JS есть встроенный инструмент, который делает это правильно и быстро — structuredClone.
Когда нужно сделать полную копию сложного объекта (со всеми вложенностями), старый трюк с JSON часто ломает данные: он не умеет копировать даты (Date), регулярные выражения (RegExp), карты (Map/Set) и циклические ссылки.
В современном JS есть встроенный инструмент, который делает это правильно и быстро — structuredClone.
const user = {
name: "Alex",
joined: new Date(),
skills: new Set(["JS", "React"]),
};
// ❌ Старый костыльный метод (дата превратится в строку, Set просто исчезнет)
const badCopy = JSON.parse(JSON.stringify(user));
// ✅ Современный нативный метод (все типы данных сохраняются!)
const goodCopy = structuredClone(user);
console.log(goodCopy.joined instanceof Date); // true
console.log(goodCopy.skills); // Set(2) {"JS", "React"}⚡️ Как ускорить рендеринг длинных страниц одной строчкой CSS
Если у вас на странице много контента (лента постов, длинные таблицы, каталоги товаров), браузер тратит кучу ресурсов на расчет макета и отрисовку даже тех элементов, которые находятся глубоко за пределами экрана.
Свойство content-visibility: auto указывает браузеру пропустить рендеринг элементов до тех пор, пока они не окажутся близко к области видимости.
Если у вас на странице много контента (лента постов, длинные таблицы, каталоги товаров), браузер тратит кучу ресурсов на расчет макета и отрисовку даже тех элементов, которые находятся глубоко за пределами экрана.
Свойство content-visibility: auto указывает браузеру пропустить рендеринг элементов до тех пор, пока они не окажутся близко к области видимости.
.card-item {
/* Браузер не рендерит содержимое карточки, пока она вне экрана */
content-visibility: auto;
/* Задаем примерную высоту, чтобы скроллбар не прыгал */
contain-intrinsic-size: 0 300px;
}🚀 Прекратите использовать Array.includes() внутри циклов
Типичная ошибка при работе с большими массивами на фронтенде — поиск элементов через .includes() или .find() внутри фильтрации.
Замена массива на Set с операцией .has() ускоряет фильтрацию больших списков в десятки раз за счет константного времени поиска $O(1)$
Типичная ошибка при работе с большими массивами на фронтенде — поиск элементов через .includes() или .find() внутри фильтрации.
const blockedUserIds = [102, 405, 981, ...10000_items];
const allUsers = [...50000_items];
// ❌ ПЛОХО: O(N * M) — массив фильтруется очень долго
const activeUsers = allUsers.filter(user =>
!blockedUserIds.includes(user.id)
);
// ✅ БЫСТРО: O(N) — переводим список в Set
const blockedSet = new Set(blockedUserIds);
const activeUsers = allUsers.filter(user =>
!blockedSet.has(user.id) // Хеш-таблица: поиск за O(1)
);
Замена массива на Set с операцией .has() ускоряет фильтрацию больших списков в десятки раз за счет константного времени поиска $O(1)$
⚡️ CSS: Забудь про JavaScript для отслеживания размера элементов
Раньше для адаптива компонентов в зависимости от размера их контейнера приходилось подключать ResizeObserver на JS. Теперь это чистое CSS-решение.
Виджет/карточка будет идеально выглядеть и в узкой боковой колонке, и во всю ширину экрана без единой строчки JS-кода.
Раньше для адаптива компонентов в зависимости от размера их контейнера приходилось подключать ResizeObserver на JS. Теперь это чистое CSS-решение.
/* 1. Объявляем контейнер */
.card-wrapper {
container-type: inline-size;
}
/* 2. Верстаем внутри карточки с привязкой к ее ширине */
.card-title {
/* 5cqw = 5% от ширины КОНТЕЙНЕРА, а не экрана */
font-size: clamp(1rem, 5cqw, 2rem);
}
/* Адаптируем сетку карточки по ее собственному размеру */
@container (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 1fr 2fr;
}
}
Виджет/карточка будет идеально выглядеть и в узкой боковой колонке, и во всю ширину экрана без единой строчки JS-кода.
🎯 TypeScript: Забудь про as — используй Type Guards для безопасной типизации
Использование as SomeType (Type Assertion) отключает проверку типов и приводит к багам в рантайме. Пиши кастомные предикаты типов
Использование as SomeType (Type Assertion) отключает проверку типов и приводит к багам в рантайме. Пиши кастомные предикаты типов
interface User { id: number; name: str; }
interface Guest { isGuest: true; }
type Account = User | Guest;
# ❌ Опасно (компилятор поверит на слово, а в рантайме может быть undefined):
function getNameUnsafe(account: Account) {
return (account as User).name;
}
# ✅ Безопасно через Type Guard (возвращаемый тип `account is User`):
function isUser(account: Account): account is User {
return 'name' in account;
}
function getNameSafe(account: Account) {
if (isUser(account)) {
return account.name; // TS автодополнением видит, что это User
}
return "Гость";
}🛠 JS / API: AbortController — отмена запросов при размонтировании компонента
Если пользователь переключает табы или быстро вводит текст в инпут, старые фетч-запросы продолжают лететь по сети и могут затереть актуальные данные
Если пользователь переключает табы или быстро вводит текст в инпут, старые фетч-запросы продолжают лететь по сети и могут затереть актуальные данные
let currentController = null;
async function searchData(query) {
// Отменяем предыдущий запрос, если он еще выполняется
if (currentController) {
currentController.abort();
}
currentController = new AbortController();
try {
const res = await fetch(`/api/search?q=${query}`, {
signal: currentController.signal // Передаем сигнал отмены
});
const data = await res.json();
renderResults(data);
} catch (err) {
if (err.name === 'AbortError') {
console.log('Запрос отменен, все в порядке');
}
}
}
💡 JS: Удобная группировка массивов без сторонних библиотек
Забудь про громоздкие .reduce() или подключение lodash.groupBy только ради одной операции. В современном JS группировка уже встроена
Чистый, читаемый код без внешних зависимостей.
Забудь про громоздкие .reduce() или подключение lodash.groupBy только ради одной операции. В современном JS группировка уже встроена
const products = [
{ name: "MacBook", category: "electronics" },
{ name: "T-Shirt", category: "clothes" },
{ name: "iPhone", category: "electronics" }
];
// Группируем по ключу category:
const grouped = Object.groupBy(products, product => product.category);
console.log(grouped);
/*
{
electronics: [{ name: "MacBook", ... }, { name: "iPhone", ... }],
clothes: [{ name: "T-Shirt", ... }]
}
*/
Чистый, читаемый код без внешних зависимостей.
@starting-style: Анимации при первом рендере без JS
Раньше, чтобы плавно анимировать появление элемента, который только что добавили в DOM, приходилось изворачиваться через requestAnimationFrame, setTimeout или тяжелые JS-библиотеки.
Теперь во всех современных браузерах работает нативный метод @starting-style
Раньше, чтобы плавно анимировать появление элемента, который только что добавили в DOM, приходилось изворачиваться через requestAnimationFrame, setTimeout или тяжелые JS-библиотеки.
Теперь во всех современных браузерах работает нативный метод @starting-style
/* Исходное состояние элемента в DOM */
.dialog {
opacity: 1;
transform: scale(1);
transition: opacity 0.3s, transform 0.3s;
}
/* Свойства, из которых НАЧИНАЕТСЯ анимация при монтировании */
@starting-style {
.dialog {
opacity: 0;
transform: scale(0.9);
}
}