Вебыч
124 subscribers
38 photos
2 videos
14 links
Мир фронтенд разработки, и не только!
Download Telegram
🚀 Как сделать виртуальный скролл на чистом CSS?

Все привыкли: если нужно вывести список из 10 000 элементов, мы тянем тяжелые JS-библиотеки вроде react-window или react-virtualized.

Но в CSS уже есть нативная альтернатива, которая делает рендеринг огромных списков почти мгновенным.

Разбираемся, как работает content-visibility и в чем подвох 👇

──

🤔 Как это работает?

Обычно браузер отрисовывает (расчитывает layout и paint) все элементы на странице сразу после загрузки, даже если они находятся в самом низу, далеко за пределами экрана. Это «вешает» процессор.

Свойство content-visibility: auto включает «ленивый рендеринг» для любого HTML-элемента.

Браузер буквально «пропускает» отрисовку элемента и расчет его геометрии, пока пользователь до него не доскроллит.

──

😬 Проблема прыгающего скролла

Если браузер не рисует элемент, он считает его высоту равной 0.
Из-за этого высота всей страницы кажется маленькой, а ползунок скролла — огромным.

Как только вы начинаете листать, элементы «распаковываются», страница резко удлиняется, и скроллбар начинает дико скакать.

Чтобы этого не было, нужно использовать свойство: contain-intrinsic-size.

──

Пример

.list-item {
/* 1. Не рисуем элемент, пока он вне экрана */
content-visibility: auto;

/* 2. Задаем примерную высоту (40px) */
/* auto позволяет браузеру запомнить реальный размер после первого рендера */
contain-intrinsic-size: auto 40px;
}


Этого достаточно, чтобы список из 50 000 строк перестал тормозить при загрузке.

──

🤷‍♂️ Но есть подвох (White Flashing)

Это не полноценная замена JS-виртуализации.

При очень быстром скролле (особенно на слабых устройствах) вы можете увидеть белые пустые места. Браузер просто не успевает «проснуться» и отрисовать контент вовремя.

В JS-библиотеках есть параметр overscan (рендерить заранее с запасом), в CSS его пока нет.

──

👏 Что и когда использовать?

▪️ CSS (content-visibility):
— Длинные статьи, ленты новостей, списки комментариев.
— Когда важна скорость загрузки (First Contentful Paint).
— Когда «белые вспышки» при бешеном скролле не критичны.

▪️ JS (Virtual Scroll):
— Сложные таблицы данных (Data Grids).
— Когда нужен идеальный UX без мелькания пустоты.
— Когда элементы имеют сложную зависимую логику.

──

🌐 Поддержка браузерами

content-visibility поддерживается всеми современными браузерами (глобальная поддержка — 91%).

▫️ Chrome 85
▫️ Safari 18
▫️ Firefox 125

──

Вывод

content-visibility: auto — это, пожалуй, самый простой способ ускорить рендеринг страницы одной строчкой кода.

Это не полная замена виртуализации, но для 90% типичных задач (вроде бесконечной ленты) работает отлично.

──

👉 Пример на CodePen:
https://codepen.io/webeach/pen/ZYWmxZa

#frontend #css #javascript
7
🤔 Как правильно клонировать массив в JavaScript?

В этом посте мы поговорим о том, как оптимальнее всего поверхностно склонировать массив.

У нас есть 3 самых популярных способа сделать это:

▫️ через spread → [...arr]
▫️ через concat → [].concat(arr)
▫️ через slice → arr.slice()

Мы провели замеры клонирования массива на 100к элементов в 1000 итераций на каждом браузерном движке, используя обычный console.time().

Наш первичный код:

const sourceArray = Array(100_000).fill(0); 
// создаем массив на 100к элементов и заполняем "нулями"


Код теста:

// spread
for (let i = 0; i < 1_000; i++) {
([...sourceArray]);
}

// concat
for (let i = 0; i < 1_000; i++) {
[].concat(sourceArray);
}

// slice
for (let i = 0; i < 1_000; i++) {
sourceArray.slice();
}


Вокруг каждого цикла вызываем console.time() и console.timeEnd().

⚙️ Железо: MacOS 15.6 / M4 Pro

──

🏁 Что показали тесты в браузерах:

🔹 Chrome (V8)

▫️ spread → ~1120ms
▫️ concat → ~44ms
▫️ slice → ~41ms

