Code Ready | Frontend
21.6K subscribers
1.27K photos
534 videos
17 files
998 links
Авторский канал по Frontend разработке.
Ресурсы, гайды, задачи, шпаргалки.
Информация ежедневно пополняется!

Cотрудничество: @energy_c

РКН: https://clck.ru/3NJCKs
Download Telegram
This media is not supported in your browser
VIEW IN TELEGRAM
✍️ Краткое руководство по HTML — основные теги и структура веб-страницы!

Здесь собраны основы HTML с понятными примерами: структура HTML-документа, работа с <head> и метатегами, HTML5-разметка, заголовки и текст, ссылки, изображения, таблицы и основные атрибуты элементов. В конце есть удобная таблица с популярными тегами и их назначением.

Оставляю ссылочку: GitHub 📱

📣 Code Ready | #репозиторий
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12👍75
Делаем «сквиркл»-углы нативно в CSS!

Обычный border-radius строит эллиптическое скругление. Но в интерфейсах часто нужны более плавные, «квадратные» скругления — squircle.

Раньше для точной формы приходилось использовать SVG, clip-path или маски.

Теперь форму угла можно менять отдельно от его радиуса:
.card {
corner-shape: squircle;
}


При этом border-radius всё ещё определяет область скругления — без ненулевого радиуса corner-shape визуально ничего не изменит.

Есть и другие формы:
.card {
corner-shape: bevel;
}


И даже вогнутые углы:
.card {
corner-shape: scoop;
}


Причём границы, фон, outline и тени следуют новой форме углов, а сами формы можно анимировать. Поддержка пока ограничена.

🔥 corner-shape позволяет делать squircle, bevel и вогнутые углы двумя строками CSS — без дополнительной разметки и SVG.

📣 Code Ready | #совет
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1410🔥6
👩‍💻 Можно ли стилизовать субтитры к видео с помощью CSS?

Псевдоэлемент::cue позволяет стилизовать текст субтитров и других текстовых дорожек, отображаемых внутри элемента <video>. Эти дорожки подключаются с помощью тега <track>. С помощью ::cue вы можете менять цвет, размер шрифта, фон и другие свойства субтитров, чтобы сделать их более читаемыми или соответствующими дизайну вашего сайта.

Внутри ::cue разрешён ограниченный набор свойств:
background, background-*
color
шрифтовые свойства: font, font-*
outline, outline-*
ruby-position
text-combine-upright
text-decoration, text-decoration-*
text-shadow
visibility
white-space


Поддержка ::cue(<селектор>) может отличаться: в некоторых браузерах он недоступен. Всегда проверяйте поддержку для нужного браузера.

📣 Code Ready | #свойство
Please open Telegram to view this post
VIEW IN TELEGRAM
15👍10🔥6
requestAnimationFrame() — почему время выполнения js нельзя привязывать к фиксированной частоте кадров!

Браузер самостоятельно управляет процессом обновления изображения на экране. JavaScript выполняется через event loop и не синхронизирован напрямую с частотой обновления дисплея.

Поэтому анимации, построенные на фиксированных задержках, могут вести себя по-разному на разных устройствах. setTimeout() не является механизмом синхронизации с браузерным rendering pipeline:
let x = 0;

function animate() {
x += 5;

element.style.transform = `translateX(${x}px)`;

setTimeout(animate, 16);
}

animate();


Значение 16 миллисекунд не означает один кадр. Это только минимальная задержка перед следующим выполнением callback. На дисплее 60 Hz это примерно соответствует одному кадру (~16.67 ms), но на устройствах с частотой 120 Hz, 144 Hz и выше браузер не будет синхронизировать выполнение JavaScript с каждым обновлением экрана.

В результате скорость такой анимации зависит от количества вызовов JavaScript, а не от реально прошедшего времени. Для визуальных обновлений используется requestAnimationFrame():
let x = 0;

function animate() {
x += 5;

element.style.transform = `translateX(${x}px)`;

requestAnimationFrame(animate);
}

requestAnimationFrame(animate);


Браузер планирует callback перед следующим обновлением изображения. Это позволяет выполнять изменения визуального состояния внутри стандартного цикла рендеринга.

