Почему innerHTML += иногда ведёт себя не так, как ожидаешь?
На первый взгляд это выглядит как самый простой способ что-то добавить в DOM — вроде бы просто дописал кусок HTML и всё. Но внутри браузер работает не так, и из-за этого появляются странные эффекты, когда вроде ничего особенного не сделал, а поведение страницы меняется.
По ощущениям просто добавили один
Упрощённо это примерно вот так:
И уже отсюда становится понятно, почему начинают вылезать побочные эффекты.
Допустим, у тебя есть кнопка с обработчиком:
После такого обновления DOM-поддерево контейнера заменяется новым. Визуально ты видишь тот же интерфейс, но старые узлы уничтожаются и создаются заново. Поэтому все обработчики событий, навешанные через
Та же история с ссылками на элементы:
И это особенно хорошо видно, когда начинаешь делать такие вещи в цикле:
Каждый раз браузер берёт текущее содержимое, сериализует его, парсит заново и пересоздаёт DOM внутри контейнера. На маленьких объёмах это почти незаметно, но когда элементов становится много — начинает сильно тормозить, потому что ты постоянно пересоздаёшь одно и то же дерево.
Поэтому в реальной практике так обычно не делают и используют более прямые DOM-методы.
Тут уже всё проще: создаёшь узел и просто добавляешь его в DOM, без пересборки контейнера.
Есть ещё вариант со строками, но без пересоздания всего содержимого:
Он вставляет кусок точечно в DOM, не пересоздавая существующие узлы, поэтому поведение стабильнее.
Если нужно добавить много элементов сразу, обычно используют
Тут всё собирается в памяти и вставляется одной операцией, без промежуточных пересборок и перерисовок.
🔥 Коротко, суть в том, что
📣 Code Ready | #практика
На первый взгляд это выглядит как самый простой способ что-то добавить в DOM — вроде бы просто дописал кусок HTML и всё. Но внутри браузер работает не так, и из-за этого появляются странные эффекты, когда вроде ничего особенного не сделал, а поведение страницы меняется.
const list = document.querySelector('.list');
list.innerHTML += `
<li>Новый элемент</li>
`;
По ощущениям просто добавили один
li, но на деле браузер не дописывает HTML внутрь DOM. Он делает иначе: берёт текущее содержимое, сериализует его в строку, добавляет новый кусок и затем заново парсит HTML, пересоздавая DOM-узлы внутри контейнера.Упрощённо это примерно вот так:
list.innerHTML = list.innerHTML + `
<li>Новый элемент</li>
`;
И уже отсюда становится понятно, почему начинают вылезать побочные эффекты.
Допустим, у тебя есть кнопка с обработчиком:
button.addEventListener('click', () => {
console.log('click');
});
container.innerHTML += `
<div>Новый блок</div>
`;
После такого обновления DOM-поддерево контейнера заменяется новым. Визуально ты видишь тот же интерфейс, но старые узлы уничтожаются и создаются заново. Поэтому все обработчики событий, навешанные через
addEventListener, на этих элементах теряются.Та же история с ссылками на элементы:
const item = document.querySelector('.item');
container.innerHTML += `
<div>Новый блок</div>
`;
console.log(item.isConnected); // false
item остаётся в переменной, но к текущему DOM он уже не привязан. Он существует как объект, но находится вне дерева документа, потому что был заменён старый поддерево контейнера.И это особенно хорошо видно, когда начинаешь делать такие вещи в цикле:
for (let i = 0; i < 1000; i++) {
list.innerHTML += `<li>${i}</li>`;
}
Каждый раз браузер берёт текущее содержимое, сериализует его, парсит заново и пересоздаёт DOM внутри контейнера. На маленьких объёмах это почти незаметно, но когда элементов становится много — начинает сильно тормозить, потому что ты постоянно пересоздаёшь одно и то же дерево.
Поэтому в реальной практике так обычно не делают и используют более прямые DOM-методы.
const li = document.createElement('li');
li.textContent = 'Новый элемент';
list.append(li);
Тут уже всё проще: создаёшь узел и просто добавляешь его в DOM, без пересборки контейнера.
Есть ещё вариант со строками, но без пересоздания всего содержимого:
list.insertAdjacentHTML(
'beforeend',
'<li>Новый элемент</li>'
);
Он вставляет кусок точечно в DOM, не пересоздавая существующие узлы, поэтому поведение стабильнее.
Если нужно добавить много элементов сразу, обычно используют
DocumentFragment, чтобы не дёргать DOM по сто раз:const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = i;
fragment.appendChild(li);
}
list.appendChild(fragment);
Тут всё собирается в памяти и вставляется одной операцией, без промежуточных пересборок и перерисовок.
innerHTML += — это не добавить элемент, как это выглядит интуитивно. Это скорее пересобери мне весь контейнер заново через HTML-строку и вставь результат. Поэтому в живых интерфейсах он иногда ведёт себя неожиданно и может неприятно влиять и на логику, и на производительность.Please open Telegram to view this post
VIEW IN TELEGRAM
1❤15👍11🔥6
Привет! В этом гайде соберём аккордеон для часто задаваемых вопросов — лёгкий и доступный.
Ключевые моменты:
• HTML: семантическая разметка и aria-атрибуты для доступности.
• CSS: плавное открытие/закрытие блоков без прыжков.
• JavaScript: простой toggle и синхронизация с aria-expanded.
Полезный UI-паттерн, который улучшает читаемость и делает интерфейс чище.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤15👍6🤝5👎1🔥1
Генерируем UUID без библиотек!
Очень частая задача: создать уникальный id для пользователя, заказа, файла или любой сущности.
Раньше для этого почти всегда тянули библиотеки типа uuid.
Хотя в современном JavaScript это уже есть нативно.
Метод сразу возвращает криптографически стойкий UUID v4.
Результат выглядит так:
Подходит для случаев, где нужен уникальный идентификатор: id сущностей, ключи записей, request/session id, временные идентификаторы.
Для секретных токенов (reset password, API tokens) лучше использовать
В браузере работает в secure context — HTTPS или localhost.
🔥
📣 JS Ready | #совет
Очень частая задача: создать уникальный id для пользователя, заказа, файла или любой сущности.
Раньше для этого почти всегда тянули библиотеки типа uuid.
import { v4 as uuid } from 'uuid';Хотя в современном JavaScript это уже есть нативно.
crypto.randomUUID()
Метод сразу возвращает криптографически стойкий UUID v4.
const id = crypto.randomUUID();
Результат выглядит так:
36b8f84d-df4e-4d49-b662-bcde71a8764fПодходит для случаев, где нужен уникальный идентификатор: id сущностей, ключи записей, request/session id, временные идентификаторы.
Для секретных токенов (reset password, API tokens) лучше использовать
crypto.getRandomValues() или crypto.randomBytes().В браузере работает в secure context — HTTPS или localhost.
crypto.randomUUID() — простой способ получать уникальные id.Please open Telegram to view this post
VIEW IN TELEGRAM
1👍23🔥7❤5
Почему чтение offsetHeight иногда тормозит интерфейс?
На первый взгляд кажется, что получить высоту элемента — это самая обычная операция. Ты просто читаешь свойство и получаешь число. Но внутри браузера всё немного интереснее.
Например:
Кажется, что мы просто изменили ширину и прочитали высоту. Но есть нюанс. После изменения DOM или CSS браузер не обязан сразу пересчитывать размеры элементов. Наоборот, современные движки стараются откладывать такие вычисления, чтобы не делать лишнюю работу после каждого изменения.
Упрощённо это выглядит так:
Именно поэтому чтение некоторых свойств может заставить браузер синхронно обновить style и layout, если после изменений они ещё не актуальны.
Например:
Но важно понимать: сами по себе эти свойства не медленные. Если layout уже актуален, браузер просто вернёт готовое значение.
Проблемы обычно начинаются, когда чтение размеров смешивают с изменением стилей внутри цикла:
На десяти элементах ты, скорее всего, ничего не заметишь. Но если их сотни или тысячи, можно получить forced synchronous layout — ситуацию, когда JavaScript заставляет браузер выполнять дорогие вычисления прямо во время выполнения скрипта.
Особенно неприятно это становится в анимациях:
Если такой код выполняется каждый кадр, браузеру приходится постоянно переключаться между изменением стилей и пересчётом layout. В результате главный поток занят вычислениями, а пользователь начинает замечать рывки и пропуски кадров.
Поэтому один из самых простых приёмов — разделять запись и чтение. Вот так плохо:
Лучше будет так:
Так браузер сможет объединить изменения и выполнить один пересчёт вместо множества лишних. Для визуальных изменений также часто используют
🔥 Если коротко: проблема не в
📣 Code Ready | #практика
На первый взгляд кажется, что получить высоту элемента — это самая обычная операция. Ты просто читаешь свойство и получаешь число. Но внутри браузера всё немного интереснее.
Например:
const box = document.querySelector('.box');
box.style.width = '500px';
console.log(box.offsetHeight);
Кажется, что мы просто изменили ширину и прочитали высоту. Но есть нюанс. После изменения DOM или CSS браузер не обязан сразу пересчитывать размеры элементов. Наоборот, современные движки стараются откладывать такие вычисления, чтобы не делать лишнюю работу после каждого изменения.
Упрощённо это выглядит так:
box.style.width = '500px';
// браузер пока просто запомнил изменение
console.log(box.offsetHeight);
// здесь уже нужны реальные размеры,
// поэтому приходится пересчитать layout
Именно поэтому чтение некоторых свойств может заставить браузер синхронно обновить style и layout, если после изменений они ещё не актуальны.
Например:
element.offsetWidth;
element.offsetHeight;
element.clientWidth;
element.clientHeight;
element.scrollHeight;
element.getBoundingClientRect();
Но важно понимать: сами по себе эти свойства не медленные. Если layout уже актуален, браузер просто вернёт готовое значение.
Проблемы обычно начинаются, когда чтение размеров смешивают с изменением стилей внутри цикла:
const items = document.querySelectorAll('.item');
items.forEach(item => {
item.style.height = '100px';
console.log(item.offsetHeight);
});
На десяти элементах ты, скорее всего, ничего не заметишь. Но если их сотни или тысячи, можно получить forced synchronous layout — ситуацию, когда JavaScript заставляет браузер выполнять дорогие вычисления прямо во время выполнения скрипта.
Особенно неприятно это становится в анимациях:
function animate(elements) {
elements.forEach(element => {
element.style.width = '200px';
const height = element.offsetHeight;
element.style.height = `${height}px`;
});
}
Если такой код выполняется каждый кадр, браузеру приходится постоянно переключаться между изменением стилей и пересчётом layout. В результате главный поток занят вычислениями, а пользователь начинает замечать рывки и пропуски кадров.
Поэтому один из самых простых приёмов — разделять запись и чтение. Вот так плохо:
items.forEach(item => {
item.style.width = '200px';
console.log(item.offsetHeight);
});
Лучше будет так:
items.forEach(item => {
item.style.width = '200px';
});
items.forEach(item => {
console.log(item.offsetHeight);
});
Так браузер сможет объединить изменения и выполнить один пересчёт вместо множества лишних. Для визуальных изменений также часто используют
requestAnimationFrame, потому что он запускает код перед следующим циклом отрисовки браузера. Но сам по себе requestAnimationFrame не избавляет от layout thrashing. Если внутри его callback продолжать чередовать изменения DOM и чтение размеров, синхронные перерасчёты всё равно останутся.offsetHeight, а в том, когда ты его читаешь. После изменений DOM или CSS он может заставить браузер синхронно пересчитать layout. Именно чередование записей и чтений чаще всего становится причиной лишних тормозов.Please open Telegram to view this post
VIEW IN TELEGRAM
1👍15❤7🔥7
This media is not supported in your browser
VIEW IN TELEGRAM
Здесь собраны качественные статьи, инструменты, библиотеки и другие полезные ресурсы для работы со стилями. Особенно полезен тем, кто хочет лучше разобраться в современных возможностях CSS, быстрее находить решения для вёрстки и держать под рукой материалы. Хорошая база как для обучения, так и для повседневной разработки.
Оставляю ссылочку: GitHub📱
Please open Telegram to view this post
VIEW IN TELEGRAM
❤13👍8🤝5
Как убрать лишние классы для пустых блоков?
Часто в компонентах есть контейнеры для описания, ошибки, подсказки или дополнительного контента.
Если данных нет — элемент остаётся пустым, но продолжает занимать место.
Обычно это решают через JS или условный рендеринг.
А потом отдельно добавляют класс при появлении контента.
Но CSS умеет определять пустые элементы самостоятельно.
Теперь блок автоматически скрывается, если внутри ничего нет.
Можно делать и обратную логику.
Отступы появятся только тогда, когда внутри действительно есть контент.
Особенно удобно для:
Где содержимое может появляться динамически.
🔥
📣 Code Ready | #совет
Часто в компонентах есть контейнеры для описания, ошибки, подсказки или дополнительного контента.
Если данных нет — элемент остаётся пустым, но продолжает занимать место.
Обычно это решают через JS или условный рендеринг.
.error {
display: none;
}А потом отдельно добавляют класс при появлении контента.
Но CSS умеет определять пустые элементы самостоятельно.
.error:empty {
display: none;
}Теперь блок автоматически скрывается, если внутри ничего нет.
Можно делать и обратную логику.
.card:not(:empty) {
padding: 16px;
}Отступы появятся только тогда, когда внутри действительно есть контент.
Особенно удобно для:
error,
hint,
description,
badge
Где содержимое может появляться динамически.
:empty позволяет убрать лишние классы и часть условной логики прямо средствами CSS.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥17👍7🤝4
Например,
get() позволяет получить значение параметра, set() обновить существующий параметр, append() добавить новый, а delete() удалить ключ вместе со всеми значениями.На картинке — основные методы
URLSearchParams, примеры работы с параметрами запроса и базовые сценарии использования в современной web-разработке.Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11👍6❤4
Атрибут, который подсказывает пользователю, что именно нужно ввести в поле ввода (обычно это пример слова или фразы). А ещё значение атрибута исчезает, как только пользователь начинает вводить текст.
Чаще всего используется с:
• <input> — для текстовых полей, поиска, email, телефона и других типов ввода;
• <textarea> — для подсказок в многострочных полях.
Часто из-за дизайнерских решений
placeholder используется для замены более семантического элемента метки. Однако это не означает, что текст-заполнитель может заменить <label>, поскольку это создаёт неудобства для пользователей с особыми потребностями.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥13❤7👍7
Проверяем интернет-скорость!
Хочешь узнать, насколько быстрый у тебя интернет? Для этого есть готовый пакет
Такой скрипт можно встроить в мониторинг или просто проверить сеть без браузера.
Шаг 1 — импортируем:
Шаг 2 — запускаем тест:
Шаг 3 — форматируем и выводим результаты:
🔥 Теперь у тебя свой
📣 Code Ready | #практика
Хочешь узнать, насколько быстрый у тебя интернет? Для этого есть готовый пакет
speedtest-net, который позволяет получить скорость загрузки, отдачи и пинга. Такой скрипт можно встроить в мониторинг или просто проверить сеть без браузера.
Шаг 1 — импортируем:
const speedTest = require('speedtest-net');Шаг 2 — запускаем тест:
(async () => {
const res = await speedTest({ acceptLicense: true });
})();Шаг 3 — форматируем и выводим результаты:
// Загрузка
console.log(`Download: ${(res.download.bandwidth / 125000).toFixed(2)} Mbps`);
// Выгрузка
console.log(`Upload: ${(res.upload.bandwidth / 125000).toFixed(2)} Mbps`);
// Пинг
console.log(`Ping: ${res.ping.latency} ms`);
🔥 Теперь у тебя свой
speedtest прямо в коде. Можно запускать вручную или добавить в cron для регулярного замера.Please open Telegram to view this post
VIEW IN TELEGRAM
👍12❤7🔥6
Крутая статья попалась на Хабре: «HTML Sanitizer API: конец эпохи DOMPurify и XSS-страданий»!
В этой статье:
📣 Code Ready | #статья
В этой статье:
• Разбирается, почему привычные способы защиты от XSS становятся сложнее и какие проблемы есть у старых подходов вроде DOMPurify;
• Показывается новый HTML Sanitizer API, который позволяет безопаснее работать с пользовательским HTML прямо на уровне браузера;
• Объясняется, как современные веб-разработчики могут упростить код и повысить безопасность приложений.🔊 Продолжай читать на Habr!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤13👍5🤝3
Node.replaceWith() — замена DOM-элемента без пересборки HTML!
Когда нужно заменить элемент на странице, часто используют
Если вы работаете с динамическими интерфейсами, где есть состояние элементов и обработчики событий, такие детали становятся важными. Например:
Выглядит просто: старый элемент заменили новым. Но браузер получает строку HTML, разбирает её и создаёт новый DOM-узел. Старый элемент исчезает из DOM, а на его месте появляется другой объект.
Если где-то была сохранена ссылка на старый элемент или на нём были обработчики событий, они не перейдут автоматически. Для браузера это уже другой DOM-объект.
Для замены конкретного узла есть более подходящий метод:
Это удобно, когда вокруг элемента есть другая разметка, которую не нужно трогать. Например:
Меняется только
Разница хорошо заметна, если сравнить с
Здесь браузер заново создаёт содержимое контейнера из строки. Даже если часть элементов выглядит так же, это могут быть уже новые DOM-объекты. Например:
Если
Именно поэтому
Это удобно, когда один элемент интерфейса переходит в другое состояние: загрузка заменяется результатом, временный блок заменяется данными с сервера или один компонент меняется на другой.
Но важно учитывать: новый элемент не наследует обработчики старого. Например:
После замены новый
Если нужно сохранить поведение, обработчик нужно добавить заново:
Также
Браузер удалит старый элемент и вставит переданные узлы на его место. Причём это могут быть не только элементы, но и текстовые узлы:
🔥 Подведем итог:
📣 Code Ready | #практика
Когда нужно заменить элемент на странице, часто используют
innerHTML, outerHTML или удаляют старый узел, а потом создают новый. В небольших интерфейсах разницы почти не видно, но внутри браузера это разные операции.Если вы работаете с динамическими интерфейсами, где есть состояние элементов и обработчики событий, такие детали становятся важными. Например:
const oldElement = document.querySelector('.message');
oldElement.outerHTML = `
<div class="success">
Saved
</div>
`;
Выглядит просто: старый элемент заменили новым. Но браузер получает строку HTML, разбирает её и создаёт новый DOM-узел. Старый элемент исчезает из DOM, а на его месте появляется другой объект.
Если где-то была сохранена ссылка на старый элемент или на нём были обработчики событий, они не перейдут автоматически. Для браузера это уже другой DOM-объект.
Для замены конкретного узла есть более подходящий метод:
const oldElement = document.querySelector('.message');
const newElement = document.createElement('div');
newElement.textContent = 'Saved';
oldElement.replaceWith(newElement);
replaceWith() работает непосредственно с DOM-узлами. Браузеру не нужно заново парсить HTML-строку — он получает готовый элемент и заменяет только нужное место в DOM-дереве.Это удобно, когда вокруг элемента есть другая разметка, которую не нужно трогать. Например:
const button = document.querySelector('.button');
const loader = document.createElement('span');
loader.textContent = 'Loading...';
button.replaceWith(loader);
Меняется только
button. Родительский элемент, соседние узлы и остальная структура страницы остаются без изменений.Разница хорошо заметна, если сравнить с
innerHTML. Например:const container = document.querySelector('.container');
container.innerHTML = `
<div class="card">
New content
</div>
`;
Здесь браузер заново создаёт содержимое контейнера из строки. Даже если часть элементов выглядит так же, это могут быть уже новые DOM-объекты. Например:
const input = document.querySelector('.search');
input.value = 'frontend';
container.innerHTML += `
<div>
New block
</div>
`;
Если
input находится внутри container, старый элемент будет пересоздан вместе с содержимым. Можно потерять значение поля, обработчики событий и ссылки на старые DOM-объекты.Именно поэтому
innerHTML += часто становится причиной багов. replaceWith() такого поведения не создаёт, потому что меняет только указанный узел. Например:const title = document.querySelector('.title');
const newTitle = document.createElement('h2');
newTitle.textContent = 'New title';
title.replaceWith(newTitle);
Это удобно, когда один элемент интерфейса переходит в другое состояние: загрузка заменяется результатом, временный блок заменяется данными с сервера или один компонент меняется на другой.
Но важно учитывать: новый элемент не наследует обработчики старого. Например:
const button = document.querySelector('.button');
button.addEventListener('click', handleClick);
const newButton = document.createElement('button');
button.replaceWith(newButton);
После замены новый
button будет другим объектом, поэтому старый обработчик к нему не относится.Если нужно сохранить поведение, обработчик нужно добавить заново:
newButton.addEventListener('click', handleClick);
Также
replaceWith() может принимать несколько узлов:element.replaceWith(title, text, button);
Браузер удалит старый элемент и вставит переданные узлы на его место. Причём это могут быть не только элементы, но и текстовые узлы:
element.replaceWith('Hello');
replaceWith() удобен для точечной замены DOM-элементов без повторного парсинга HTML и пересоздания всей окружающей структуры. В отличие от innerHTML, он работает напрямую с узлами и даёт больше контроля над изменениями DOM.Please open Telegram to view this post
VIEW IN TELEGRAM
❤12👍10🔥7
Тег
<math> используется для встраивания математических выражений на веб-страницу с помощью языка MathML. Он позволяет отображать дроби, степени, корни, индексы, матрицы и другие математические конструкции без изображений.Кроме глобальных атрибутов, <math> поддерживает:
• display="inline" — формула отображается внутри строки (по умолчанию).
• display="block" — формула выводится отдельным блочным элементом.
• alttext — альтернативное текстовое описание формулы.
Все теги MathML чувствительны к регистру: используйте строго строчные (
<msup>, а не <MSUP>). Для лучшего внешнего вида можно подключать стили к формуле, но аккуратно, некоторые свойства не работаютPlease open Telegram to view this post
VIEW IN TELEGRAM
👍12❤9🔥6
CSS-вычисления теперь работают с auto и max-content!
Многие знают
Например, так написать нельзя:
Такое значение считается невалидным.
Для этого появился
Теперь браузер сначала вычисляет
Это работает и с другими размерами, зависящими от содержимого.
Полезно, когда элемент должен сохранить естественный размер содержимого, но при этом стать немного больше без JS.
🔥
📣 Code Ready | #совет
Многие знают
calc(), но он не умеет вычислять auto, max-content, fit-content и min-content.Например, так написать нельзя:
.panel {
width: calc(max-content + 2rem);
}Такое значение считается невалидным.
Для этого появился
calc-size()..panel {
width: calc-size(max-content, size + 2rem);
}Теперь браузер сначала вычисляет
max-content, а затем прибавляет к нему 2rem.Это работает и с другими размерами, зависящими от содержимого.
.panel {
height: calc-size(auto, size + 1rem);
}Полезно, когда элемент должен сохранить естественный размер содержимого, но при этом стать немного больше без JS.
calc-size() позволяет выполнять вычисления с auto, max-content и fit-content — то, чего обычный calc() делать не умеет.Please open Telegram to view this post
VIEW IN TELEGRAM
❤19🔥6👍5
This media is not supported in your browser
VIEW IN TELEGRAM
На сайте собрана большая база материалов по веб-разработке: подробные уроки по HTML5, CSS3, JavaScript и jQuery, а также удобные справочники по HTML- и CSS-свойствам. Здесь можно найти разбор тегов, форм, анимаций, Flexbox, Grid, DOM и других ключевых технологий фронтенда. Большое количество практических примеров, готовых решений и UI-эффектов для интерфейсов.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11👍5🔥3
Date.parse() и неочевидные проблемы при работе с датами!
При работе с датами в JavaScript часто используют
Ошибки обычно появляются, когда эти разные сценарии начинают обрабатываться одинаково. Например:
JavaScript возвращает количество миллисекунд от 1 января 1970 года UTC.
Проблемы возникают, когда в
Такие форматы не являются надёжным контрактом между системами. Их обработка может отличаться в зависимости от окружения и реализации JavaScript. Например:
Такая запись неоднозначна. Без явного формата невозможно гарантировать одинаковый результат во всех окружениях.
Поэтому при обмене датами между backend и frontend обычно используют ISO 8601 с явным указанием времени и часового пояса:
Здесь описан конкретный момент времени. Указаны дата, время и часовой пояс (UTC), поэтому значение будет одинаково интерпретироваться независимо от локального времени пользователя.
Отдельная проблема возникает при работе с датами без времени. Например:
Строка
После этого значение преобразуется в локальный часовой пояс пользователя. В часовом поясе UTC+3 пользователь увидит:
А в UTC-5:
Само значение времени остаётся одинаковым, но календарная дата при отображении может измениться. Именно здесь часто возникает ошибка в модели данных.
Например, сервер возвращает дату рождения пользователя:
На frontend создаётся объект
Разработчик ожидает получить:
Объект
Дата создания сообщения, время оплаты или момент отправки уведомления являются событиями во времени. Для них важно передавать полное значение с часовым поясом.
А вот календарные значения, например дату рождения или дату окончания периода, не всегда стоит преобразовывать в
Так значение останется календарной датой и не будет изменено из-за часового пояса.
Также стоит учитывать, что
Результат:
Основная проблема при работе с датами в JavaScript связана не с самим
🔥 Перед использованием
📣 Code Ready | #практика
При работе с датами в JavaScript часто используют
Date.parse() или new Date(), чтобы преобразовать строковое значение в дату или получить timestamp. При этом важно понимать, какое значение хранится в системе: календарная дата, локальное время или конкретный момент времени. Ошибки обычно появляются, когда эти разные сценарии начинают обрабатываться одинаково. Например:
const timestamp = Date.parse(
'2024-01-15T10:30:00Z'
);
console.log(timestamp);
JavaScript возвращает количество миллисекунд от 1 января 1970 года UTC.
Проблемы возникают, когда в
Date.parse() передают даты в произвольном формате:Date.parse('01/15/2024');
Date.parse('15.01.2024');
Date.parse('2024/01/15');
Такие форматы не являются надёжным контрактом между системами. Их обработка может отличаться в зависимости от окружения и реализации JavaScript. Например:
Date.parse('01/02/2024');
Такая запись неоднозначна. Без явного формата невозможно гарантировать одинаковый результат во всех окружениях.
Поэтому при обмене датами между backend и frontend обычно используют ISO 8601 с явным указанием времени и часового пояса:
new Date(
'2024-01-15T10:30:00Z'
);
Здесь описан конкретный момент времени. Указаны дата, время и часовой пояс (UTC), поэтому значение будет одинаково интерпретироваться независимо от локального времени пользователя.
Отдельная проблема возникает при работе с датами без времени. Например:
const date = new Date(
'2024-01-15'
);
console.log(date);
Строка
2024-01-15 в JavaScript интерпретируется как UTC-дата:2024-01-15T00:00:00.000Z
После этого значение преобразуется в локальный часовой пояс пользователя. В часовом поясе UTC+3 пользователь увидит:
15.01.2024 03:00
А в UTC-5:
14.01.2024 19:00
Само значение времени остаётся одинаковым, но календарная дата при отображении может измениться. Именно здесь часто возникает ошибка в модели данных.
Например, сервер возвращает дату рождения пользователя:
{
"birthday": "2000-01-15"
}
На frontend создаётся объект
Date:const birthday = new Date(
user.birthday
);
birthday.toLocaleDateString('ru-RU');
Разработчик ожидает получить:
15.01.2000. Но пользователь в другом часовом поясе может увидеть: 14.01.2000. Причина в том, что дата рождения — это календарное значение. Она не зависит от часов, минут и часового пояса.Объект
Date в JavaScript используется для работы с конкретным моментом времени, поэтому при преобразовании строк учитывается часовой пояс. Для временных событий лучше использовать полный ISO-формат:const createdAt = new Date(
'2024-01-15T14:30:00Z'
);
Дата создания сообщения, время оплаты или момент отправки уведомления являются событиями во времени. Для них важно передавать полное значение с часовым поясом.
А вот календарные значения, например дату рождения или дату окончания периода, не всегда стоит преобразовывать в
Date. В таких случаях безопаснее работать с исходным значением:const [year, month, day] =
'2000-01-15'.split('-');
const formattedDate =
`${day}.${month}.${year}`;
Так значение останется календарной датой и не будет изменено из-за часового пояса.
Также стоит учитывать, что
Date.parse() не выбрасывает исключение при невозможности распознать дату:const result = Date.parse(
'invalid-date'
);
console.log(result);
Результат:
NaN. Поэтому такие значения необходимо проверять:const timestamp = Date.parse(value);
if (Number.isNaN(timestamp)) {
throw new Error(
'Invalid date'
);
}
Основная проблема при работе с датами в JavaScript связана не с самим
Date.parse(). Проблемы появляются, когда смешиваются разные типы данных: календарные даты, локальное время и конкретные моменты времени.Date важно определить, что именно хранится в данных: дата, время события или значение, которое зависит от часового пояса. Только после этого стоит выбирать способ представления и обработки.Please open Telegram to view this post
VIEW IN TELEGRAM
❤14👍7🔥6
Как создавать независимые компоненты без конфликтов CSS-переменных?
В больших проектах часто используют глобальные переменные:
Но потом один компонент случайно меняет значение, и стили начинают влиять друг на друга.
CSS позволяет делать локальные токены прямо внутри компонента.
Теперь переменная действует только внутри
Можно создавать полностью изолированные темы компонентов.
Внутренние элементы автоматически получают эти значения:
Без дополнительных классов и дублирования.
Особенно удобно для:
где каждый блок должен иметь свои настройки.
🔥 Локальные CSS custom properties позволяют строить компонентные дизайн-системы без огромного количества модификаторов и переопределений.
📣 Code Ready | #совет
В больших проектах часто используют глобальные переменные:
:root {
--color: blue;
}Но потом один компонент случайно меняет значение, и стили начинают влиять друг на друга.
CSS позволяет делать локальные токены прямо внутри компонента.
.card {
--color: #222;
color: var(--color);
}Теперь переменная действует только внутри
.card и его потомков.Можно создавать полностью изолированные темы компонентов.
.dark-card {
--bg: #111;
--text: white;
}Внутренние элементы автоматически получают эти значения:
.title {
color: var(--text);
}Без дополнительных классов и дублирования.
Особенно удобно для:
.theme,
.component,
.widget
где каждый блок должен иметь свои настройки.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10🔥4🤝3
Intersection Observer — отслеживаем появление элементов в области видимости!
Эффективная альтернатива scroll-событиям. Используется для ленивой загрузки, анимаций и инфинит-скролла.
Инициализируем наблюдатель:
📣 Code Ready | #практика
Эффективная альтернатива scroll-событиям. Используется для ленивой загрузки, анимаций и инфинит-скролла.
Инициализируем наблюдатель:
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
}
});
});
Подключаем наблюдение:
document.querySelectorAll('.anim').forEach((el) => {
observer.observe(el);
});
Стили для появления:
.anim { opacity: 0; transform: translateY(30px); transition: 0.4s ease; }
.visible { opacity: 1; transform: none; }
🔥 Простая и масштабируемая практика, которую используют в продакшене: YouTube, Medium, e-commerce.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10❤8👍7