Spread медленнее, чем concat и slice, аж в 27 раз!
Почему? Потому что при клонировании через spread V8 создает итератор, проходится по каждому элементу и добавляет его в новый массив. Slice же использует быструю копию памяти.

🔹 Firefox (SpiderMonkey)

▫️ spread → ~366ms
▫️ concat → ~50ms
▫️ slice → ~18ms

SpiderMonkey справился быстрее всех!
Это значит, что он не копировал данные физически. Он использовал механизм COW (Copy-on-Write) или "ленивые массивы". Он просто создал "ссылку" на старые данные и сказал: "Скопирую по-настоящему только тогда, когда ты захочешь что-то изменить".

🔹 Safari (JavaScriptCore)

▫️ spread → ~300ms
▫️ concat → ~1055ms
▫️ slice → ~1800ms

В Safari (JavaScriptCore) явно есть специальная оптимизация для оператора Spread. Инженеры Apple видят, что весь современный фронтенд написан на спредах, и оптимизировали именно этот путь.
А вот slice в Safari на больших объемах работает ужасно. Скорее всего, Safari честно аллоцирует новую память и копирует данные, в то время как V8 и Firefox используют хитрости.

──

💁‍♂️ Так какой способ выбрать?

На простых задачах разницы нет никакой, вы просто её не почувствуете.

Но если вы работаете с большими данными (миллионы элементов), то:
▪️ Есть смысл использовать старый-добрый slice() (быстрее везде, кроме Safari).
▪️ Либо написать функцию-утилиту, которая проведет микро-тест при запуске и выберет оптимальный метод для браузера пользователя (пример).

#frontend #javascript
1👍62
🔥 Представляю всем мою новую библиотеку небольшого расширения React

react-x — небольшая либа, которая прокачивает JSX для работы с CSS-классами, стилями и CSS-переменными без лишнего шума, хаков и кастомных хелперов.

Вдохновился svelte 🙃

──

🤔 Зачем это нужно?

Если ты устал от:

▫️ clsx(className, condition && styles.container)
▫️ сложных style объектов
▫️ трудной работы с css-переменными при разработке UI-компонентов

…то react-x закрывает эти боли прямо на уровне JSX.

──

Возможности

▪️ Удобная работа с классами
<x.div
class:container
class:disabled={isDisabled}
/>

// или
<x.div
classList={['container', isDisabled && 'disabled']}
/>


▪️ Работа с inline-стилями теперь проще и читабельнее:
<x.div
style:backgroundColor="blue"
style:width={100}
style:height={100}
/>


▪️ CSS-переменные больше не "боль"
<x.div
class:container
var:bg="blue"
var:size="100px"
/>


в стилях:
.container {
background-color: var(--bg);
width: var(--size);
height: var(--size);
}


──

📦 Установка

npm install @webeach/react-x


──

📚 Документация

Полная документация и примеры доступны на английском и на русском языках:
👉 https://github.com/webeach/react-x

#frontend #javascript #react
1🔥4
🤔 Почему Math.random() опасен?

Все знают фразу: «не используй Math.random() для безопасности».
Но обычно никто не объясняет почему именно.

Давайте разберем конкретный механизм и реальный сценарий проблемы.

──

💠 Как Math.random() работает под капотом

В большинстве окружений Node.js это движок V8.
И начиная примерно с 2015 года V8 использует PRNG-алгоритм xorshift128+.

Он хранит внутреннее состояние из двух 64-битных чисел:

▫️ state0
▫️ state1

Каждый вызов Math.random():

1) обновляет состояние
2) возвращает число, построенное на этом состоянии

Упрощённо это выглядит так:
let state0 = 1n; // неизвестно
let state1 = 2n; // неизвестно

function xorshift128plus() {
let s1 = state0
let s0 = state1

state0 = s0

s1 ^= (s1 << 23n) & 0xFFFFFFFFFFFFFFFFn
s1 ^= s1 >> 17n
s1 ^= s0
s1 ^= s0 >> 26n

state1 = s1

return (state0 + state1) & 0xFFFFFFFFFFFFFFFFn
}


❗️ Math.random() — это не «настоящая случайность», а всего лишь псевдослучайный генератор.

──

🫠 Так где же проблема?

Проблема в том, что если злоумышленник "разгадает":

▫️ state0
▫️ state1

…то он сможет предсказывать все будущие Math.random()

То есть «рандом» превращается в предсказуемую последовательность.

──

🧩 Разве можно восстановить состояние?

Да. И это самое неприятное.

