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

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

Скрипт улетающего в корзину товара без 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
Часто при верстке мобильных версий сайта возникает необходимость сделать ссылки на различные мессенджеры так, чтобы нужное приложение открывалось сразу же по щелчку на этой ссылке. Хотя эта информация легко находится в сети, я решил собрать все необходимое в одном месте. Смотреть примеры кода

Преобразование массива байтов в строку на Go

4X_Pro
Занимаясь разработкой SiteKnockerBot для мониторинга сайтов, я столкнулся с тем, что результат HTTP-запроса в Go читается в массив (вернее, slice) байтов. Но для дальнейшей обработки требовалось преобразовать его в строку. На первый взгляд казалось, что это просто — достаточно воспользоваться приведением типов:
str:=string(bytes_array[:]);
Скоро выяснилось, что это плохо с точки зрения производительности: для переменной str будет выделена новая область памяти, куда будет скопировано всё содержимое bytes_array. Если объем данных значителен или одновременно выполняется множество goroutines, то выполнение ощутимо замедляется (в моём случае среднее время обработки запроса увеличивалось почти на 100 мс). Возник вопрос: можно ли как-то преобразовать массив без копирования. После недолгих поисков в Интернете нашлось решение, реализованное через небезопасную работу с указателями:
func bytesToString(b []byte) string {     bh := (*reflect.SliceHeader)(unsafe.Pointer(&b));     sh := reflect.StringHeader{bh.Data, bh.Len};     return *(*string)(unsafe.Pointer(&sh)); } str:=bytesToString(bytes_array);
Работает этот код так: берет указатель на заголовок slice, извлекает из него длину и указатель на данные, и записывает в аналогичную структуру для строки, а потом возвращает её из функции. Для того, чтобы код скомпилировался, нужно в import указать "reflect" и "unsafe".

Немного о заголовках для кеширования в HTTP

4X_Pro
В протоколе HTTP названия директив для заголовка Cache-Control даны крайне неудачно, что часто вызывает путаницу у разработчиков CMS. Итак, попробуем разобраться, что каждая из них делает:
  • no-cache — не запрещает кеширование вообще, как это можно было бы ожидать из названия, а только указывает, что при каждом обращении к ресурсу нужно сначала сделать запрос на сервер с условными заголовками If-Modified-Since или If-None-Match, для проверки, что результат не изменился.
  • no-store — запрещает сохранение ответа в кеше. Именно эта директива и есть запрет кеширования. Повторное обращение к данному ресурсу будет отправлено на сервер, причем без заголовков типа If-Modified-Since.
  • must-revalidate — по названию можно было бы предположить, что она делает перепроверку изменения содержимого на сервере. Но на самом деле нет: эта директива указывает, что делать с контентом, у которого истёк max-age и нет возможности перезапросить его с сервера (например, в автономном режиме). Если директива указана, вместо страницы будет выдано сообщение, что она более недоступна. Если же её нет, то страница будет показана, даже несмотря на то, что она является устаревшей (stale в терминах протокола HTTP).
  • max-age — срок в секундах, в течение которого страница считается актуальной (не перешедшей в состояние устаревшей — stale). Если не указан, считается равным нулю.
Несколько сценариев их использования

Дополнительная защита от спама в комментариях

4X_Pro
Боты становятся умнее и всё чаще и чаще обходят и обычные CAPTCHA, и reCAPTCHA от Google. В связи с этим возникает вопрос: как дополнительно защитить сайт от спама? Я нашел такое решение: поскольку боты пытаются заслать комментарий в любое текстовое поле, которое они найдут, их можно обмануть так: перед основным полем для комментария-сообщения сделать дополнительное поле-обманку, которое скрыть средствами CSS. Подробнее о том, как это реализовать

Скриншот сайта из командной строки

4X_Pro
Во многих SEO-сервисах при анализе сайта показывается его скриншот. Стало интересно, как делать скриншоты автоматически. Оказалось, всё просто: нужно запустить броузер с необходимыми параметрами командной строки.

Для Chrome/Chromium/Opera они выглядят так:
chromium-browser --headless --screenshot="имя_файла" --window-size=1920,1200 --hide-scrollbars "URL_сайта"
Если запустить без --window-size, скриншот будет сделан для разрешения экрана 800x600. Если для скриншота не указан путь, Chrome пытается сохранить его в домашний каталог пользователя
Для Firefox все проще:
firefox --screenshot "имя_файла" --window-size=1920,1200 "URL_сайта"
Примечание: если у Firefox настроен так, что при старте спрашивает, какой из профилей выбрать, то нужно явно указать профиль при старте через параметр -p, иначе не сработает. Например:
firefox --screenshot "4xpro.png" "http://4xpro.ru" -p Default-release

Красивая транслитерация для URL с помощью AWS Lambda

4X_Pro
Занимаясь SEO, столкнулся с тем, что во многих CMS URLы для страниц генерируются так: название прогоняется через транслитератор, потом небуквенные символы заменяются на _, и на этом все. С учетом того, что при наполнении сайта данные часто вставляются через буфер обмена с лишними пробелами, получаем ужасные адреса вида http://example.com/-ochen-horoshiy--tovar_-_kupite-оbyazatelno_. Глядя на это, я решил, что нужно создать транслитератор, который будет делать красивые URL. Такой транслитератор должен уметь следующее:
  • делать регистр букв всегда нижним
  • удалять все посторонние символы, кроме букв, цифр, тире и прочерков, а пробелы заменять на «-» (в соответствии с рекомендациями Google);
  • обрезать пробелы по краям, а также несколько пробелов, идущих подряд;
  • уметь обрезать URL по первой запятой (это полезно для многих магазинов, где после запятой часто идут второстепенные параметры товара, которые не требуется выносить в URL);
  • уметь обрезать URL по границе слова так, чтобы не превышать указанную длину.
Встроить такое в какую-либо конкретную CMS несложно. Но хотелось сделать какое-то более универсальное решение — API, к которой можно было бы обращаться из любой CMS. И вот недавно я узнал о бессерверных вычислениях и платформе Lambda для Amazon Web Services. Я счел, что она для таких задач подходит идеально и решил попробовать её в деле.
Подробнее о том, как создавался такой транслитератор и пример интеграции

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

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