Но сама привязка к кадрам не делает анимацию корректной. Если браузер пропустит несколько кадров из-за нагрузки, изменение состояния будет зависеть от количества вызовов requestAnimationFrame(), а не от реального времени.

Для независимости от частоты кадров необходимо использовать timestamp, который передаёт requestAnimationFrame():
let startTime = null;

function animate(timestamp) {
if (startTime === null) {
startTime = timestamp;
}

const elapsed = timestamp - startTime;
const x = elapsed * 0.1;

element.style.transform = `translateX(${x}px)`;

requestAnimationFrame(animate);
}

requestAnimationFrame(animate);


Теперь положение элемента вычисляется через прошедшее время, а не через количество кадров. Такой подход сохраняет одинаковую скорость анимации независимо от того, работает устройство на 60 Hz, 120 Hz или другой частоте обновления.

При этом requestAnimationFrame() не отменяет особенности браузерного rendering pipeline. Например, чтение геометрии элемента сразу после изменения стилей может вызвать forced synchronous layout:
element.style.width = '500px';

const height = element.offsetHeight;


После изменения стиля браузеру необходимо получить актуальное значение layout. Поэтому он может выполнить синхронный перерасчёт перед возвратом значения. В больших интерфейсах такие операции внутри animation loop становятся причиной лишней нагрузки и снижения производительности.

Также необходимо управлять жизненным циклом запущенных анимаций. Если компонент удалён, а callback продолжает создавать новые кадры, приложение продолжает выполнять ненужную работу:
let frameId;
let running = true;

function animate() {
if (!running) return;

update();

frameId = requestAnimationFrame(animate);
}

frameId = requestAnimationFrame(animate);

// cleanup
running = false;
cancelAnimationFrame(frameId);


Кроме того, requestAnimationFrame() не делает любую анимацию автоматически дешёвой. Для оптимальной работы браузеру желательно избегать постоянных layout и paint операций. Свойства вроде transform и opacity чаще позволяют обновлять слой через compositor без полного перерасчёта страницы.

🔥 requestAnimationFrame() — это не просто более плавная альтернатива таймерам. Это механизм синхронизации JavaScript с браузерным циклом обновления изображения, который позволяет выполнять визуальные изменения в правильный момент, рассчитывать анимации через реальное время, учитывать разные частоты обновления экранов и избегать лишних операций при работе с интерфейсом.

📣 Code Ready | #практика
Please open Telegram to view this post
VIEW IN TELEGRAM
11👍10🔥7
👩‍💻 Показываем результат работы программы с помощью <samp>!

Тег <samp> используется для оформления вывода данных, которые показываются пользователю в результате выполнения программы. Содержимое этого элемента воспринимается устройствами как простой текст.

Внутри тега <samp> размещается информация, которую пользователь может получить в процессе взаимодействия с программой:
вывод информации о ходе выполнения программы;
ошибки, предупреждения и подсказки инструментов разработчика или операционной системы;
приглашение к вводу данных.


Важно не путать <samp> с <code>: <code> обозначает фрагмент программного кода, а <samp> — результат, который программа выводит пользователю.

📣 Code Ready | #атрибут
Please open Telegram to view this post
VIEW IN TELEGRAM
11👍8🔥5🤝1
👨‍💻 Интересная статья попалась на Хабре: «o2 game engine»!

В этой статье:
• Познакомитесь с компактным open-source движком для 2D/3D-игр на C++ и JavaScript;
• Разберёте его архитектуру, редактор, UI, физику, анимации и поддержку Windows, Linux, macOS, Android, iOS и Web;
• Узнаете, как в движок интегрирован ИИ, способный помогать создавать игровые механики и самостоятельно тестировать результат.

🔊 Продолжай читать на Habr!


📣 Code Ready | #статья
Please open Telegram to view this post
VIEW IN TELEGRAM
👍107🔥3🤝1
7,2 млн рублей призового фонда и реальные задачи космической отрасли 🚀

В сентябре пройдет серия КосмоХакатонов для студентов, молодых ученых и специалистов.
Участникам предстоит за два дня разработать собственное решение, поработать с экспертами и представить проект жюри.