Если злоумышленнику известны несколько последних значений Math.random(), то он может восстановить внутреннее состояние через SMT-солвер (например, Z3 Microsoft Research).

Идея такая:

1) мы берем последовательность значений Math.random()
2) подаем её в солвер
3) просим найти такие state0/state1, которые могли сгенерировать эту последовательность

Если солвер находит решение — всё, поток случайных чисел сломан.

──

🔻 Мини-демо

Я сделал небольшой скрипт на python, который предсказывает Math.random()

👉 https://github.com/webeach-learn/math-random-predictor

Сгенерируйте 5 значений на Node.js:
console.log([...Array(5)].map(() => Math.random()))


И вставьте в скрипт main.py.

Запустите скрипт и он предскажет будущий Math.random() вашего Node.js процесса.

──

😱 Реальный сценарий атаки

Представим типичный функционал сброса пароля на сайте:

Сайт генерирует токен так:
const token = Math.random().toString(36).slice(2)


И отправляет пользователю ссылку:
https://some-site.com/reset-password?token=...


Теперь злоумышленник делает следующее:

1️⃣ отправляет 5 запросов на сброс пароля (на свою почту)

2️⃣ получает 5 токенов, сгенерированных сервером

3️⃣ восстанавливает внутреннее состояние Math.random()

const sourceRandom = parseInt(hash, 36) / Math.pow(36, hash.length)


4️⃣ предсказывает следующие токены, которые сервер сгенерирует уже для других пользователей

В итоге злоумышленник может перехватить чужой сброс пароля и забрать чужой аккаунт.

──

Вывод

Для токенов, паролей, reset-ссылок, session-id и всего «секьюрного» используйте криптографический генератор:

import { randomBytes } from 'node:crypto';

const token = randomBytes(32).toString('hex');


Math.random() используйте только на клиенте, например, для каких-либо "случайных" значений в UI.

──

⚠️ Важно

Эта информация предоставлена только в образовательных целях — не используйте подобные техники для взлома, эксплуатации уязвимостей или получения доступа к чужим аккаунтам. Используйте это знание, чтобы делать свои системы безопаснее.

#frontend #javascript #security
16👍4
🧐 using элегантная замена try...finally?

Если вы когда-нибудь работали с сокетами, слушателями событий или таймерами, вы знаете эту боль: открыл ресурс — будь добр закрой его в блоке finally. Иначе получите утечку памяти.

Но начиная со стандарта ES2025 появилось ключевое слово using, которое решает эту проблему элегантно и без лишнего кода.

──

👎 Как мы писали раньше:

const file = openFile('data.txt');
try {
file.write('hello');
} finally {
file.close(); // рутина, которую легко забыть
}


👍 Как пишем теперь:

function writeData() {
using file = openFile('data.txt');
file.write('hello');
} // При выходе из функции файл закроется САМ!


──

🧩 Как это работает под капотом?

Никакой магии. Когда переменная, объявленная через using, выходит из области видимости (заканчивается блок { ... }), то движок автоматически вызывает у этого объекта специальный метод — [Symbol.dispose]().

class TempLogger {
constructor() {
this.timer = setInterval(() => console.log('tick'), 1000);
}

[Symbol.dispose]() {
clearInterval(this.timer);
console.log('Очищено!');
}
}

{
using logger = new TempLogger();
// ... делаем дела
} // тут в консоль упадет 'Очищено!' и таймер умрет


──

🤔 А если очистка асинхронная?

Для баз данных или веб-сокетов есть await using. Он ищет метод [Symbol.asyncDispose]() и честно ждёт (await), пока ресурс закроется, прежде чем отпустить поток дальше.

async function dbWork() {
await using tx = new Transaction();
await tx.execute('UPDATE...');

// Если тут упадет ошибка, транзакция сама откатится перед выходом!
}


──

🫡 На практике

Пока что фича совсем свежая. В старых браузерах понадобится полифил.

Но если вы пишете на TypeScript (начиная с версии 5.2) — эта магия уже доступна из коробки!
Чтобы всё заработало, достаточно добавить одну настройку в ваш tsconfig.json:

{
"compilerOptions": {
"lib": ["esnext.disposable"]
}
}


Для Node.js серверов это вообще абсолютный мастхэв, который делает код чище раза в два и страхует от типичных ошибок 😌

──

⚠️ Важный нюанс

Переменная, объявленная через using, строго иммутабельна и ведёт себя как const. Вы не можете случайно её переопределить в процессе:

using db = connect();
db = newConnection(); // TypeError: Assignment to constant variable.


#frontend #javascript #typescript
6
🔥 Представляю всем ECSS — Extended CSS

Новый язык, который расширяет CSS тремя конструкциями для декларативного управления состояниями компонентов прямо в файлах стилей.

А точнее — полноценную экосистему:
Сайт документации: https://ecss.webea.ch
Исходники (Rust-парсер, Vite-плагин, TS-плагин и расширение): https://github.com/webeach/ecss

──

Зачем нужен ECSS?

Вы наверняка уставали писать className={clsx(styles.button, variant === 'primary' && styles.primary)}. Логика состояний размазывается между JS и CSS. Добавление варианта заставляет править оба файла.

В ECSS логика остаётся в стилях, а в компонентах нет JS-классов:

@state-variant Variant {
values: primary, danger;
}

@state-def Button(--variant Variant: primary, --disabled boolean) {
padding: 8px 16px;

@if (--disabled) {
opacity: 0.5;
pointer-events: none;
}
@if (--variant == primary) {
background: blue;
}
}


А в компоненте вы просто передаёте пропсы (работает с React, Vue, Svelte, SolidJS):

import styles from './Button.ecss'

<button {...styles.Button({ variant: 'primary' })}>


──

🧠 Философия: новый уровень Separation of Concerns

С архитектурной точки зрения, ECSS предлагает переосмыслить сам интерфейс взаимодействия между логикой и представлением.

Вместо того чтобы заставлять компонент управлять визуальным «автоматом состояний», вы переносите ответственность туда, где она и должна быть — в стили. Вся логика вида «если состояние X, то вид Y» теперь инкапсулирована внутри .ecss файла через директивы @state-def и @if.

Компонент перестает быть контроллером визуальных состояний и превращается в простого поставщика данных.

Для архитектора это означает:
— Радикальное снижение когнитивной нагрузки при чтении кода.
— Упрощение юнит-тестирования.
— Компоненты становятся «чище», так как из них исчезает мусорная логика, относящаяся исключительно к визуализации.

──

💎 Фичи и инструменты

Написано на Rust: супер-быстрый парсер (napi-rs).
Без JS в компонентах: .ecss файлы компилируются в чистый CSS + крошечные функции-фабрики. Никакого рантайм-оверхеда.
Полная типизация: @ecss/typescript-plugin на лету генерирует строгие типы для ваших стилей прямо в IDE.
VS Code расширение: подсветка синтаксиса, диагностика ошибок и подсказки типов при наведении.

──

📦 Установка

Для проекта на Vite:
npm install -D @ecss/vite-plugin @ecss/typescript-plugin


──

⚛️ Пример использования в React

import styles from './Button.ecss';

export function Button({ variant, disabled, children }) {
return (
<button {...styles.Button({ variant, disabled })}>
{children}
</button>
);
}


──

🟢 Пример использования в Vue

<script setup lang="ts">
import styles from './Button.ecss'

const { variant, disabled } = defineProps<{
variant?: 'primary' | 'danger'
disabled?: boolean
}>()
</script>

<template>
<button v-bind="styles.Button({ variant, disabled })">
<slot />
</button>
</template>


──

📚 Документация

Полная спецификация языка и руководства по интеграции со всеми популярными фреймворками (на русском и английском):
👉 https://ecss.webea.ch

──

⚠️ Это пока что первая альфа-версия, возможно будут изменения в синтаксисе.

#css #frontend #react #vue #svelte #solidjs #typescript
11🔥3🤯1
🛡 Представляю dist-guard — сканер секретов для бандлов

Инструмент, который проверяет ваши собранные файлы (dist, build) на утечки токенов, API-ключей, паролей и приватных данных — прямо перед деплоем.

──

🤔 Зачем это нужно?

Бандлеры (Vite, Webpack, Rollup) иногда тянут в production-сборку то, что не должно туда попасть. Переменные окружения, жёстко прописанные ключи, строки подключения к базам данных, локальные пути разработчика — всё это порой оказывается в dist/index.js в открытом виде.

dist-guard запускается после сборки и завершается с кодом 1, если находит что-то подозрительное — не давая деплою пройти.

──

💎 Что умеет

