В этой статье:
• Вспомните, как contenteditable позволяет редактировать содержимое элементов прямо в браузере;• Разберётесь, зачем использовать <time> и где пригодится currentColor;• Узнаете разницу между background-clip и background-origin и посмотрите на практические примеры их применения.🔊 Продолжай читать на Habr!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9❤8🤝5
Почему старый fetch-запрос может перезаписать новый?
Асинхронные операции завершаются независимо от порядка запуска. Поэтому в интерфейсах, где новый запрос делает предыдущий результат неактуальным, может возникнуть race condition.
Например, пользователь быстро продолжает вводить поисковый запрос. Запрос для более длинной и актуальной строки может завершиться раньше предыдущего. Если каждый ответ безусловно передавать в
Проблема здесь не в
Если устаревшие запросы больше не нужны, для
При следующем вводе
Важно понимать границу такой отмены.
При этом отмена — не единственный способ защиты от устаревших результатов. Если операцию нельзя или не требуется отменять, актуальность результата можно проверить непосредственно перед изменением состояния:
Здесь каждый запрос получает монотонно возрастающий идентификатор. Изменить интерфейс может только результат последней запущенной операции. Даже если более старый запрос завершится позже, его результат будет проигнорирован.
🔥 Основной принцип шире конкретного API: результат асинхронной операции должен изменять состояние только до тех пор, пока эта операция остаётся актуальной.
📣 Code Ready | #практика
Асинхронные операции завершаются независимо от порядка запуска. Поэтому в интерфейсах, где новый запрос делает предыдущий результат неактуальным, может возникнуть race condition.
Например, пользователь быстро продолжает вводить поисковый запрос. Запрос для более длинной и актуальной строки может завершиться раньше предыдущего. Если каждый ответ безусловно передавать в
renderResults(), более старый результат в итоге перезапишет актуальный:search.addEventListener('input', async event => {
const query = encodeURIComponent(event.target.value);
const response = await fetch(`/api/search?q=${query}`);
renderResults(await response.json());
});Проблема здесь не в
fetch() как таковом, а в отсутствии контроля актуальности асинхронной операции: любой завершившийся запрос получает возможность изменить состояние интерфейса.Если устаревшие запросы больше не нужны, для
fetch() их можно отменять через AbortController:let controller;
search.addEventListener('input', async event => {
controller?.abort();
controller = new AbortController();
try {
const query = encodeURIComponent(event.target.value);
const response = await fetch(`/api/search?q=${query}`, {
signal: controller.signal
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const results = await response.json();
renderResults(results);
} catch (error) {
if (error.name !== 'AbortError') {
console.error(error);
}
}
});
При следующем вводе
abort() переводит предыдущий AbortSignal в состояние aborted. Связанная с ним fetch-операция прерывается, поэтому её устаревший результат не попадёт в renderResults().AbortSignal нельзя использовать повторно после отмены: он остаётся в состоянии aborted. Для следующей операции необходимо создать новый AbortController:controller.abort();
controller = new AbortController();
Важно понимать границу такой отмены.
abort() прерывает клиентскую fetch-операцию и чтение тела ответа, но не гарантирует прекращение обработки запроса на сервере. К моменту отмены запрос уже мог быть отправлен, а сервер мог начать его обработку.AbortController и debounce также решают разные задачи. debounce уменьшает количество запускаемых запросов, откладывая выполнение до паузы во вводе. AbortController позволяет прервать клиентскую операцию, которая уже была запущена, но перестала быть актуальной. В интерактивном поиске эти механизмы можно использовать совместно.При этом отмена — не единственный способ защиты от устаревших результатов. Если операцию нельзя или не требуется отменять, актуальность результата можно проверить непосредственно перед изменением состояния:
let requestId = 0;
search.addEventListener('input', async event => {
const id = ++requestId;
const query = encodeURIComponent(event.target.value);
const response = await fetch(`/api/search?q=${query}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const results = await response.json();
if (id === requestId) {
renderResults(results);
}
});
Здесь каждый запрос получает монотонно возрастающий идентификатор. Изменить интерфейс может только результат последней запущенной операции. Даже если более старый запрос завершится позже, его результат будет проигнорирован.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍5🤝3
В этой статье:
• Объясняется, как токены превращаются в эмбеддинги и как модель представляет смысл в многомерном пространстве;• На примере grokking показывается, как нейросеть переходит от запоминания ответов к пониманию общего правила;• Разбираются внутренние представления LLM и причины неожиданных артефактов в их ответах.🔊 Продолжайте читать на Habr!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10❤6👍5
This media is not supported in your browser
VIEW IN TELEGRAM
В репозитории собраны не просто вопросы, а готовые ответы и пояснения по ключевым концепциям фронтенда, включая типичные спорные моменты и задачи, на которых часто проверяют понимание языка и React. Можно использовать для повторения теории, самопроверки и поиска тем, которые стоит подтянуть.
Оставляю ссылочку: GitHub📱
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13🤝10🔥5
CSS теперь разрешает создавать собственные функции!
В дизайн-системах постоянно повторяются одни и те же вычисления.
Можно вынести саму логику вычисления в
И дальше использовать её практически как встроенную CSS-функцию.
Параметры можно типизировать, чтобы функция принимала только ожидаемые значения.
Причём функция может содержать не только
Поддержка пока ограничена.
🔥 Смысл
📣 Code Ready | #совет
В дизайн-системах постоянно повторяются одни и те же вычисления.
.card {
padding: calc(6 * 4px);
}
.modal {
gap: calc(4 * 4px);
}Можно вынести саму логику вычисления в
@function.@function --space(--n) {
result: calc(var(--n) * 4px);
}И дальше использовать её практически как встроенную CSS-функцию.
.card {
padding: --space(6);
}
.modal {
gap: --space(4);
}Параметры можно типизировать, чтобы функция принимала только ожидаемые значения.
@function --space(--n <number>) returns <length> {
result: calc(var(--n) * 4px);
}Причём функция может содержать не только
calc(): внутри доступны локальные CSS-переменные и условная логика, а одна пользовательская функция может вызывать другую.Поддержка пока ограничена.
@function не в сокращении пары строк. CSS получает переиспользуемую логику с аргументами и типами — то, ради чего раньше приходилось повторять формулы или переносить вычисления в препроцессор.Please open Telegram to view this post
VIEW IN TELEGRAM
👍17🔥10🤝4
ДА! Вайбкодинг реально начали проверять на интервью, поэтому мы нашли собеседующего, который проводит AI-секцию в международном финтехе, чтобы вы увидели что на ней спрашивают и как к ней подготовиться
Как это будет:
Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для разработчиков, которые хотят сменить работу, повысить свой грейд, ЗП и прокачать скиллы.
Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_front_bot
Реклама.
О рекламодателе.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1
inert — отключение целой области интерфейса без обхода дочерних элементов!
Иногда часть интерфейса требуется временно исключить из взаимодействия: например, при открытии выдвижной панели или на время операции, когда фоновый интерфейс не должен принимать пользовательский ввод.
Управлять состоянием каждого элемента отдельно можно, но такой подход требует учитывать разные типы элементов и механизмы взаимодействия:
Состоянием можно управлять через
После установки
Пользовательская активация также блокируется: клик пользователя по кнопке внутри inert-области не приводит к
В отличие от
Например, основной интерфейс можно деактивировать при открытии выдвижной панели, расположенной вне inert-контейнера:
Состояние всего фонового интерфейса переключается одной операцией независимо от количества элементов внутри
Для модальных интерфейсов есть
Сам
🔥
📣 Code Ready | #практика
Иногда часть интерфейса требуется временно исключить из взаимодействия: например, при открытии выдвижной панели или на время операции, когда фоновый интерфейс не должен принимать пользовательский ввод.
Управлять состоянием каждого элемента отдельно можно, но такой подход требует учитывать разные типы элементов и механизмы взаимодействия:
button.disabled = true;
input.disabled = true;
link.tabIndex = -1;
disabled применим не ко всем HTML-элементам, а изменение tabindex решает только задачу фокуса. Для блокировки целой области документа существует глобальный атрибут inert:<main id="content">
<button>Сохранить</button>
<a href="/profile">Профиль</a>
<input type="text">
</main>
Состоянием можно управлять через
HTMLElement.inert, без поиска и изменения каждого интерактивного элемента внутри контейнера:const content = document.querySelector('#content');
content.inert = true;После установки
inert контейнер и его потомки в плоском дереве (flat tree) не могут получать фокус. Это относится и к программному вызову focus():content.inert = true;
document.querySelector('#content input').focus();
Пользовательская активация также блокируется: клик пользователя по кнопке внутри inert-области не приводит к
click на этой кнопке. Программный element.click() при этом следует рассматривать отдельно:const button = document.querySelector('#content button');
button.addEventListener('click', () => {
console.log('click');
});
content.inert = true;В отличие от
inert, pointer-events: none влияет на определение элемента под указателем, но сам по себе не исключает элементы из последовательной навигации с клавиатуры:.content {
pointer-events: none;
}inert действует шире: элементы не получают фокус, пользовательское взаимодействие блокируется, а содержимое исключается из дерева доступности.Например, основной интерфейс можно деактивировать при открытии выдвижной панели, расположенной вне inert-контейнера:
<main id="content">
<!-- основной интерфейс -->
</main>
<aside id="drawer" hidden>
<!-- выдвижная панель -->
</aside>
Состояние всего фонового интерфейса переключается одной операцией независимо от количества элементов внутри
main:function openDrawer() {
content.inert = true;
drawer.hidden = false;
}
function closeDrawer() {
content.inert = false;
drawer.hidden = true;
}Для модальных интерфейсов есть
<dialog>. Вызов showModal() помещает диалог в верхний слой (top layer), а остальную часть документа браузер делает inert автоматически:const dialog = document.querySelector('dialog');
dialog.showModal();Сам
inert не задаёт визуальное состояние заблокированной области. При необходимости оно оформляется отдельно через CSS:[inert] {
opacity: 0.6;
}inert позволяет исключить целую область документа из взаимодействия без ручного управления disabled, tabindex и состоянием каждого вложенного элемента.Please open Telegram to view this post
VIEW IN TELEGRAM
👍12🔥7🤝4❤2
Тег
<data> позволяет показать пользователю обычный текст и одновременно связать его с другим значением, которое удобно использовать в скриптах или при обработке данных.В <data> используются:
• содержимое тега — текст, который видит пользователь;
• value — машиночитаемое значение;
• JavaScript — при необходимости может получить значение через свойство value.
Элемент
<data> стоит использовать, когда вы не хотите выделять элементы на вёрстке, например, при помощи <div> или <span>, но вам нужно связать текст с внутренним ID или значением, который вы будете использовать при обработке запросов пользователя.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10👍7❤3🤝3
:autofill — псевдокласс, который находит поля формы, куда браузер сам подставил сохранённые данные: адрес, телефон, почту. Нужен, чтобы подсвеченные браузером поля не выбивались из дизайна формы.Как работает автозаполнение:
• срабатывает только по инициативе браузера, через JavaScript вызвать его нельзя;
• браузер может заполнить сразу несколько полей формы;
• поле остаётся :autofill, пока пользователь не изменит значение;
• после ручного редактирования оно больше не считается автозаполненным.
Не полагайтесь на
:autofill для валидации: это лишь визуальное состояние, а не факт корректности данных.Please open Telegram to view this post
VIEW IN TELEGRAM
👍13❤6🔥5
Разбираем Reflect API и его основные методы для чтения, изменения, проверки и удаления свойств, вызова функций, создания экземпляров, работы с дескрипторами и получения собственных ключей объекта. Reflect применяется для метапрограммирования, низкоуровневой работы с объектами и реализации Proxy-логики с явным результатом операций.Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9❤7🔥7
scheduler.yield() — разбиваем долгую JS-задачу, чтобы не блокировать интерфейс!
Длительная синхронная работа на
Классический способ разбить такую работу — планировать продолжение через
Это принципиально отличается от
Делать
Поддержка
🔥
📣 Code Ready | #практика
Длительная синхронная работа на
main thread блокирует выполнение других задач. Даже обычный цикл может превратиться в длительную задачу, если обработка элементов достаточно тяжёлая:function processItems(items) {
for (const item of items) {
renderItem(item);
}
}Классический способ разбить такую работу — планировать продолжение через
setTimeout(). В современных браузерах для добровольной передачи управления планировщику есть более специализированный механизм — scheduler.yield():async function processItems(items) {
for (let i = 0; i < items.length; i++) {
renderItem(items[i]);
if ((i + 1) % 100 === 0) {
await scheduler.yield();
}
}
}scheduler.yield() приостанавливает текущую async-функцию и планирует её продолжение как отдельную задачу, давая браузеру возможность выполнить другую ожидающую работу на main thread:await scheduler.yield();
// выполнение продолжится позже
Это принципиально отличается от
await Promise.resolve(): resolved Promise продолжает выполнение через microtask queue и не обеспечивает полноценную передачу управления event loop для выполнения следующей задачи:await Promise.resolve();
// продолжение выполняется как microtask
Делать
yield() после каждой итерации не стоит: каждая передача управления создаёт дополнительные накладные расходы. Размер порции работы зависит от стоимости конкретных операций, поэтому 100 или 200 итераций — лишь пример, а не универсальное значение.if ((i + 1) % CHUNK_SIZE === 0) {
await yieldToMain();
}Поддержка
scheduler.yield() пока не универсальна, поэтому стоит предусмотреть запасной вариант:function yieldToMain() {
if (globalThis.scheduler?.yield) {
return scheduler.yield();
}
return new Promise(resolve => setTimeout(resolve, 0));
}scheduler.yield() полезен для кооперативного планирования длительной работы на main thread: обработки больших наборов данных и постепенного обновления сложного UI. Для тяжёлых вычислений, интенсивно использующих процессор, Worker остаётся предпочтительным решением: yield() не переносит вычисления с main thread, а только разбивает их выполнение во времени.Please open Telegram to view this post
VIEW IN TELEGRAM
👍13❤7🔥6
В этой статье:
• Разберётесь, почему Flexbox и Grid не гарантируют визуально идеальное выравнивание;• Узнаете, как метрики шрифтов и высота строки влияют на положение текста;• Посмотрите, почему иконки рядом с текстом часто «съезжают» и как это исправлять на практике.🔊 Продолжай читать на Habr!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10🔥9🤝5