Участников ждут:
🛰 реальные задачи космической отрасли
👥 команды от 3 до 5 человек
💻 очный и онлайн-форматы
🧑‍💻 работа с экспертами и трекерами
🏆 финал в Москве

Первый КосмоХакатон стартует уже 4 сентября в Ростове-на-Дону.

Дальше серия продолжится в Красноярске, Нижнем Новгороде, Благовещенске и Санкт-Петербурге, а завершится финалом в Москве.

Участие бесплатное. Присоединиться могут студенты, аспиранты, молодые ученые и специалисты от 18 лет.
🔗 Зарегистрироваться: https://космохакатон.рф
dataset: работа с data-* без getAttribute() и setAttribute()!

HTML-атрибуты data-* позволяют хранить данные, непосредственно связанные с конкретным DOM-элементом. Для доступа к таким значениям DOM предоставляет стандартный API dataset.
<button
data-user-id="42"
data-action="delete"
>
Удалить
</button>


Свойство dataset возвращает объект DOMStringMap, через который можно читать значения data-* как свойства JavaScript-объекта.
const button = document.querySelector('button');

console.log(button.dataset.userId);
// "42"

console.log(button.dataset.action);
// "delete"


При формировании имени свойства префикс data- удаляется, а дефис перед строчной ASCII-буквой a-z удаляется, при этом следующая буква преобразуется в верхний регистр. Поэтому data-user-role становится dataset.userRole.
<div
id="user"
data-user-role="admin"
></div>


Это преобразование выполняется самим DOM API, поэтому обращаться к исходному имени атрибута вручную не требуется.
const user = document.querySelector('#user');

console.log(user.dataset.userRole);
// "admin"


dataset поддерживает не только чтение. Присваивание свойства изменяет соответствующий data-* атрибут элемента.
button.dataset.action = 'restore';

console.log(button.getAttribute('data-action'));
// "restore"


Если соответствующего data-* атрибута ещё нет, присваивание через dataset создаст его в DOM.
button.dataset.loading = 'true';

console.log(button.hasAttribute('data-loading'));
// true


Удаление свойства через оператор delete удаляет и соответствующий атрибут элемента.
delete button.dataset.loading;

console.log(button.hasAttribute('data-loading'));
// false


Важная особенность dataset: значения представлены строками. При присваивании числа, boolean и другие значения преобразуются в строковое представление.
button.dataset.userId = 42;
button.dataset.active = true;

console.log(button.dataset.userId);
// "42"

console.log(button.dataset.active);
// "true"

console.log(typeof button.dataset.userId);
// "string"


Если приложению нужен другой тип, преобразование следует выполнять явно. Это особенно важно для чисел и boolean-значений, поскольку строка "false" в JavaScript сама по себе является truthy.
const userId = Number(button.dataset.userId);
const isActive = button.dataset.active === 'true';


Один из практических сценариев для data-* — делегирование событий. При этом использовать event.target.dataset напрямую не всегда корректно: фактической целью события может оказаться вложенный span, svg или другой дочерний элемент.
<ul id="users">
<li>
<button data-user-id="42">
<span>Удалить</span>
</button>
</li>
</ul>


Для такого случая надёжнее найти ближайший подходящий элемент через closest() и дополнительно проверить, что он относится к нужному контейнеру.
const list = document.querySelector('#users');

list.addEventListener('click', event => {
const button =
event.target.closest('[data-user-id]');

if (!button || !list.contains(button)) {
return;
}

const userId =
Number(button.dataset.userId);

removeUser(userId);
});


dataset хорошо подходит для небольших объёмов метаданных, принадлежащих конкретному DOM-элементу: идентификаторов, режимов, состояний интерфейса и параметров поведения.

Для сложных объектов и полноценного состояния приложения data-* использовать не стоит — DOM в таком случае фактически превращается в хранилище состояния, для которого существуют более подходящие структуры данных.
button.dataset.userId = '42';
button.dataset.action = 'delete';
button.dataset.confirm = 'true';


🔥 dataset — стандартный DOM API поверх data-*, который делает работу с пользовательскими атрибутами короче и удобнее, но не обеспечивает типизацию их значений: значения dataset представлены строками и при необходимости требуют явного преобразования.

