#такое дня
Январь — время заполнения годовых Performance Review aka оценки эффективности работы.
Я, честно, в русскоязычном пространстве не работал в компаниях, где они проводились бы. Как в вашей компании это называется?
Как правило, степеньупоротости глубины проработки проблем в отчётах зависит от... да ни от чего она не зависит. Бывает, что в компании на 10 человек проводится оценка по Методу 360 градусов, а бывает, что в огромной корпорации опираются только на число закрытых PR-ов и мнение менеджера.
В любом случае, очень часто приходится писать отчёт на самого себя. С одной стороны, сам себя не похвалишь — никто не похвалит, с другой — почти все мы подвержены синдрому самозванца.
Сегодня я услышал интересное мнение:
— Я просто везде отметил «выше ожиданий» и в комментарии попросил менеджера объяснить, почему он так не считает.
Позиция, как минимум, смелая. Она точно лучше чем отчёт, котором просто бы стояло «Соответствую ожиданиям» и всё.
Но насколько ж сильна вера в менеджера...
В любом случае, мне интересно, проходят ли и как подобные раунды оценки в ваших компаниях?
#work #review
Январь — время заполнения годовых Performance Review aka оценки эффективности работы.
Я, честно, в русскоязычном пространстве не работал в компаниях, где они проводились бы. Как в вашей компании это называется?
Как правило, степень
В любом случае, очень часто приходится писать отчёт на самого себя. С одной стороны, сам себя не похвалишь — никто не похвалит, с другой — почти все мы подвержены синдрому самозванца.
Сегодня я услышал интересное мнение:
— Я просто везде отметил «выше ожиданий» и в комментарии попросил менеджера объяснить, почему он так не считает.
Позиция, как минимум, смелая. Она точно лучше чем отчёт, котором просто бы стояло «Соответствую ожиданиям» и всё.
Но насколько ж сильна вера в менеджера...
В любом случае, мне интересно, проходят ли и как подобные раунды оценки в ваших компаниях?
#work #review
❤8👍4🤩1
This media is not supported in your browser
VIEW IN TELEGRAM
#codepen дня
Давно собирался написать игру по Гарри Поттеру, но не знал, как?
Не расстраивайся, я принёс решение! Steve Gardner и его прекрасное создание Spell Caster!
Вот: https://codepen.io/ste-vg/full/zYerxoR
Я обожаю подобные примеры. Игра буквально укладывается в несколько экранов кода, но красива до безобразия.
Из технологий — Three.js и стейт-машина Stately, позволяющая описывать состояния приложения через удобные диаграммы.
Много комментариев по коду, интересная реализация распознавания жестов. Хоть сейчас бери и в магазин выкладывай :)
Я залип, в общем, как в игре, так и в её исходниках.
#webgl #threejs #game #бородач
Давно собирался написать игру по Гарри Поттеру, но не знал, как?
Не расстраивайся, я принёс решение! Steve Gardner и его прекрасное создание Spell Caster!
Вот: https://codepen.io/ste-vg/full/zYerxoR
Я обожаю подобные примеры. Игра буквально укладывается в несколько экранов кода, но красива до безобразия.
Из технологий — Three.js и стейт-машина Stately, позволяющая описывать состояния приложения через удобные диаграммы.
Много комментариев по коду, интересная реализация распознавания жестов. Хоть сейчас бери и в магазин выкладывай :)
Я залип, в общем, как в игре, так и в её исходниках.
#webgl #threejs #game #бородач
👍10
#тип дня
Типом дня назначается вон тот в кожаной куртке. Да-да, о тебе говорю!
TL;DR: вычисленный тип функции с дженерик-аргументом можно сузить, декларируя тип как const.
Кроме шуток, разве тебя не бесит, что указываешь вот дженерик тип аргумента функции, производишь манипуляции над переданным объектом — а в ответ тебе вычисленный базовый тип?
Непонятно? Давай так:
Какой вычисленный тип получат first и second?
Очевидно, такой:
Но... это же довольно неудобно. А если мы схему валидируем, например? Или форму описываем. Или тесты пишем в конце-концов. Почему при заранее известном константном типе входных данных мы получаем вычисленную строку?
К счастью, начиная с TypeScript 5.0 можно объявлять дженерики как константные типы! Иначе говоря, работать с максимально узкими (narrow) типами. Это, собственно, аналог указания as const у предопределённой константы.
Использование простое:
И на выходе тип у first и second будет такой:
Песочница: пуньк.
Давайте исходя из полученной информации создадим валидатор по известной схеме данных и дальше поработаем с ним:
Определение getSurname приобретёт такой вид:
То есть, обратите внимание, не просто строку на вход, а конкретные, существующие в нужных параметрах строки.
Теперь это вполне себе напоминает удобную работу с коллекциями данных, не стыдно и форму провалидировать.
Песочница: пуньк.
Штука относительно новая, к ней ещё придётся немного привыкнуть. Но уже можно пробовать применять.
Ах да, ссылка на соответствующий PR в TypeScript: https://github.com/microsoft/TypeScript/pull/51865
#typescript #generic #const
Типом дня назначается вон тот в кожаной куртке. Да-да, о тебе говорю!
TL;DR: вычисленный тип функции с дженерик-аргументом можно сузить, декларируя тип как const.
Кроме шуток, разве тебя не бесит, что указываешь вот дженерик тип аргумента функции, производишь манипуляции над переданным объектом — а в ответ тебе вычисленный базовый тип?
Непонятно? Давай так:
function wrapNames <T>(names:T[]) {
return names.map<{name: T}>(name => ({name}));
}
const [first, second] = wrapNames(['Sergey', 'Alex'])
Какой вычисленный тип получат first и second?
Очевидно, такой:
{
name: string;
}
Но... это же довольно неудобно. А если мы схему валидируем, например? Или форму описываем. Или тесты пишем в конце-концов. Почему при заранее известном константном типе входных данных мы получаем вычисленную строку?
К счастью, начиная с TypeScript 5.0 можно объявлять дженерики как константные типы! Иначе говоря, работать с максимально узкими (narrow) типами. Это, собственно, аналог указания as const у предопределённой константы.
Использование простое:
function wrapNames <const T>(names:T[])
И на выходе тип у first и second будет такой:
{
name: "Sergey" | "Alex";
}
Песочница: пуньк.
Давайте исходя из полученной информации создадим валидатор по известной схеме данных и дальше поработаем с ним:
// initValidator
function parse<const T extends { name: string; surname: string }>(users: T[]) {
return (name: T['name']) => users.find(u => u.name === name)?.surname;
}
// validate
const getSurname = parse([
{
name: 'Joe',
surname: 'Doe',
age: 30,
},
{
name: 'John',
surname: 'Dohn'
}
]);
Определение getSurname приобретёт такой вид:
getSurname: (name: "Joe" | "John") => string | undefined
То есть, обратите внимание, не просто строку на вход, а конкретные, существующие в нужных параметрах строки.
Теперь это вполне себе напоминает удобную работу с коллекциями данных, не стыдно и форму провалидировать.
Песочница: пуньк.
Штука относительно новая, к ней ещё придётся немного привыкнуть. Но уже можно пробовать применять.
Ах да, ссылка на соответствующий PR в TypeScript: https://github.com/microsoft/TypeScript/pull/51865
#typescript #generic #const
GitHub
`const` modifier on type parameters by ahejlsberg · Pull Request #51865 · microsoft/TypeScript
With this PR we implement a new const modifier for type parameters. In a function, method, or constructor invocation, when a literal expression in an argument is contextually typed by a const type ...
👍25❤3
#фишка дня
Сегодня на ваших экранах новая серия сериала "Не боги горшки обжигают".
На сей раз отличился Kent C. Dodds, которого уж никак нельзя упрекнуть в незнании фронтенда 🙂
Но давайте разбираться. Что же случилось?
Если пройти по ссылке, то можно понять, что случилась типичная для интернет-каталогов ситуация: карточка товара со ссылкой куда-нибудь ещё.
И да, если раньше всем было вообще пофигу на валидацию — рендерит и ладно — то нынче в дело вступают SSR/SSG и они не позволяют гидрацию невалидного HTML 🥁
А попасть в ситуацию такую ну просто слишком легко: достаточно обозвать пару компонентов как-то иначе, чем просто
Так что же делать-то? Псевдоэлементы к спасению!
Верстаем как обычно, вставляем просто две ссылки: одну на товар, вторую куда-нибудь ещё. И растягиваем псевдоэлемент первой на всю ширину. Как-то так: https://codepen.io/alinaki/pen/OJdJQyB
Все довольны, котята спасены!
#css #trick #card #validation #бородач
Сегодня на ваших экранах новая серия сериала "Не боги горшки обжигают".
На сей раз отличился Kent C. Dodds, которого уж никак нельзя упрекнуть в незнании фронтенда 🙂
Но давайте разбираться. Что же случилось?
Если пройти по ссылке, то можно понять, что случилась типичная для интернет-каталогов ситуация: карточка товара со ссылкой куда-нибудь ещё.
И да, если раньше всем было вообще пофигу на валидацию — рендерит и ладно — то нынче в дело вступают SSR/SSG и они не позволяют гидрацию невалидного HTML 🥁
А попасть в ситуацию такую ну просто слишком легко: достаточно обозвать пару компонентов как-то иначе, чем просто
a
, и всё, потерялись. Нет, кнопки тоже нельзя. Нет, любой интерактивный контент нельзя.Так что же делать-то? Псевдоэлементы к спасению!
Верстаем как обычно, вставляем просто две ссылки: одну на товар, вторую куда-нибудь ещё. И растягиваем псевдоэлемент первой на всю ширину. Как-то так: https://codepen.io/alinaki/pen/OJdJQyB
Все довольны, котята спасены!
#css #trick #card #validation #бородач
👍7
This media is not supported in your browser
VIEW IN TELEGRAM
#фишка дня
Как заставить элемент изменять свою ширину... ступенчато?
Ну, например, вам нужно, чтобы шахматная сетка при любых условиях оставалась цельной.
И сегодня у нас аж несколько вариантов, как этого добиться.
1. От Аны Тюдор: https://codepen.io/thebabydino/pen/zYbZpBq
Используем функцию модуля:
Но вот беда, в Chrome модуль пока не поддерживается... но не унываем! Есть ещё два варианта от Темани Афифа aka CSS Challenges.
2. Эффективное значение функции выше это, буквально, округление. Но так и такая функция в CSS имеется! Как-то так:
Пример: https://codepen.io/alinaki/pen/VwRMpPY
Но... Chrome опять в обломе.
Да, и в первом и во втором вариантах на коне Firefox и Safari. Как я уже недавно писал, парни из Chrome взяли привычку реализовывать только то, что можно красиво оформить в пост. И реализовали тригонометрические функции, забыв про базу.
Потому нам ничего не остаётся кроме как...
3. CSS Grid конечно же: https://codepen.io/t_afif/pen/KKEXwmR
И секрет будет в правиле:
Всем шахмат, котаны :)
#css #trick #math
Как заставить элемент изменять свою ширину... ступенчато?
Ну, например, вам нужно, чтобы шахматная сетка при любых условиях оставалась цельной.
И сегодня у нас аж несколько вариантов, как этого добиться.
1. От Аны Тюдор: https://codepen.io/thebabydino/pen/zYbZpBq
Используем функцию модуля:
width: calc(95vmin + -1*mod(95vmin, 15px));
Но вот беда, в Chrome модуль пока не поддерживается... но не унываем! Есть ещё два варианта от Темани Афифа aka CSS Challenges.
2. Эффективное значение функции выше это, буквально, округление. Но так и такая функция в CSS имеется! Как-то так:
min-width: round(95vmin,15px);
Пример: https://codepen.io/alinaki/pen/VwRMpPY
Но... Chrome опять в обломе.
Да, и в первом и во втором вариантах на коне Firefox и Safari. Как я уже недавно писал, парни из Chrome взяли привычку реализовывать только то, что можно красиво оформить в пост. И реализовали тригонометрические функции, забыв про базу.
Потому нам ничего не остаётся кроме как...
3. CSS Grid конечно же: https://codepen.io/t_afif/pen/KKEXwmR
И секрет будет в правиле:
grid-template-columns: repeat(auto-fill, 15px);
Всем шахмат, котаны :)
#css #trick #math
👍24❤4
#статья дня
Smashing Magazine — один из старейших онлайн-журналов по веб-разработке. Статьи там как правило подбираются весьма подробные и полные, объясняющие все аспекты даже, казалось бы, простых эффектов.
Сегодня в наш канал залетает как раз такая статья: 3D-эффекты для картинок. Блеск, параллакс и поворот.
Весьма впечатляющий разбор, включающий в себя даже немного геометрических расчётов, чтоб вы понимали, откуда ноги растут.
Сразу ссылки на кодпены с примерами, чтоб далеко не ходить:
1. https://codepen.io/t_afif/pen/VwEJqKV
2. https://codepen.io/t_afif/pen/qBJyXNy
3. https://codepen.io/t_afif/pen/yLRRBKj
Ну и, конечно, сама статья: https://www.smashingmagazine.com/2023/07/shines-perspective-rotations-css-3d-effects-images/
Будет полезно не только лишь всем.
#css #3d #image #бородач
Smashing Magazine — один из старейших онлайн-журналов по веб-разработке. Статьи там как правило подбираются весьма подробные и полные, объясняющие все аспекты даже, казалось бы, простых эффектов.
Сегодня в наш канал залетает как раз такая статья: 3D-эффекты для картинок. Блеск, параллакс и поворот.
Весьма впечатляющий разбор, включающий в себя даже немного геометрических расчётов, чтоб вы понимали, откуда ноги растут.
Сразу ссылки на кодпены с примерами, чтоб далеко не ходить:
1. https://codepen.io/t_afif/pen/VwEJqKV
2. https://codepen.io/t_afif/pen/qBJyXNy
3. https://codepen.io/t_afif/pen/yLRRBKj
Ну и, конечно, сама статья: https://www.smashingmagazine.com/2023/07/shines-perspective-rotations-css-3d-effects-images/
Будет полезно не только лишь всем.
#css #3d #image #бородач
❤3👍2🤩2
#детектив дня
Сегодня в чат пришёл подписчик с забавной проблемой.
Дано: форма с двумя полями, одно просто как HTML-тег, второе — React-компонент. Ну, очевидно, компонент тоже содержит поле ввода.
При нажатию на клавишу Ввод событие onSubmit не происходит, форма не отправляется. Хотя, вроде как, всем известно, что это — стандартное поведение.
Было замечено, что при удалении React-компонента всё отлично работает. И первым предположением стало, что React что-то испортил, или песочница кривая.
Кто-то догадался сделать ванильный пример, поведение сохранилось.
То есть, вот такое:
...просто-напросто не работает. Стали копать.
И докопались до спецификации 1995 года для HTML 2.0!
Вот она: https://www.w3.org/MarkUp/html-spec/html-spec_8.html#SEC8.2
И там написано следующее:
Перевожу: чёрным по белому сказано, что форма должна отправляться по кнопке submit (или специальной картинке, но то такое).
То есть, наше предположение о том, что форма всегда должна отправляться по нажатию Ввода — уже неверное.
Но, буквально, в следующем же абзаце:
Так-так-так, а что тут у нас? А тут написано, что единственное текстовое поле — это частный случай и нажатие клавиши ввода должно рассматриваться как отправка формы!
Удивительное рядом, короче. По всей видимости, такое поведение связано с обработкой событий focus и blur, когда полей больше одного.
Короче, так как же решить проблему и не добавлять кнопку?
Очень просто, вставить в форму следующее:
По-моему, пахнет лёгким бредом, но уж как есть.
И да, за много лет веб-разработки мне ни разу не понадобилась форма без кнопки, но расследование вышло забавным.
#html #form #submit
Сегодня в чат пришёл подписчик с забавной проблемой.
Дано: форма с двумя полями, одно просто как HTML-тег, второе — React-компонент. Ну, очевидно, компонент тоже содержит поле ввода.
При нажатию на клавишу Ввод событие onSubmit не происходит, форма не отправляется. Хотя, вроде как, всем известно, что это — стандартное поведение.
Было замечено, что при удалении React-компонента всё отлично работает. И первым предположением стало, что React что-то испортил, или песочница кривая.
Кто-то догадался сделать ванильный пример, поведение сохранилось.
То есть, вот такое:
<form onSubmit="alert('aha')">
<input type="text"/>
<input type="text"/>
</form>
...просто-напросто не работает. Стали копать.
И докопались до спецификации 1995 года для HTML 2.0!
Вот она: https://www.w3.org/MarkUp/html-spec/html-spec_8.html#SEC8.2
И там написано следующее:
When the user indicates that the form should be submitted (using a submit button or image input), the form data set is processed according to its method, action URI and enctype.
Перевожу: чёрным по белому сказано, что форма должна отправляться по кнопке submit (или специальной картинке, но то такое).
То есть, наше предположение о том, что форма всегда должна отправляться по нажатию Ввода — уже неверное.
Но, буквально, в следующем же абзаце:
When there is only one single-line text input field in a form, the user agent should accept Enter in that field as a request to submit the form.
Так-так-так, а что тут у нас? А тут написано, что единственное текстовое поле — это частный случай и нажатие клавиши ввода должно рассматриваться как отправка формы!
Удивительное рядом, короче. По всей видимости, такое поведение связано с обработкой событий focus и blur, когда полей больше одного.
Короче, так как же решить проблему и не добавлять кнопку?
Очень просто, вставить в форму следующее:
<input type="submit" hidden/>
По-моему, пахнет лёгким бредом, но уж как есть.
И да, за много лет веб-разработки мне ни разу не понадобилась форма без кнопки, но расследование вышло забавным.
#html #form #submit
🤩30👍23❤4👎1
#фишка дня
Как легко и быстро добиться эффекта фаски на кнопке без использования вложенных и псевдоэлементов?
Конечно же, использовать градиентный бордер!
— Ну да, конечно, это кто делает градиентные границы без вложенных элементов?
Ну как, кто. Мы и делаем. Всё довольно просто:
1. Задаём нужную нам ширину, используя обычный прозрачный border.
2. Как только мы наложили границу, мы получили доступ к двум свойствам боксовой модели: border-box и padding-box.
Условно, border-box — это весь размер элемента, включая границу. А padding-box — всё до границы.
Итого, первый фон-градиент распространяется на контент, а второй — заходит на созданный нами прозрачный бордер.
➖ нельзя сделать прозрачную кнопку.
➕ можно использовать вообще любую картинку или тип градиента (линейный, радиальный, конический).
Пример: https://codepen.io/alinaki/pen/zYbPBEd
Кто-то готов сделать фаску ещё более выраженной? :)
#css #bevel #emboss #border
Как легко и быстро добиться эффекта фаски на кнопке без использования вложенных и псевдоэлементов?
Конечно же, использовать градиентный бордер!
— Ну да, конечно, это кто делает градиентные границы без вложенных элементов?
Ну как, кто. Мы и делаем. Всё довольно просто:
border: 0.5vw solid transparent;
background: linear-gradient(#17181c, #17181c) padding-box, linear-gradient(to right, #1e1e22, #121316, #1e1e22) border-box;
1. Задаём нужную нам ширину, используя обычный прозрачный border.
2. Как только мы наложили границу, мы получили доступ к двум свойствам боксовой модели: border-box и padding-box.
Условно, border-box — это весь размер элемента, включая границу. А padding-box — всё до границы.
Итого, первый фон-градиент распространяется на контент, а второй — заходит на созданный нами прозрачный бордер.
➖ нельзя сделать прозрачную кнопку.
➕ можно использовать вообще любую картинку или тип градиента (линейный, радиальный, конический).
Пример: https://codepen.io/alinaki/pen/zYbPBEd
Кто-то готов сделать фаску ещё более выраженной? :)
#css #bevel #emboss #border
👍16
#такое дня
Уже довольно поздно. Отметьтесь реакциями, кто ещё онлайн.
Наберём достаточное количество — скину историю смешного бага, который у нас недавно произошёл по невнимательности.
Не наберём — скину её утром. Никуда она не денется.
Upd. Хорошо разогнались. Сейчас всё будет.
Уже довольно поздно. Отметьтесь реакциями, кто ещё онлайн.
Наберём достаточное количество — скину историю смешного бага, который у нас недавно произошёл по невнимательности.
Не наберём — скину её утром. Никуда она не денется.
Upd. Хорошо разогнались. Сейчас всё будет.
🤩67👍36🤡4❤1🤬1
#баг дня
Преамбула
Некий сервис, назовём его Doodle Lampolytics, заставил всех своих клиентов переползти с модели подсчёта просмотров страниц на модель подсчёта событий.
Суть проблемы сервиса очевидна: постраничная модель слабо подходила для серьёзной аналитики данных в веб-приложениях. Даже элементарные лендинги стали гораздо сложнее, чем просто переходы по ссылкам.
И если раньше мы в нагрузку к «просмотру» передавали так называемые индивидуальные размерности и метрики, привязанные к сессии, то теперь — каждое событие может нести индивидуальный их набор.
Не в последнюю очередь это связано с ужесточением запретов на слежку за посетителями сайтов.
Поскольку в типичном проекте зачастую присутствует не одна сотня событий/страниц, то переезд сопровождается знатным перетряхиванием всей аналитики.
Суть
Суд да дело, приходим мы к идее передачи события с неким контекстом:
В одном из постов выше я уже показывал, что этот самый контекст (context) обогащает собой общие для всех событий параметры, превращая событийную модель в некое подобие сессионной.
Передаём туда, например, productId и orderId — и получаем трекинг истории конкретного продукта или заказа. Удобно, когда продукт сильно кастомизированный. Можно накинуть туда optionId и следить, какие продукты какими опциями нашпиговывают.
Таким образом, мы отсылаем объекты вида:
Потом объект из camelCase конвертируется в snake_case, как того требует документация, и дёргается некий API. Запомните этот момент.
И всё, поехали в продакшен. События уходят, счастье. Заодно, те же самые события, за некоторым исключением, можно пихать в сервисы вроде Sentry.
И всё бы ничего, но интерфейс Doodle Lampolytics настолько проклят, что оказалось намного проще выгружать данные оттуда сразу в Doodle LargeQuery и SQL-ем их запрашивать.
Но в какой-то момент времени мне это радикально надоело. Мне нравилась простота старого интерфейса, и я решил разобраться в новом. Проблема была в том, что я там не мог найти не только половину событий, но и их параметры.
Я почитал документацию, нашёл где создавать свои размерности и метрики на основе передаваемых параметров — в админке — захожу туда и наблюдаю следующую картину:
— Какого хера?! — заорал я, я же был в офисе.
Но параметры с ı вместо i исчислялись десятками, в то время как остальные — десятками тысяч. И я зачем-то успокоился. Подумал, что это косяк ламполитики, с которым можно разобраться попозже.
Но сегодня я зашёл в наш прекрасный чат, зачем-то ткнул в аватарку одного из подписчиков и увидел у него в истории некую песню с названием Bakı.
И тут до меня резко дошло, какой я идиот.
Прежде чем читать дальше, вы можете посмотреть на код функции, которой мы преобразуем camelCase в snake_case: https://github.com/jonschlinkert/snakecase/blob/master/index.js
Особо внимательные, наверное, уже догадались, в чём проблема. А ведь мы даже тесты написали.
Поскольку не все пройдут по ссылке, я приведу кусочек:
Ничего не смущает? Я подожду.
...
Да, правильно. Если забыть передать локаль (язык клиента, сервера в нашем случае), то будет взята переменная среды.
И для кого-то из наших клиентов в этой переменной оказался турецкий (или узбекский) язык.
В которых нет буквы i, вместо неё — ı.
Вы все правильно поняли, мы не полностью контролируем среду выполнения. Это так называемый Doodle AppsScript. Он исполняется на языке, в котором открыт офисный инструмент посетителя.
Естественно, мы локаль передать забыли. Хороший вопрос, почему мы вообще не проверили это поведение изначально... но тут как есть.
Стыдно? Стыдно. Зря я винил Doodle Lampolytics.
Преамбула
Некий сервис, назовём его Doodle Lampolytics, заставил всех своих клиентов переползти с модели подсчёта просмотров страниц на модель подсчёта событий.
Суть проблемы сервиса очевидна: постраничная модель слабо подходила для серьёзной аналитики данных в веб-приложениях. Даже элементарные лендинги стали гораздо сложнее, чем просто переходы по ссылкам.
И если раньше мы в нагрузку к «просмотру» передавали так называемые индивидуальные размерности и метрики, привязанные к сессии, то теперь — каждое событие может нести индивидуальный их набор.
Не в последнюю очередь это связано с ужесточением запретов на слежку за посетителями сайтов.
Поскольку в типичном проекте зачастую присутствует не одна сотня событий/страниц, то переезд сопровождается знатным перетряхиванием всей аналитики.
Суть
Суд да дело, приходим мы к идее передачи события с неким контекстом:
trackEvent('event_name', context) {}
В одном из постов выше я уже показывал, что этот самый контекст (context) обогащает собой общие для всех событий параметры, превращая событийную модель в некое подобие сессионной.
Передаём туда, например, productId и orderId — и получаем трекинг истории конкретного продукта или заказа. Удобно, когда продукт сильно кастомизированный. Можно накинуть туда optionId и следить, какие продукты какими опциями нашпиговывают.
Таким образом, мы отсылаем объекты вида:
{
productId,
optionId,
orderId,
locale,
}
Потом объект из camelCase конвертируется в snake_case, как того требует документация, и дёргается некий API. Запомните этот момент.
И всё, поехали в продакшен. События уходят, счастье. Заодно, те же самые события, за некоторым исключением, можно пихать в сервисы вроде Sentry.
И всё бы ничего, но интерфейс Doodle Lampolytics настолько проклят, что оказалось намного проще выгружать данные оттуда сразу в Doodle LargeQuery и SQL-ем их запрашивать.
Но в какой-то момент времени мне это радикально надоело. Мне нравилась простота старого интерфейса, и я решил разобраться в новом. Проблема была в том, что я там не мог найти не только половину событий, но и их параметры.
Я почитал документацию, нашёл где создавать свои размерности и метрики на основе передаваемых параметров — в админке — захожу туда и наблюдаю следующую картину:
product_id,
product_ıd,
option_id,
option_ıd
— Какого хера?! — заорал я, я же был в офисе.
Но параметры с ı вместо i исчислялись десятками, в то время как остальные — десятками тысяч. И я зачем-то успокоился. Подумал, что это косяк ламполитики, с которым можно разобраться попозже.
Но сегодня я зашёл в наш прекрасный чат, зачем-то ткнул в аватарку одного из подписчиков и увидел у него в истории некую песню с названием Bakı.
И тут до меня резко дошло, какой я идиот.
Прежде чем читать дальше, вы можете посмотреть на код функции, которой мы преобразуем camelCase в snake_case: https://github.com/jonschlinkert/snakecase/blob/master/index.js
Особо внимательные, наверное, уже догадались, в чём проблема. А ведь мы даже тесты написали.
Поскольку не все пройдут по ссылке, я приведу кусочек:
const lowercase = (input = '', options) => input.toLocaleLowerCase(options?.locale);
Ничего не смущает? Я подожду.
...
Да, правильно. Если забыть передать локаль (язык клиента, сервера в нашем случае), то будет взята переменная среды.
И для кого-то из наших клиентов в этой переменной оказался турецкий (или узбекский) язык.
В которых нет буквы i, вместо неё — ı.
Вы все правильно поняли, мы не полностью контролируем среду выполнения. Это так называемый Doodle AppsScript. Он исполняется на языке, в котором открыт офисный инструмент посетителя.
Естественно, мы локаль передать забыли. Хороший вопрос, почему мы вообще не проверили это поведение изначально... но тут как есть.
Стыдно? Стыдно. Зря я винил Doodle Lampolytics.
🤩23👍9❤3
#заметка дня
Иногда полезно спорить с мэтрами по поводу разных штук. Не стоит всё подряд принимать на веру.
Вот, например, Кори Хаус, весьма известный консультант по React, предложил буквально следующее: чтобы не запутаться в бесконечных хуках useEffect в React, вместо неименованной функции передавайте именованную.
То бишь, вместо:
Предлагается
На первый взгляд выглядит разумно, не правда ли?
Самодокументирующийся код без комментариев, хорошо видно намерение, сразу понятно, используется или нет.
Так вот, товарищи. Это далеко не самое лучшее решение, да ещё и многословное. Гораздо более разумно будет создать свой собственный хук.
Что такое свой собственный хук? Вы не поверите, это буквально тот же самый useEffect, но вынесенный в функцию:
Да, внутри функции кастомного хука можно держать useState, обращаться к внешним источникам данных и возвращать что угодно. Это, например, рекомендуемый способ использования того же React Query и официальная рекомендация команды React для переиспользования логики: https://react.dev/learn/reusing-logic-with-custom-hooks
Почему такой подход лучше?
Потому что, обладая всеми преимуществами варианта, предложенного Кори, он не только сокращает размер основного компонента но и легко тестируется!
Да, представьте себе, вы задолбаетесь правильно тестировать компонент с даже двумя-тремя встроенными хуками. А вынесенные отдельно — не только легко мокаются и подменяются, но и прекрасно тестируются сами по себе.
Так что команда React могла бы назвать раздел документации не "как переиспользовать", а "как тестировать". Это важнее.
В общем, не забываем про голову, котаны. Готов выслушать ваши сомнения, впрочем 🙂
#react #javascript #hooks
Иногда полезно спорить с мэтрами по поводу разных штук. Не стоит всё подряд принимать на веру.
Вот, например, Кори Хаус, весьма известный консультант по React, предложил буквально следующее: чтобы не запутаться в бесконечных хуках useEffect в React, вместо неименованной функции передавайте именованную.
То бишь, вместо:
useEffect(() => {
// do stuff
}, [...deps]);
Предлагается
useEffect(function doSomething() {
// do stuff
}, [...deps]);
На первый взгляд выглядит разумно, не правда ли?
Самодокументирующийся код без комментариев, хорошо видно намерение, сразу понятно, используется или нет.
Так вот, товарищи. Это далеко не самое лучшее решение, да ещё и многословное. Гораздо более разумно будет создать свой собственный хук.
Что такое свой собственный хук? Вы не поверите, это буквально тот же самый useEffect, но вынесенный в функцию:
function useDoSomething(deps) {
useEffect(() => {
// some effect
}, [...deps]);
}
Да, внутри функции кастомного хука можно держать useState, обращаться к внешним источникам данных и возвращать что угодно. Это, например, рекомендуемый способ использования того же React Query и официальная рекомендация команды React для переиспользования логики: https://react.dev/learn/reusing-logic-with-custom-hooks
Почему такой подход лучше?
Потому что, обладая всеми преимуществами варианта, предложенного Кори, он не только сокращает размер основного компонента но и легко тестируется!
Да, представьте себе, вы задолбаетесь правильно тестировать компонент с даже двумя-тремя встроенными хуками. А вынесенные отдельно — не только легко мокаются и подменяются, но и прекрасно тестируются сами по себе.
Так что команда React могла бы назвать раздел документации не "как переиспользовать", а "как тестировать". Это важнее.
В общем, не забываем про голову, котаны. Готов выслушать ваши сомнения, впрочем 🙂
#react #javascript #hooks
👍19🤡1
#такое дня
Решил принести вам на ночь. В 2013 году в официальной документации React предлагалось использовать jQuery для AJAX-запросов, а в качестве mock-сервера использовать встроенный в Python HTTP-сервер.
Правда, я, помню, использовал для таких задач сервер, встроенный в PHP... ну кто что умел.
С тех пор прошло 10 лет. Где мы теперь, котаны? :)
Решил принести вам на ночь. В 2013 году в официальной документации React предлагалось использовать jQuery для AJAX-запросов, а в качестве mock-сервера использовать встроенный в Python HTTP-сервер.
Правда, я, помню, использовал для таких задач сервер, встроенный в PHP... ну кто что умел.
С тех пор прошло 10 лет. Где мы теперь, котаны? :)
🤩6
#инструмент дня
Раз уж упомянули пару постов назад Кори Хауса, то давайте ещё чего-нибудь по его рекомендации.
И сегодня это инструмент для поиска дубликатов кода: jscpd.
Сразу ссылка на GitHub: https://github.com/kucherenko/jscpd
Как получаются дубликаты, клоны кода? Да от плохих абстракций. Сами по себе они ничем не плохи, ну есть у вас повторы там-сям, кому от этого плохо?
Ну, пока лежат — никому. Но если в одном таком куске кода баг — это лишь вопрос времени, когда он себя проявит в другим таком же куске. А чинить-то его везде надо.
Ну и на фронтенде банально раздувается размер бандла. Тоже такое себе.
Вообще, клонированный код это показатель того, что где-то у вас плохо с абстракциями, одна и та же логика применяется к, казалось бы, разным сущностям и так далее. Само по себе, это не так плохо, не просто же так в оппозиции к DRY (do not repeat yourself) существует WET (write everything twice).
Но иметь инструмент для поиска совсем уж лишнего — весьма неплохо.
А для тех, кому надо докопаться до сути, имеется целое исследование по поиску клонов кода, на 115 страниц: https://research.cs.queensu.ca/TechReports/Reports/2007-541.pdf
Но все, что я могу сказать — это что jscpd является реализацией детектора копий первого типа 🫠
Пользуется кто-нибудь чем-то подобным в своих проектах? Делитесь.
#js #clone #lint
Раз уж упомянули пару постов назад Кори Хауса, то давайте ещё чего-нибудь по его рекомендации.
И сегодня это инструмент для поиска дубликатов кода: jscpd.
Сразу ссылка на GitHub: https://github.com/kucherenko/jscpd
Как получаются дубликаты, клоны кода? Да от плохих абстракций. Сами по себе они ничем не плохи, ну есть у вас повторы там-сям, кому от этого плохо?
Ну, пока лежат — никому. Но если в одном таком куске кода баг — это лишь вопрос времени, когда он себя проявит в другим таком же куске. А чинить-то его везде надо.
Ну и на фронтенде банально раздувается размер бандла. Тоже такое себе.
Вообще, клонированный код это показатель того, что где-то у вас плохо с абстракциями, одна и та же логика применяется к, казалось бы, разным сущностям и так далее. Само по себе, это не так плохо, не просто же так в оппозиции к DRY (do not repeat yourself) существует WET (write everything twice).
Но иметь инструмент для поиска совсем уж лишнего — весьма неплохо.
А для тех, кому надо докопаться до сути, имеется целое исследование по поиску клонов кода, на 115 страниц: https://research.cs.queensu.ca/TechReports/Reports/2007-541.pdf
Но все, что я могу сказать — это что jscpd является реализацией детектора копий первого типа 🫠
Пользуется кто-нибудь чем-то подобным в своих проектах? Делитесь.
#js #clone #lint
👍14
#фишка дня
Даже две, но об одном и том же: как сделать "альбомный" бэкдроп у картинки.
Что такое бэкдроп? Это фон, который полностью зависит от контента, например, заливается усредненным цветом. Вы наверняка видели похожие эффекты в разных аудиоплеерах, отсюда и название — альбомный. Да и на ютубе раньше было модно заполнять края у вертикальных видео тем же размытым видео.
Итак, два варианта.
1. От Артура Бьена. Надо предупредить, он немного упоротый:
а) заполняем фон контейнера однопиксельными копиями картинки:
б) накладываем сверху псевдоэлемент с небольшим тюном цвета:
в) если что-то надо наложить сверху, стоит использовать режим смешивания overlay, чтобы не вляпаться в идентичный цвет.
Пример: https://codepen.io/alinaki/pen/MWxrjZV
2. Всё бы хорошо, но эти бесконечные копии изображения... ох. Поэтому, есть достаточно интересный вариант от Темани Афифа aka CSS Challenges.
Будем использовать border-image. Отгрызаем полоски изображения по границе и повторяем их:
Полученный эффект лучше всего смотрится на небольшого размера блоках: https://codepen.io/alinaki/pen/LYaeRvw
По-моему, стоит поиграть с настройками размытия ещё чуток, но эффект точно интереснее.
Если вы не знакомы с border-image, вот свежая статья от автора эффекта, собственно: https://www.smashingmagazine.com/2024/01/css-border-image-property/
Ну и, конечно, можно просто размыть картинку саму за собой. Но это было бы слишком скучно. Во втором варианте даже присутствует эффект Philips Ambilight.
#css #trick #backdrop #blur
Даже две, но об одном и том же: как сделать "альбомный" бэкдроп у картинки.
Что такое бэкдроп? Это фон, который полностью зависит от контента, например, заливается усредненным цветом. Вы наверняка видели похожие эффекты в разных аудиоплеерах, отсюда и название — альбомный. Да и на ютубе раньше было модно заполнять края у вертикальных видео тем же размытым видео.
Итак, два варианта.
1. От Артура Бьена. Надо предупредить, он немного упоротый:
а) заполняем фон контейнера однопиксельными копиями картинки:
background-image: url(var(--bg));
background-size: 1px 1px;
background-repeat: repeat;
б) накладываем сверху псевдоэлемент с небольшим тюном цвета:
div::after {
--backdrop: invert(0.2) contrast(0.8) saturate(1.7) blur(8px);
backdrop-filter: var(--backdrop);
}
в) если что-то надо наложить сверху, стоит использовать режим смешивания overlay, чтобы не вляпаться в идентичный цвет.
mix-blend-mode: overlay;
Пример: https://codepen.io/alinaki/pen/MWxrjZV
2. Всё бы хорошо, но эти бесконечные копии изображения... ох. Поэтому, есть достаточно интересный вариант от Темани Афифа aka CSS Challenges.
Будем использовать border-image. Отгрызаем полоски изображения по границе и повторяем их:
div: before {
content:"";
border- image:
var (--img) 2/
calc(50% - var (--w)/2 + var (--b)) /
calc (1.5*var(--b));
filter: contrast(.8) blur(var(--b));
}
Полученный эффект лучше всего смотрится на небольшого размера блоках: https://codepen.io/alinaki/pen/LYaeRvw
По-моему, стоит поиграть с настройками размытия ещё чуток, но эффект точно интереснее.
Если вы не знакомы с border-image, вот свежая статья от автора эффекта, собственно: https://www.smashingmagazine.com/2024/01/css-border-image-property/
Ну и, конечно, можно просто размыть картинку саму за собой. Но это было бы слишком скучно. Во втором варианте даже присутствует эффект Philips Ambilight.
#css #trick #backdrop #blur
❤25👍2🤩2
This media is not supported in your browser
VIEW IN TELEGRAM
#статья дня
На связи опять стилизация скроллбаров. Мы только недавно обсудили, как избежать некоторых связанных с этим багов в Safari, как подоспел Chrome 121 с интересным обновлением.
И что он нам принёс? Вы не поверите, поддержу стандартных правил scrollbar-width и scrollbar-color.
В Firefox они с 2018 года, а Chrome всё это время использовал правила от WebKit. Впрочем, все пользовались и было пофигу.
Пример:
Естественно, по этому поводу блог разработчиков Google Chrome разродился огромной статьёй, в которой прошёлся по типам скроллбаров (оверлей и классический), по анатомии скроллбара (трек, движок/лифт, кнопки) и по особенностям совместимости со старыми правилами с префиксом -webkit (Safari всё там же, да).
Ссылка на статью: https://developer.chrome.com/docs/css-ui/scrollbar-styling
Мне стандарт не нравится, он весьма ограничен по своим возможностям и результат получается так себе. Надеюсь, это временно и релиз означает, что дело сдвинулось с мёртвой точки.
#css #scrollbar
На связи опять стилизация скроллбаров. Мы только недавно обсудили, как избежать некоторых связанных с этим багов в Safari, как подоспел Chrome 121 с интересным обновлением.
И что он нам принёс? Вы не поверите, поддержу стандартных правил scrollbar-width и scrollbar-color.
В Firefox они с 2018 года, а Chrome всё это время использовал правила от WebKit. Впрочем, все пользовались и было пофигу.
Пример:
.scroller {
--scrollbar-color-thumb: hotpink;
--scrollbar-color-track: blue;
--scrollbar-width: thin;
--scrollbar-width-legacy: 10px;
}
/* Modern browsers with `scrollbar-*` support */
@supports (scrollbar-width: auto) {
.scroller {
scrollbar-color: var(--scrollbar-color-thumb) var(--scrollbar-color-track);
scrollbar-width: var(--scrollbar-width);
}
}
Естественно, по этому поводу блог разработчиков Google Chrome разродился огромной статьёй, в которой прошёлся по типам скроллбаров (оверлей и классический), по анатомии скроллбара (трек, движок/лифт, кнопки) и по особенностям совместимости со старыми правилами с префиксом -webkit (Safari всё там же, да).
Ссылка на статью: https://developer.chrome.com/docs/css-ui/scrollbar-styling
Мне стандарт не нравится, он весьма ограничен по своим возможностям и результат получается так себе. Надеюсь, это временно и релиз означает, что дело сдвинулось с мёртвой точки.
#css #scrollbar
👍11
This media is not supported in your browser
VIEW IN TELEGRAM
#фишка дня
Стопудово вы делали эффекты как на видео через три div-а или span-а. Ну просто потому что трансформации на SVG это абсолютная боль.
Типа такого: https://codepen.io/alinaki/pen/abXpvyQ
Да, пример очень простой, но даже это на SVG бывает проблемно санимировать.
Хотя, казалось бы, для этого и предназначено.
А вся проблема в том, что для SVG определение координат для преобразований происходит немного иначе, нежели чем для элементов. Выходов из ситуации есть несколько.
Первый, от Аны Тюдор: исправить viewBox, поставив вместо 0, 0 (левый верхний угол) — -width/2,-height/2, соответственно, исправив остальные координаты.
Второй, интереснее, от Джея: указать следующие правила в CSS:
Правило transform-box исправит положение координатной сетки, от которой мы уже сменим дефолтную точку отсчёта для преобразований — transform-origin.
По-умолчанию, кстати, transform-box установлен как view-box. То есть, в нашем примере, заполняет лишь 24 пикселя по каждой стороне🤡
А вот и, собственно, пример:
https://codepen.io/alinaki/pen/YzBNyEz
Не бойтесь анимировать SVG, котаны. Просто не полагайтесь на дефолт.
#css #transition #svg #бородач
Стопудово вы делали эффекты как на видео через три div-а или span-а. Ну просто потому что трансформации на SVG это абсолютная боль.
Типа такого: https://codepen.io/alinaki/pen/abXpvyQ
Да, пример очень простой, но даже это на SVG бывает проблемно санимировать.
Хотя, казалось бы, для этого и предназначено.
А вся проблема в том, что для SVG определение координат для преобразований происходит немного иначе, нежели чем для элементов. Выходов из ситуации есть несколько.
Первый, от Аны Тюдор: исправить viewBox, поставив вместо 0, 0 (левый верхний угол) — -width/2,-height/2, соответственно, исправив остальные координаты.
Второй, интереснее, от Джея: указать следующие правила в CSS:
transform-box: fill-box;
transform-origin: 50% 50%;
Правило transform-box исправит положение координатной сетки, от которой мы уже сменим дефолтную точку отсчёта для преобразований — transform-origin.
По-умолчанию, кстати, transform-box установлен как view-box. То есть, в нашем примере, заполняет лишь 24 пикселя по каждой стороне
А вот и, собственно, пример:
https://codepen.io/alinaki/pen/YzBNyEz
Не бойтесь анимировать SVG, котаны. Просто не полагайтесь на дефолт.
#css #transition #svg #бородач
Please open Telegram to view this post
VIEW IN TELEGRAM
👍18
#новость дня
Итак, мы писали Джаваскрипт в браузере, в консоли, на серверах, на микроконтроллерах, в играх, в кофеварках, в аудиоплеерах, телевизорах, в макросах офисных пакетов, в NoSQL базах данных...
Но до сих пор не писали его в хранимых процедурах MySQL!
Итак, встречайте: JavaScript Stored Programs in MySQL. И соответствующий пост в блоге Oracle: https://blogs.oracle.com/mysql/post/introducing-javascript-support-in-mysql
Зачем? Ну для разных целей:
1. Извлечение данных, очевидно
2. Форматирование
3. Примерный поиск
4. Валидация данных
5. Собственные алгоритмы сжатия, сериализации и десериализации данных
Пример:
Работает всё, ожидаемо, на GraalVM. Почему ожидаемо? Ну потому что Oracle и Java/JVM :)
Что, котаны, пишем стартап на MySQL? 😬
#mysql #sql #js
Итак, мы писали Джаваскрипт в браузере, в консоли, на серверах, на микроконтроллерах, в играх, в кофеварках, в аудиоплеерах, телевизорах, в макросах офисных пакетов, в NoSQL базах данных...
Но до сих пор не писали его в хранимых процедурах MySQL!
Итак, встречайте: JavaScript Stored Programs in MySQL. И соответствующий пост в блоге Oracle: https://blogs.oracle.com/mysql/post/introducing-javascript-support-in-mysql
Зачем? Ну для разных целей:
1. Извлечение данных, очевидно
2. Форматирование
3. Примерный поиск
4. Валидация данных
5. Собственные алгоритмы сжатия, сериализации и десериализации данных
Пример:
CREATE FUNCTION gcd_js (a INT, b INT) RETURNS INT
LANGUAGE JAVASCRIPT AS $$
let [x, y] = [Math.abs(a), Math.abs(b)];
while(y) [x, y] = [y, x % y];
return x;
$$;
Работает всё, ожидаемо, на GraalVM. Почему ожидаемо? Ну потому что Oracle и Java/JVM :)
Что, котаны, пишем стартап на MySQL? 😬
#mysql #sql #js
🤡10🤩4👍1
#заметка дня
Есть такой элемент —
Пишете в него некий HTML/SVG код, потом клонируете и вставляете куда вам надо.
На этом месте апологеты фреймворков ухмыльнулись, но вы же не одни на свете, да?
Важно понимать, что содержимое шаблона — это фрагмент (обёртка над реальными нодами, если грубо), поэтому обращаться с ним надо соответственно.
Почему для этой цели не использовать просто скрытый
Ну второе вообще не в кассу, это просто абьюз. А первое:
- Контент внутри темплейта не будет загружаться, пока не склонирован явно: ни картинки, ни стили, ни скрипты
- Валидатор игнорирует содержимое, хоть
- Поисковые системы знают про
- Ну и именование говорит само за себя. Сахар все любят.
#html #template #бородач
Есть такой элемент —
template
. Название говорит само за себя: это шаблон. Пишете в него некий HTML/SVG код, потом клонируете и вставляете куда вам надо.
На этом месте апологеты фреймворков ухмыльнулись, но вы же не одни на свете, да?
const content = template.content. firstElementChild.cloneNode(true);
Важно понимать, что содержимое шаблона — это фрагмент (обёртка над реальными нодами, если грубо), поэтому обращаться с ним надо соответственно.
Почему для этой цели не использовать просто скрытый
div
или — привет из прошлого — script type="text/template"
?Ну второе вообще не в кассу, это просто абьюз. А первое:
- Контент внутри темплейта не будет загружаться, пока не склонирован явно: ни картинки, ни стили, ни скрипты
- Валидатор игнорирует содержимое, хоть
dd
храните там, хоть td
- Поисковые системы знают про
template
и точно не индексируют. А с div hidden
всё не так однозначно.- Ну и именование говорит само за себя. Сахар все любят.
#html #template #бородач
👍11❤3
#тип дня
Кричащий банан 🍌 (да-да) принёс шикарную фишку TypeScript: как типизировать ключ объекта без каста.
Решение: заранее объявите тип переменной перебора как
Пруф на TS Playground.
#typescript #cast #бородач
Кричащий банан 🍌 (да-да) принёс шикарную фишку TypeScript: как типизировать ключ объекта без каста.
Решение: заранее объявите тип переменной перебора как
keyof typeof
. Всё, вы великолепны.Пруф на TS Playground.
#typescript #cast #бородач
👍22❤4
#число дня
В дружественном чате прошло очередное ежеквартальное обсуждение разрешений макетов и экранов.
Я не буду пока репостить свои статьи на тему, лишь задам вопрос.
Кто догадается, что такое 1536x864 на этой годовой статистике разрешений экранов мониторов?
Между прочим, 11% дисплеев.
https://gs.statcounter.com/screen-resolution-stats/desktop/worldwide
При встрече угощу кофе того, кто догадается первым.
Ну или кого вообще встречу.
#css #resolution
В дружественном чате прошло очередное ежеквартальное обсуждение разрешений макетов и экранов.
Я не буду пока репостить свои статьи на тему, лишь задам вопрос.
Кто догадается, что такое 1536x864 на этой годовой статистике разрешений экранов мониторов?
Между прочим, 11% дисплеев.
https://gs.statcounter.com/screen-resolution-stats/desktop/worldwide
При встрече угощу кофе того, кто догадается первым.
Ну или кого вообще встречу.
#css #resolution
👍6
#статья дня
Злые Марсиане выпускают крутейшие статьи с завидной регулярностью. Вот очередная: https://evilmartians.com/chronicles/html-best-practices-for-login-and-signup-forms
На сей раз — про ошибки при разработке форм. И их, согласно статье, 11. Давайте кратко пробежимся.
1. Ставь autocomplete в нужное положение (username, current-password etc.).
Мы тут, кстати, обсуждали возможные значения и даже проблемы.
2. type="email", тут всё ясно.
3. Интерактивные элементы должны быть кнопками или ссылками, а не дивами (и ведь находятся же).
4. Не забывай про существование тега form (и такие тоже существуют).
5. Не используй placeholder как label.
6. Помни о label, особенно для галочек.
7. Состояние :focus должно быть визуально подтверждено.
8. Маркируй невидимые поля для скринридеров.
9. Не прерывай ввод внезапной валидацией (бесит).
10. Исключи случайную повторную отправку формы.
11. Помни о сетевых задержках.
В статье полно примеров и объяснений. Проверяйте свои формы, котаны.
#form #ux #бородач
Злые Марсиане выпускают крутейшие статьи с завидной регулярностью. Вот очередная: https://evilmartians.com/chronicles/html-best-practices-for-login-and-signup-forms
На сей раз — про ошибки при разработке форм. И их, согласно статье, 11. Давайте кратко пробежимся.
1. Ставь autocomplete в нужное положение (username, current-password etc.).
Мы тут, кстати, обсуждали возможные значения и даже проблемы.
2. type="email", тут всё ясно.
3. Интерактивные элементы должны быть кнопками или ссылками, а не дивами (и ведь находятся же).
4. Не забывай про существование тега form (и такие тоже существуют).
5. Не используй placeholder как label.
6. Помни о label, особенно для галочек.
7. Состояние :focus должно быть визуально подтверждено.
8. Маркируй невидимые поля для скринридеров.
9. Не прерывай ввод внезапной валидацией (бесит).
10. Исключи случайную повторную отправку формы.
11. Помни о сетевых задержках.
В статье полно примеров и объяснений. Проверяйте свои формы, котаны.
#form #ux #бородач
❤20