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

Социальные сети


Новости сайта в Telegram

t.me/4x_pro

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

Включаем 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 отключен.

Асинхронная загрузка Recaptcha

4X_Pro
В одном из прежних сообщений я уже описывал, как добавить на сайт ReCAPTCHA для защиты от спама. Но скрипт ReCAPTCHA имеет увеличивает время загрузки сайта и ухудшает показатели в Google Page Insights. Если она нужна на одной странице (скажем, только регистрации пользователя), то это не страшно. Но вот если нужно защитить форму, которая есть на каждой странице сайта (например, форма обратной связи или записи на какое-нибудь мероприятие), загрузку приходится делать всё время.
Тем не менее, есть решение, которое позволяет обойти проблему. Будем загружать ReCAPTCHA не тогда, когда грузится страница целиком, а тогда, когда пользователь начинает что-то вводить в нужную форму. Первое, что приходит в голову — это повесить обработчик на событие click, который будет добавлять тег script. Однако нужно помнить, что продвинутые пользователи могут поставить курсор в форму не мышью, а клавишей Tab. Поэтому обработчик будем вешать на событие focus. Пропишем нужной форме (или даже формам) класс recaptcha и повесим на событие focus обработчик. Чтобы избежать двойной загрузки скрипта, используем переменную recaptcha_loaded:
let recaptcha_forms = document.querySelectorAll('.recaptcha'); let recaptcha_loaded = false; for (let i=0; i<recaptcha_forms.length;i++) {   recaptcha_forms[i].addEventListener('focus',function (e) {     if (!recaptcha_loaded) {       let recaptcha = document.createElement('script');       recaptcha.src="https://www.google.com/recaptcha/api.js";       recaptcha.onload = ()=>recaptcha_loaded=true;       document.head.appendChild(recaptcha);     }   },true); }
На всякий случай пример кода формы:
<form method="POST" action="" class="recaptcha"> <!-- Тут идут поля нашей формы --> <div class="g-recaptcha" data-sitekey="6L..."></div> </form>
Важно: добавлять обработчик нужно с параметром true, так как событие focus на прямом ходу обработки не является всплывающим. Альтернативный вариант — использовать событие focusin, которое всплывает и на прямом ходу.
Данный пример кода делался для ReCAPTCHA v2, но можно использовать его и с любой аналогичной системой защиты от спама.

«Живое» обновление html-страницы при верстке

4X_Pro
Каждый, кто когда либо сталкивался с версткой, знает, сколько времени уходит на постоянное обновление страницы, чтобы увидеть результаты изменений. Но, как выяснилось, существует несколько решений для этой проблемы: LiveReloadX для Node.js и дополнение LiveReload для Visual Studio Code. Также есть LiveReload для Python. Все они работают так: отслеживают изменения в файловой системе и запускают на localhost Websocket-сервер, через который отправляют уведомления, что нужно перезагрузить страницу. А в редактируемый HTML нужно добавить простой скрипт, который и будет получать эти уведомления и перезагружать страницу. Перейти к описанию установки и использования

Ссылки на мессенджеры на сайте

4X_Pro
Часто при верстке мобильных версий сайта возникает необходимость сделать ссылки на различные мессенджеры так, чтобы нужное приложение открывалось сразу же по щелчку на этой ссылке. Хотя эта информация легко находится в сети, я решил собрать все необходимое в одном месте. Смотреть примеры кода

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

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