📣 Code Ready | #практика
Please open Telegram to view this post
VIEW IN TELEGRAM
👍107🔥5
📱 Clipboard API — работа с буфером обмена!

В этой шпаргалке собраны основные методы Clipboard API для программной работы с буфером обмена. Здесь показано, как читать и записывать текстовые данные, работать с HTML, изображениями и другими поддерживаемыми MIME-типами, создавать ClipboardItem с несколькими представлениями данных, проверять поддержку форматов и извлекать содержимое нужного типа, а также использовать legacy-механизм копирования в качестве fallback.

📣 Code Ready | #шпора
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍166🔥6
👩‍💻 Этим атрибутом помечают обязательные поля формы!

Атрибут required добавляется обязательным полям формы. Если поле с таким атрибутом не заполнено, то при отправке формы браузер покажет предупреждение и отменит отправку.

Атрибут required можно использовать для <select>, <textarea>, а также для <input> со следующими типами:
text,
search,
url,
tel,
email,
password,
date,
month,
week,
time,
datetime-local,
number,
checkbox,
radio,
file.


Важно: этот атрибут проверяется только на стороне браузера. Для защиты данных всегда нужна дополнительная проверка на сервере, так как HTML-валидацию можно обойти.

📣 Code Ready | #атрибут
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12🔥9🤝72
This media is not supported in your browser
VIEW IN TELEGRAM
😍 RS School Materials — большая подборка материалов для изучения frontend-разработки!

Здесь собраны полезные ресурсы по HTML, CSS, Flexbox, Grid, JavaScript, React, Git и Computer Science: учебники, статьи, интерактивные курсы, игры и задачи. Отдельные разделы посвящены JavaScript-проектам и подготовке к собеседованиям. Удобная подборка для начинающих.

Оставляю ссылочку: GitHub 📱


📣 Code Ready | #репозиторий
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10🔥6🤝62
Передавай простые значения из HTML прямо в CSS!

Раньше attr() практически ассоциировался только с content. Расширенный синтаксис умеет читать атрибут как конкретный CSS-тип и использовать результат в обычных свойствах.

Например, прогресс можно хранить прямо в разметке:
<div class="progress" data-value="72"></div>


А ширину вычислить без style="width: 72% и без JS:
.progress {
width: calc(attr(data-value type(<number>)) * 1%);
}


То же самое работает с типизированными значениями. Например, компонент может получить цвет из data-*:
.badge {
background: attr(data-color type(<color>), gray);
}


Это удобно для серверного рендера и компонентов, где данные уже находятся в DOM, тк не нужно дублировать их в inline-style или генерировать отдельные классы.

Но пока расширенный attr() для произвольных свойств и type() ещё имеет неполную поддержку браузерами.

🔥 По сути, data-* теперь может быть небольшим типизированным API между разметкой и CSS.

📣 Code Ready | #совет
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14🔥76
❤️ Супер интересная статья попалась на Хабре: «CSS задачи на 200 IQ»!

В этой статье:
• Узнаете, как сделать аналог fixed-элемента внутри прокручиваемого контейнера;
• Разберёте необычный способ передачи параметров в SVG через CSS;
• Посмотрите, как scroll-driven animations помогают решить сложную задачу с автоматическим размером элементов.

🔊 Продолжай читать на Habr!


📣 Code Ready | #статья
Please open Telegram to view this post
VIEW IN TELEGRAM
11👍6🔥4🤝2
Разбираем insertAdjacentHTML() — вставляем HTML без повторного парсинга существующего содержимого!

Для добавления разметки в элемент иногда используют innerHTML +=:
list.innerHTML += '<li>Новый элемент</li>';


Но это не точечная вставка. Браузер получает текущее содержимое как строку, добавляет новую разметку и снова устанавливает innerHTML.

В результате существующие дочерние DOM-узлы могут быть заменены новыми:
const button = list.querySelector('button');

list.innerHTML += '<li>Новый</li>';

console.log(
list.querySelector('button') === button
);
// false


Для вставки HTML без повторного парсинга существующего содержимого есть insertAdjacentHTML():
const button = list.querySelector('button');

list.insertAdjacentHTML(
'beforeend',
'<li>Новый</li>'
);

