Портрет 4X_Pro
Был в Сети сегодня, 01:39
Мультиблог
4X_Pro
Кратко о себе: Web-разработчик. Пишу на PHP, Python, JavaScript. Знаю Ruby и Go, со студенческих времён более-менее помню C и asm. Linuxоид (использую Mint+LXDE). Сторонник IndieWeb.

Компьютерное

Regexp для выноса URL ссылки в скобки за её текстом

4X_Pro
Иногда бывает нужно вынести адрес ссылки в скобки за её текстом, то есть преобразовать её из вида
<a href="http://example.com">Текст</a>
в формат
Текст (http://example.com)
Такое, например, полезно при выдаче данных в RSS, который впоследствии импортируется в Twitter.
Делается это достаточно легко с помощью такого  регулярного выражения:
$text = preg_replace('|<a\W[^>]*?href=[\'"]([^>]+?)[\'"][^>]*?>(.*?)</a>|u','$2 ($1)',$text);

Как определить язык пользователя в Telegram

4X_Pro
При разработке мультиязычного бота для Telegram возникает вопрос, на каком языке отвечать пользователю в начале диалога. Оказывается, узнать это достаточно просто: в сообщениях (объект Message) есть поле from. Оно представляет собой объект User, где имеется поле language_code, в котором и лежит код языка, выставленный у пользователя в настройках.
Итоговый код на PHP будет выглядеть примерно так:
$updates = $bot->getUpdates();   foreach ($updates as $item) {     $language = $item['message']['from']['language_code'];     if ($language==='ru') $response = 'Привет!';     else $response = 'Hello!';     $bot->sendMessage($response,$item['message']['chat']['id']);   }

Отслеживаем время запросов в Apache и NGinx

4X_Pro
Я уже писал о том, как отслеживать медленные запросы с помощью MySQL. Но не всегда причина бывает в базе данных. Поэтому имеет смысл применять и другой способ — использовать логи Web-серверов. В современных версиях Apache и NGinx есть возможность выводить в лог время выполнения запроса с точностью до миллисекунд.
В NGinx это делается так: сначала объявляем формат лога с помощью директивы log_format и придумываем ему имя, например, logtimed. Возьмём за основу формат по умолчанию, и добавим к нему время выполнения и длину запроса. А потом объявим использование этого формата в директиве access_log:
log_format combined '$remote_addr - $remote_user [$time_local] '                     '"$request" $status $body_bytes_sent '                     '"$http_referer" "$http_user_agent" '                     ' $request_time $request_length'; access_log /var/log/nginx/access.log logtimed
Директива может использоваться на любом уровне (http, server, location), но в документации сказано, что следует использовать тот же, на котором прописана директива root.

В Apache требуется включить mod_log_config. Далее всё делается аналогично,  только директивы называются LogFormat и CustomLog, а вывод времени запроса можно задать двумя способами — либо как %D (в микросекундах), либо как %msT (с версии 2.4.13) — в миллисекундах, либо как %T — в сеундах:
LogFormat "%h %l %u %t \"%r\" %>s %b %D" logtimed CustomLog /var/log/apache2/access.log logtimed
Эти директивы можно использовать либо в глобальной конфигурации, либо на уровне VirtualHost.
Надеюсь, эта информация поможет выявить медленные запросы и сделать ваши сайты и сервисы быстрее.

Включаем TLS 1.3 в nginx и Apache

4X_Pro
Версия протокола TLS 1.3 позволяет загружать сайты быстрее, чем TLS 1.2, за счёт того, что на этапе установки шифрованного соединения нужен только один обмен данными (round-trip) вместо двух. Поэтому по возможности следует включить её поддержку. Для этого нужно, чтобы на сервере была установлена библиотека openssl версии 1.1.1 или выше. Тогда в nginx это можно сделать с помощью директивы:
ssl_protocols TLSv1.2 TLSv1.3;
Указывать её нужно в секции server, там же, где пишется listen 443 ssl http2;
В Apache поддерживаемые протоколы указываются директивой SSLProtocol. Она расположена в файле настроек модуля ssl (в Ubuntu это /etc/apache2/mods-available/ssl.conf), также можно указать и для отдельных виртуальных хостов. Для включения TLS 1.3 прописываем так:
SSLProtocol -all +TLSv1.2 +TLSv1.3
Проверить поддержку версии 1.3 можно с помощью TLS checker или с помощью более продвинутого сервиса SSL server test от SSL Labs.

Полный список чаcовых поясов в PHP

4X_Pro
Как известно, в PHP есть функция date_default_timezone_set, которая устанавливает часовой пояс, который будет использоваться по умолчанию при работе с датами. На вход она принимает строки вида "Europe/Moscow", "Africa/Algiers". Полный список этих строк есть в мануале, но каждый раз переписывать его оттуда (или хотя бы выбирать те, которые используются среди целевой аудитории сайта) — напрасная трата сил и времени. Кроме того, если нужно хранить в базе часовой пояс каждого пользователя, то лучше сохранять его как число, а не как строку.
Оказалось, что в PHP предусмотрена функция для решения этой проблемы. Называется она DateTimeZone::listIdentifiers. По умолчанию выводит вообще все имеющиеся идентификаторы временных зон. Но можно получить список часовых поясов для отдельного региона или даже нескольких, передав их в качестве первого параметра: DateTimeZone::listIdentifiers(DateTimeZone::EUROPE | DateTimeZone::ASIA);
Также можно установить в первом параметре значение  DateTimeZone::PER_COUNTRY и в качестве второго передать двухбуквенный код страны. Важно: буквы в коде должны быть заглавными:
DateTimeZone::listIdentifiers(DateTimeZone::PER_COUNTRY,'RU');
Дальше всё просто: получить список идентификаторов, пронумеровать значения и вывести в select, а дальше сохранить номер выбранного значения в базе вместо строки.

Как получить из HTML-документа все ссылки с определённым атрибутом на PHP

4X_Pro
Недавно решил написать клиент для протокола IndieAuth. Для этого потребовалось сделать возможность находить на произвольной Web-странице все ссылки с атрибутом rel="me", причём ссылки могут быть как обычные (тег a), так и скрытые (тег link).

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

Пришлось вспомнить про такое расширение как PHP DOM. Оно позволяет работать с XML и HTML с помощью API, аналогичной той, которая есть для работы с DOM в броузерном JavaScript, а также делать запросы с помощью XPath. Вот им-то я и решил воспользоваться.
Итак, для начала создаём объект DOMDocument и загружаем в него HTML. Вместо обычного load, предназначенного для работы с правильно отформатированным XML, используем loadHTMLFile:
Смотреть пример кода

Несколько приёмов по улучшению PageSpeed Insights

4X_Pro
Я уже не раз писал о разных способах улучшить время загрузки сайта, но за последнее время нашёл ещё несколько приёмов:
  1. Выносите подключение шрифтов (директива @font-face) из внешних CSS в тег style в HTML до их подключения. Это незначительно увеличит объём кода страницы, зато позволит уменьшить длину критических цепочек. Если изначально броузер сначала скачивал CSS, разбирал его и уже потом начинал загружать шрифты, то теперь загрузка шрифта и файла CSS будет идти одновременно.
    Кроме того, нужно учитывать, что популярные шрифты уже могут быть установлены у пользователя локально. В частности, это касается шрифтов Roboto, которые изначально предустановлены в Android. Поэтому в src прописываем сначала директиву local, а уже потом url:
    src: local("Roboto"),url("fonts/Roboto-Medium.ttf");
    Не забывайте также указать свойство font-display: swap для шрифтов, которыми выводится основной контент и font-display: optional — для шрифтов вспомогательных элементов, например, значков из FontAwesome.
  2. Явно задавайте размеры для блоков, в которых будут помещаться элементы с других сайтов: виджеты соцсетей, баннеры, Google-карты и тому подобное. В этом случае контент сайта не будет «ездить» после окончания их подгрузки, из-за чего он субъективно будет казаться более быстрым с точки зрения пользователя. Это улучшает показатель CLS (content layout shift) в PageSpeed Insights.
  3. Одним из основных источников замедления сайта являются вспомогательные элементы типа онлайн-консультантов, форм заказа обратного звонка, уведомления обо всяких акциях и т.п., которые реализуются с помощью сторонних сервисов. Во многих случаях их загрузку имеет смысл отложить. Для этого берём тот код, который вы получили от сервиса, убираем из него тег <script> и закрывающий тег </script>, и оборачиваем с помощью функции setTimeout. Должно получиться что-то такое:
    <script> setTimeout(function() { код_сервиса }, время); </script>
    Где время задаётся в милисекундах. Для улучшения показателей Page Speed Insights желательно использовать задержку не менее 5000 мс.
  4. В Интернет-магазинах очень любят использовать слайдеры — блоки, в которых несколько картинок циклически сменяют друг друга. Идея хорошая, но есть одно но: в начальный момент, пока JavaScript для отображения слайдера не загрузился, на какой-то момент на экране появляются все изображения сразу.  Потом скрипт слайдера рассчитывает высоту и скрывает лишнее, из-за чего получается, что контент под слайдером «ездит» по экрану. (А если пришёл пользователь с отключенным JavaScript, то все изображения так и останутся видны.) Избежать этого достаточно просто. Предположим, у нас есть такой код слайдера:
    <div class="slider"> <a href="/page1.htm"><img src="banner1.png" alt="Картинка 1" /></a> <a href="/page2.htm"><img src="banner2.png" alt="Картинка 2" /></a> <a href="/page3.htm"><img src="banner3.png" alt="Картинка 3" /></a> </div>
    Прописываем в CSS такой код:
    .slider a { display:none } .slider a:first-child { display: block }
    В этом случае все картинки, кроме самой первой, просто скрываются до момента, когда загрузится и выполнится код слайдера. Кроме того, из-за того, что второе и третье изображение скрыты, броузер откладыает их загрузку, а вместо этого загружает другие элементы страницы, которые на начальном этапе нужнее.
  5. Если на сайте есть iframe с вспомогательным тяжёлым контентом, например, ролики с YouTube, карты и тому подобные элементы, имеет смысл сделать так: пропишите в свойство src about:blank (или код какой-нибудь заглушки в srcdoc), а реальную загрузку сделайте тогда, когда пользователь докрутит страницу до нужного объекта. Пример такого кода:
    <iframe src="about:blank" id="map"></iframe> <script> let map2 = document.getElementById("map"); let observer_map = new IntersectionObserver(function(entries) { if(entries[0].isIntersecting === true) {     map2.src="https://yandex.ru/map-widget/v1/?***"; // URL карты     observer_map.disconnect();   } }, { threshold: [0.1] }); observer_map.observe(map2); </script>
    Подробнее о том, как работает этот код, читайте в заметке про отслеживание попадания элемента в зону видимости.
Используйте эти несколько простых трюков, и ваш показатель PageSpeed Insights вырастет, а сайты станут быстрее и приятнее для использования!

Как отцентрировать текст по вертикали

4X_Pro
У начинающих верстальщиков часто возникает вопрос, как отцентрировать текст в блоке по горизонтали и вертикали. С горизонталью всё достаточно просто: используем свойство text-align. А вот для вертикали раньше приходилось прибегать ко всяческим ухищрениям. Но с появлением flex-верстки всё стало намного проще. Зададим в CSS класс, для которого пропишем выравнивание по центру по главной и вспомогательной оси:
.centered { display: flex; justify-content: center;  align-items: center; text-align: center }
И просто применим его к нужному блоку:
<div class="centered">Немного текста <br /> и дополнительного <br />содержимого</div>
Посмотреть пример

Скрипт улетающего в корзину товара без jQuery

4X_Pro
Во многих Интернет-магазинах можно встретить эффект, когда при нажатии кнопки «Добавить» фотография товара летит в направлении изображения корзины. Обычно его реализуют на jQuery с помощью метода transfer, но можно обойтись и чистым JavaScript. Это делается за несколько шагов:
  1. Определяем координаты фотографии товара и корзины на экране
  2. Создадим новый DOM-элемент с копией фотографии товара и задаём ему начальные координаты
  3. Рассчитаем расстояние, на которое нужно переместить фотографию
  4. Добавляем соответствующие transform и transition, после чего добавляем копию в документ.
Реализация описанного на чистом JavaScript

Работа с прокруткой в современном JavaScript

4X_Pro

Плавная прокрутка к нужному элементу

Раньше самым популярным способом сделать плавную прокрутку до нужного элемента было использование функции animate из JQuery. Но в современных броузерах есть более простой способ, не требующий сторонних библиотек: через функцию scrollIntoView, которая есть у каждого DOM-элемента. Чтобы прокрутка была плавной, ей нужно передать объект с параметром behavior: 'smooth'. Также есть параметр block, который задаёт, где окажется элемент после прокрутки: сверху ('start') области просмотра, посередине ('center') или внизу ('end'). Также можно указать 'nearest' для минимальной прокрутки до попадания в зону видимости.
Тогда ссылка для прокрутки к нужному элементу будет выглядеть так:
<a href="#идентификатор" onclick="document.getElementById('идентификатор').scrollIntoView({behavior:'smooth',block:'nearest'});return false" >К самому интересному!</a>
Вместо document.getElementById можно получать нужный элемент и другими способами, например, через document.querySelector.
А ссылку для прокрутки наверх страницы можно сделать без идентификатора вообще, используя в качестве элемента body:
<a href="#" onclick="document.body.scrollIntoView({behavior:'smooth',block:'start'});return false"> Наверх</a>

Проверка, что элемент попал в зону видимости

Ещё одна задача, с которой приходится часто сталкиваться, — это обработка ситуаций, когда элемент появляется в зоне видимости или исчезает из неё. Раньше для этого приходилось вешать обработчик на событие scroll и дальше высчитывать смещение элемента и проверять, попадает ли оно в нужный диапазон. При этом, если код обработчика выполнялся долго, терялась плавность прокрутки страницы. Сейчас появилось более простое решение — интерфейс IntersectionObserver.

Используется он так: сначала создаём объект через new и передаём ему функцию-обработчик и объект с опциями. Функция-обработчик имеет один параметр — массив объектов IntersectionObserverEntry, наиболее важными свойствами которых являются isIntersecting — признак того, появился объект в зоне видимости или ушёл, и target — указание на сам DOM-элемент.

Две наиболее важных опции:
root — элемент, внутри которого делается прокрутка и внутри которого должны находиться наблюдаемые элементы (если его не указать, отслеживание будет вестись во всём документе), и
tresholds — массив числовых значений, который определяет, насколько должен появиться элемент в зоне видимости, чтобы IntersectionObserver вызвал функцию-обработчик. Например, tresholds: [0.0,0.33,1.0] означает, что когда элемент будет входить в зону видимости, функция обработчик будет вызвана трижды: когда появится только его край (значение 0), когда он появится на треть (значение 0.33) и когда войдёт в зону видимости полностью (значение 1).

Далее для созданного объекта нужно вызвать функцию observe, в которую передать элементы, появление/исчезновение которых нужно отслеживать. Прекратить отслеживание можно с помощью метода disconnect.

Пример кода, который загружает ролик с YouTube в тот момент, когда пользователь прокручивает страницу до него:
<!-- создаем пустой iframe --> <iframe class="scalable" width="269" height="150" src="about:blank" id="youtube_video" allowfullscreen=""></iframe> <script> // создаем сам объект-наблюдатель и объявляем функцию-обработчик let observer = new IntersectionObserver(function(entries) { //  проверка, что элемент стал видимым if(entries[0].isIntersecting === true) { // меняем атрибут src iframe на реальный адрес ролика       entries[0].target.src="https://www.youtube.com/embed/WUYPb8bOr1c"; // поскольку ролик нужно загрузить всего один раз, отключаем дальнейшее отслеживание       observer.disconnect();     }     else { // если бы мы не сделали disconnect, // здесь можно было бы повесить обработчик для ситуаций, когда iframe уходит из области видимости     }   } }, // Блок опций: начинаем грузить ролик сразу, как только iframe хоть немного попадает в зону видимости { threshold: [0] }); // указываем, за каким элементом нам надо следить observer.observe(document.getElementById('youtube_video')); </script>
На этом пока всё. Возможно, позже, в отдельном сообщении распишу, как сделать загрузку в iframe preview-изображения для видео и обеспечить его работу даже в том случае, если JavaScript отключен.

Страницы:
Задать вопрос

Здесь можно задать мне вопрос или спросить совета по любой теме, затронутой в блогах или на форуме. После того, как я отвечу, вопрос и ответ появятся в соответствующем разделе. Но не забываем, что я — сторонник slow life, поэтому каких-либо сроков ответов не обещаю. Самые интересные вопросы станут основой для новых тем на форуме или записей в блоге.
Сразу предупреждаю: глупости, провокации, троллинг и тому подобное летит прямо в /dev/null.