Проверяет более 50 типов утечек:
▪️ SaaS-токены: Stripe, GitHub, Slack, OpenAI, Figma, Supabase...
▪️ Cloud-ключи: AWS, GCP, Azure, DigitalOcean...
▪️ SSH/RSA/PGP приватные ключи
▪️ Строки подключения: PostgreSQL, MongoDB, MySQL, Redis...
▪️ Локальные пути разработчика: /Users/yourname/, C:\Users\yourname\

Найденные секреты автоматически маскируются в выводе, чтобы не засветить их в логах CI/CD.

──

🚀 Быстрый старт

npx @webeach/dist-guard


──

🔧 Использование

Добавьте в npm scripts — запускается после сборки, блокирует деплой при утечке:

{
"scripts": {
"build": "vite build",
"scan": "dist-guard",
"release": "npm run build && npm run scan && npm run deploy"
}
}


Или в GitHub Actions:

- name: Security Scan
run: pnpm dist-guard


──

⚙️ Конфигурация

// package.json
{
"distGuard": {
"include": ["{dist,build}/**/*.*"],
"exclude": ["**/*.map"],
"ignoreRules": ["GenericAPIKey"]
}
}


──

Подробное описание и документация в репозитории:
https://github.com/webeach/dist-guard

#security #nodejs #cli #devtools #frontend #typescript
🔥3
😅 Что такое «миры» (realms) в JavaScript?

По сути, «мир» (realm) в JS — это отдельная область выполнения кода. Она может существовать внутри вкладки/окна браузера или, например, внутри iframe.

Но иногда эти «миры» сталкиваются 💥

Давайте разберёмся на реальных примерах 👇

──

🔸 Айфреймовое проникновение

// создаём iframe
const iframe = document.createElement('iframe');

document.body.append(iframe);

// объект window из iframe
const iframeWindow = iframe.contentWindow;


Теперь попробуем сравнить любые объекты/функции из iframe с текущим окном:

console.log(window.Array === iframeWindow.Array);
console.log(window.Object === iframeWindow.Object);
console.log(window.setTimeout === iframeWindow.setTimeout);


Всё будет false, потому что iframe — это отдельный контекст выполнения JS, и все сущности там лежат в другой области памяти.

Следовательно, все созданные инстансы тоже не будут корректно сравниваться:

// создаём объект внутри iframe
iframeWindow.eval('window.testObject = {};');

// получаем ссылку на него
const testObject = iframeWindow.testObject;

// проверяем принадлежность к Object
console.log(testObject instanceof Object);


Здесь тоже будет false, потому что мы проверяем объект, созданный в другом окружении, через Object из текущего.

──

🔹 Реальный пример

Представим, что на странице есть виджет, подключённый через iframe (в рамках того же домена):

<iframe class="my-widget" src="/widgets/my-widget.html"></iframe>


На основной странице есть конфиг, сгенерированный бэкендом:

<script>
window.CONFIG = {
data: {},
entities: [{ name: 'one', value: 1 }],
};
</script>


Внутри виджета мы получаем этот конфиг через родительское окно:

const rootConfig = window.top.CONFIG;


Наша задача — пройтись по массиву entities и отрисовать список:

rootConfig.entities.map((item) => { ... });


Но разработчик не доверяет бэкенду и добавляет проверку:

if (rootConfig.entities instanceof Array) {
// ...
}


Как думаете, выполнится ли условие? 😌

Ответ — НИКОГДА. Массив создан в другом окружении, а Array — из текущего.

Именно поэтому instanceof Array считается плохой практикой. Вместо этого нужно использовать Array.isArray:

if (Array.isArray(rootConfig.entities)) {
// ...
}


Array.isArray работает корректно независимо от realm.

──

🔹 Ещё пример

Та же проблема возникает не только с iframe, но и с окнами.

Представим, что мы открываем новое окно через window.open(...), где происходит авторизация:

// основная вкладка
window.SOME_DATA = {};

const authWindow = window.open();


// код в новом окне
const data = window.opener.SOME_DATA;

if (data instanceof Object) {
// никогда не выполнится
}


──

🤔 А как быть с объектами?

Если для массивов есть Array.isArray, то для объектов универсального метода нет. Это связано с тем, что понятие «объект» в JS довольно размыто.

Но можно написать свою проверку:

function isPlainObject(object) {
if (object !== null && typeof object === 'object') {
const proto = Object.getPrototypeOf(object);

return (
// обычный объект из текущего контекста
proto === Object.prototype ||
// объект без прототипа (Object.create(null))
proto === null ||
// объект из другого realm (например, iframe)
// проверяем, что его прототип — базовый Object.prototype того мира
Object.getPrototypeOf(proto) === null
);
}

return false;
}