console.log(
list.querySelector('button') === button
);
// true


Метод принимает позицию вставки и HTML-строку:
beforebegin
afterbegin
beforeend
afterend


Например, вставка первым дочерним элементом:
list.insertAdjacentHTML(
'afterbegin',
'<li>Первый</li>'
);


А afterend вставляет разметку после самого элемента:
list.insertAdjacentHTML(
'afterend',
'<p>После списка</p>'
);


beforebegin и afterend работают относительно родительского элемента, поэтому целевой элемент должен иметь родителя.

Что важно: insertAdjacentHTML() передает строку HTML-парсеру и не выполняет санитизацию. Передача недоверенного HTML создает риск XSS. Для обычного текста используйте textContent или insertAdjacentText(), а недоверенный HTML необходимо корректно санитизировать.

🔥 В отличие от innerHTML +=, insertAdjacentHTML() позволяет вставить новый HTML-фрагмент в нужную позицию, не заменяя уже существующие DOM-узлы.

📣 Code Ready | #практика
Please open Telegram to view this post
VIEW IN TELEGRAM
👍137🔥5
Почему change не всегда подходит для отслеживания изменений в input!

При работе с формами часто пытаются отслеживать ввод через событие change:
input.addEventListener('change', event => {
console.log(event.target.value);
});


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

Если интерфейс должен реагировать непосредственно во время ввода, используется событие input:
input.addEventListener('input', event => {
console.log(event.target.value);
});


Теперь обработчик вызывается при каждом изменении значения:
J
Ja
Jav
Java


При этом input — это не просто реакция на нажатие клавиш. Событие возникает и при других действиях пользователя, например при вставке текста, удалении, вставке через контекстное меню или перемещении текста через drag & drop.

Например, динамический поиск обычно строят так:
search.addEventListener('input', event => {
const query = event.target.value.trim();

filterItems(query);
});


Частая ошибка — использовать keyup как замену input:
input.addEventListener('keyup', handler);


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

У события input есть дополнительная информация через InputEvent:
input.addEventListener('input', event => {
console.log(event.inputType);
});


Например, можно определить, было ли действие вставкой текста, удалением символа или вставкой из буфера обмена.

Если обработчик input запускает тяжёлую операцию или запрос к API, обычно используют debounce, чтобы не выполнять действие после каждого символа:
search.addEventListener(
'input',
debounce(event => {
fetchResults(event.target.value);
}, 300)
);


При этом у change остаются свои правильные сценарии. Например, для <select> он хорошо подходит, потому что важен момент выбора нового значения:
select.addEventListener('change', event => {
updateLanguage(event.target.value);
});


Важный нюанс: программное изменение value само по себе не создаёт событие input:
input.value = 'JavaScript';


Браузер отличает изменение через код от реального пользовательского ввода. Если приложению нужно уведомить другие части системы, событие можно вызвать отдельно:
input.value = 'JavaScript';

input.dispatchEvent(
new Event('input', { bubbles: true })
);


Но такое событие будет синтетическим (isTrusted === false), а не настоящим действием пользователя.

В сложных редакторах также учитывают IME-ввод, например при работе с китайской, японской или корейской раскладкой. Во время композиции можно проверить:
input.addEventListener('input', event => {
if (event.isComposing) {
return;
}

processValue(event.target.value);
});


🔥 Главное правило простое: если нужно реагировать на процесс редактирования в реальном времени — используйте input. Если нужно получить факт завершённого изменения значения — подходит change. Если нужно отслеживать только действия клавиатуры — используйте keyup, понимая его ограничения.

📣 Code Ready | #практика
Please open Telegram to view this post
VIEW IN TELEGRAM
13🤝10👍7🔥1
📱 AbortController — отмена запросов и асинхронных операций!

Разбираем AbortController и AbortSignal, с помощью которых можно отменять fetch-запросы и другие поддерживаемые операции, проверять состояние и причину отмены, прерывать собственную асинхронную логику, задавать таймауты и объединять несколько сигналов отмены. Используется для управления жизненным циклом операций, предотвращения лишней работы и корректной очистки ресурсов.

📣 Code Ready | #шпора
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥19👍9🤝32