IT — Размеры SVG-элементов в реальных пикселах
При использовании динамической SVG-графики, может возникнуть ситуация, когда удобно работать с каким-нибудь конкретным viewBox-ом, указав ту координатную сетку, с координатами и размерами которой будет удобно работать. Однако не все векторные примитивы вам может захотеться рисовать в масштабе этой сетки. Какие-то из них вы пожелаете сохранить в пикселном представлении. Что же делать?
- Можно поменять viewBox таким образом, что бы 1-ца сетки в нём соответсвовала 1px на канве. Что далеко не всегда бывает удобным вариантом.
- Можно подсчитать кол-во реальных px-й на 1 сетки viewBox-а и исходить из них.
В этом посту я остановлюсь на 2-ом варианте. Итак, с чего начнём? Начнём с того, что для границ-рамок-как-угодно-называйте, в общем для stroke, есть уже готовый механизм: vector-effect: non-scaling-stroke. Указав это свойство ваши stroke-width будут задаваться в реальных пикселах, а не в масштабе SVG.
А что делать, скажем, с радиусом для circle? Положим мы хотим нарисовать точку, но хотим чтобы её размер в px-ах не зависел от размеров канвы и масштаба на ней. Ну для начала посчитаем кол-во пикселей на 1-цу сетки:
const { width: wpx, height: hpx } = svg.getBoundingClientRect();
const kx = wpx / wi;
const ky = hpx / hi;
Где wi и hi это координатные width и height для viewBox. Если kx === 10, то на 1-цу сетки у вас 10 пикселей.
Ок, что дальше? Можно использовать эти коэффициенты на стороне JS, а можно отдать почти всё на откуп CSS, воспользовавшись CSS-variables. Например так:
<svg viewBox="" style={{ '--var-kx': kx }}/>
а в CSS используем calc:
circle { r: calc(5 / var(--var-kx)); }
UPD: Firefox не умеет r в css :(
Хотелось бы, конечно, чего-то вроде vector-effect: non-scaling и для других полей. Но меня устроил и этот вариант.
IT — Тернистый путь расширений в Chrome
Довольно часто я сталкиваюсь с тем, что сторонние сайты работают не совсем так, как я бы хотел. И будучи web-программистом я пытаюсь на это дело как-то повлиять. Но одно дело, скажем, стили сайту подправить, и совсем другое вмешаться в его работу JavaScript-ом. Эта заметка о втором.
Итак. На этот раз жертвой пал puzzle-english. Ресурс для изучения английского. Среди прочего там есть возможно уникальная возможность тренировать аудирование на небольших заранее заготовленных фразах, записанных от нескольких дикторов. Предлагается на выбор по 3-5 слов на каждое произнесённое слово. Итого ты или кликаешь по кнопкам мышью, собирая фразу, или с клавиатуры клавишами цифр. Всё бы ничего, если бы не отсутствие поддержки чисел с num-панели. Поддерживаются только те, что над буквами. Что, лично для меня, страшно неудобно. Ну что ж, приступим.
Для начала я решил узнать как оно там вообще работает. Посмотрел все eventListener-ы на document и нашёл нужный методом перебора при debug-е. Он слушает keydown и несколько неуклюже обрабатывает event.which, поддерживая только один диапазон для чисел. Поиграл с ним и понял, что при "патченном" which num-цифры работают прекрасно. Написал небольшой скрипт в консоли примерно следующего толку и решил что дело в шляпе:
document.addEventListener('keydown', evt =>
{
if(evt.which > 96 && evt.which < 105)
{
const which = evt.which - 48;
$.event.trigger(
{
type: 'keydown',
which,
originalEvent: { target: document.body }
});
}
});
Забыл упомянуть, puzzle-english агрессивно использует jQuery. Чем я не применул воспользоваться. Там ведь свои обёртки и свои рецепты. Обернул этот код расширением и... Обломался. Chrome не позволяет использовать со страницы жертвы ничего кроме DOM. Т.е. доступа к jQuery нет, доступа к любым кастомным window-полям тоже нет. Ничего нет.
Ну не беда, подумал я и решил организовать keydown средствами браузера. Что у нас там есть? document.createEvent и initKeyboardEvent. Поковырялся — работает плохо. Да ещё и deprecated. Ок, а как ещё можно? Ага, у нас есть класс KeyboadEvent. Ok:
new KeyboardEvent('keydown', { which: 50 });
Ага. Нифига. В документации одно, в браузере другое. Плевал он на which и прочие поля. Что пишут нам интернеты? Ух, дичь какая... Зато работает:
window.evt = evt = document.createEvent("KeyboardEvent");
evt.initKeyboardEvent('keydown',
true, false, null, 0, false,
0, false, which, 0);
evt.__defineGetter__('which', () => which);
document.dispatchEvent(evt);
Правда только из консоли. Почему? Потому что браузер заменяет отправленные из расширения события их копиями в основной тред. В целях безопасности. И того мы снова теряем which.
Что в итоге? В итоге вспомнил, что там ещё мышью можно было кликать. Стало быть можно эмулировать клик. Просто вызвав у DOMElement-а метод click. С этим уже граблей не было.
В общем мораль такова: расширения скованы цепями. Попытки сделать что-то просто — могут упереться в открытые баги или просто запреты. Так и живём ;)
IT — Лигатуры
Иногда на чужих скриншотах я замечал, что привычные программистам конструкции вроде === и != могут быть отображены одним изящным глифом, вместо трёх разных. А тут наткнулся на то, что у человека такие штуки заработали в SublimeText 3. Стал разбираться как подключить и выяснил:
- Эти штуки называются лигатурами. И ваш текстовый редактор (или терминал) должен иметь их поддержку. Большинство современных профессиональных инструментов такую поддержку имеют.
- SublimeText 3 обзавёлся такой поддержкой совсем недавно. С версии 3146
- Необходимо установить в систему шрифт с поддержой лигатур. Варианты: Fira Code, Monoid, Iosevka и др. В целом их мало.
- В sublime-е шрифт меняется настройкой font_face.
О остановился на Fira Code. Начертание не привычное, оно отличается от того шрифта, что используется в st3 по умолчанию. Но в целом очень даже ничего. Вот его рекламная брошюрка:
IT — Простейший способ выгрузить все свои слова с lingualeo
Рецепт не для программистов, но относительно опытных пользователей ПК:
- Открываем раздел Dictionary (Словарь) на lingualeo
- Проматываем словарь до упора вниз, чтобы сайт отобразил все слова (он их лениво подгружает)
- Открываем консоль разработчика (F12 или Ctrl+Shift+I)
- Выбираем в открывшейся панели таб "Console"
- Вставляем туда следующую команду: copy(Array.from(document.querySelectorAll('.dict-item-word')).map(el => [el.getAttribute('data-word-value'), el.querySelector('.translates').innerText]))
- Открываем этот или любой подобный сайт (который умеет конвертировать JSON в нужный вам формат)
- Вставляем в текстовую область через Ctrl+V (Cmd+V) текст из буфера обмена (в п.5 туда были помещены все слова в формате JSON)
- Кликаем по ссылке "Download the entire CSV" и сохраняем получившийся файл куда вам нужно (можно пересохранить его в нужный формат, поменять кодировку и пр. (по умолчанию UTF8)).
В случае если разработчики поменяют вёрстку ― п.5 поломается :-) Увы
IT — Конфигурация mocha и mocha.opts
Случайно наткнулся на то, что можно вынести command line parameters для консольного запуска mocha в отдельный файл test/mocha.opts. Но тут же наткнулся на грабли. Файл должен лежать именно в test/mocha.opts, а у меня директория тестов называлась tests, и этого хватило. Т.е. механизм поиска файла сильно уступает аналогичному для, скажем, .eslintrc или .gitignore. Сам формат файла простой, пример:
--require test/setup.js --compilers js:babel-core/register
Теперь mocha можно запускать без параметров. Интересная выресовывается картинка, много разных вспомогательных файлов: mocha.opts, .babelrc, .eslintrc, .gitignore, webpack.config.js, README.md, package.json и пр. шушера. А ведь не так давно мне было привычно размещать исходники прямо в корневой директории проекта, теперь же нужно им отдельную директорию создавать.
- Во-первых чтобы они не утонули среди вспомогательных файлов
- Во-вторых т.к. всё равно нужно куда-то итоговую сборку собирать
- В третьих таким образом удобно избегать node_modules (к примеру для поиска по файлам проекта)