#javascript #frontend
15
😌 Что такое CSS FlexBox?

По сути, FlexBox — это система раскладки элементов в CSS, которая позволяет удобно управлять расположением блоков внутри контейнера.

Раньше для этого приходилось использовать float, таблицы, хаки и страдания 😭

Но потом появился FlexBox — и верстать интерфейсы стало намного проще

Давайте разберёмся на практике 👇

──

🔹 Включаем FlexBox

Допустим, у нас есть контейнер с карточками:

<div class="container">
<div class="item">One</div>
<div class="item">Two</div>
<div class="item">Three</div>
</div>


По умолчанию элементы будут идти сверху вниз:

.item {
border: 1px solid black;
}


Но если добавить:

.container {
display: flex;
}


то элементы сразу выстроятся в одну линию 😌

Потому что display: flex превращает элемент в flex-контейнер, а его дочерние элементы становятся flex-элементами.

──

🔹 Главная ось (flex-direction)

У FlexBox всегда есть главная ось (main axis).

По умолчанию она направлена слева направо:

.container {
display: flex;
flex-direction: row;
}


Но направление можно поменять:

.container {
flex-direction: column;
}


И тогда элементы будут идти сверху вниз.

Есть и другие варианты:

flex-direction: row-reverse;
flex-direction: column-reverse;


Они просто переворачивают порядок элементов.

──

🔹 Выравнивание по главной оси (justify-content)

Теперь представим, что контейнер стал шире, чем элементы внутри.

Как распределить свободное место? 🤔

Для этого используется:

.container {
justify-content: center;
}


Элементы окажутся по центру.

Другие популярные значения:

justify-content: flex-start;
justify-content: flex-end;
justify-content: space-between;
justify-content: space-around;
justify-content: space-evenly;


💡 justify-content работает вдоль главной оси.

Если flex-direction: column, то выравнивание будет уже по вертикали.

──

🔹 Выравнивание по поперечной оси (align-items)

Теперь поговорим про вторую ось — cross axis.

Если главная ось идёт слева направо, то поперечная — сверху вниз.

Для управления выравниванием используется:

.container {
align-items: center;
}


Элементы будут выровнены по центру поперечной оси.

Другие значения:

align-items: flex-start;
align-items: flex-end;
align-items: stretch;


💡 align-items работает поперёк главной оси.

Это очень важно 😄

──

🔹 Перенос элементов (flex-wrap)

По умолчанию FlexBox пытается уместить всё в одну строку.

Из-за этого элементы могут начать сжиматься:

.container {
display: flex;
}


Чтобы разрешить перенос строк:

.container {
flex-wrap: wrap;
}


Теперь элементы смогут переноситься на новую строку.

──

🔹 Размеры элементов (flex-grow, flex-shrink, flex-basis)

flex-grow отвечает за коэффициент расширения элемента, если в контейнере есть свободное место.

По умолчанию flex-grow: 0, поэтому элементы сами по себе не растягиваются.

flex-shrink отвечает за коэффициент сжатия элемента, если места не хватает.

По умолчанию flex-shrink: 1, поэтому элементы могут сжиматься.

А flex-basis задаёт базовый размер элемента до распределения свободного пространства.

Например:

.item:nth-child(1) {
flex-grow: 2;
}

.item:nth-child(2) {
flex-grow: 1;
}

.item:nth-child(3) {
flex-grow: 1;
}


В таком случае первый элемент (One) получит в 2 раза больше свободного пространства, чем остальные элементы.

💡 Все эти свойства можно записать сокращённо через:

flex: grow shrink basis;


Например:
flex: 1 1 auto;


Но многие разработчики считают сокращённое свойство flex плохой практикой, потому что оно скрывает реальные значения и усложняет чтение кода 👀

──

🔹 Почему FlexBox стал таким популярным?

Потому что он:

▫️ нормально центрирует элементы
▫️ умеет распределять пространство
▫️ хорошо адаптируется
▫️ избавляет от старых CSS-хаков
▫️ делает верстку гораздо предсказуемее

Сейчас почти любой современный UI использует FlexBox.

Но важно понимать:

💡 FlexBox — это инструмент для одномерной раскладки.

То есть либо строка, либо колонка.

А вот для сложных двумерных сеток уже лучше подходит CSS Grid.

──

🔖 Сохраняй, чтобы не забыть!

#frontend #css #flexbox
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2