Перейти к содержанию

Маска ввода телефона с кодом страны: intl-tel-input для Keitaro

Маска ввода телефона с кодом страны на intl-tel-input 29.5.3 без jQuery: страна из макроса Keitaro, форматирование, проверка номера и отправка в E.164…

Маска ввода телефона с кодом страны: intl-tel-input для Keitaro

Маска ввода телефона на лендинге делает две вещи: подставляет код страны и не даёт отправить заведомо битый номер. Ниже готовый скрипт на библиотеке intl-tel-input 29.5.3 без jQuery. Страну он берёт из макроса Keitaro, форматирует номер по мере ввода, проверяет длину для этой страны и отправляет в партнёрку номер в формате E.164, например +79161234567.

Это обновлённая версия маски, которую мы выкладывали в 2024 году. Старый код подключал intl-tel-input 17.0.8, брал утилиты от версии 19.2.16 и загружал jQuery, которой нигде не пользовался. Что поменялось, сведено в таблицу в конце статьи.

Что умеет маска

  • Подставляет код страны из макроса Keitaro, посетитель вводит только сам номер.
  • Форматирует номер по правилам страны прямо при вводе: 916 123-45-67 для России, 1512 3456789 для Германии.
  • Показывает в поле пример номера для страны вместо надписи «Телефон».
  • Не пускает в поле буквы и лишние цифры.
  • Перед отправкой проверяет номер и пишет ошибку на языке лендинга.
  • Отдаёт в форму номер в E.164: одинаковый формат проще проверять на дубли и передавать в партнёрку.
  • Работает сразу со всеми формами на странице.
Маска ввода телефона с кодом страны: номер по маске и ошибка «Номер слишком короткий»
Тестовый лендинг в Chromium: +7 подставлен из макроса Keitaro, короткий номер форма не отправляет

Как подключить маску к лендингу в Keitaro

  1. Сохраните код из следующего раздела в файл phone-mask.js и положите его в папку лендинга рядом с index.html или index.php.
  2. В каждую форму добавьте скрытое поле country со значением {country}. Keitaro подставит в него код страны посетителя.
  3. Проверьте, что поле телефона называется phone. Если в лендинге другое имя, поменяйте селектор input[name="phone"] в скрипте.
  4. Подключите скрипт перед </body>.
  5. Загрузите архив в Keitaro как локальный лендинг и пройдите по ссылке кампании: в поле должен появиться код страны.

Форма после правок выглядит так:

<form action="api.php" method="post">
  <input type="hidden" name="country" value="{country}">
  <input type="text" name="name" placeholder="Имя" required>
  <input type="tel" name="phone" required>
  <button type="submit">Заказать</button>
</form>

<script src="phone-mask.js"></script>

{country} или {country_code}: какой макрос ставить

В актуальном списке плейсхолдеров Keitaro есть {country} с описанием «Код страны». Макрос {country_code} встречается в старых инструкциях и в прежней справке Keitaro значился псевдонимом того же макроса, но в текущей документации его нет. Для новых лендингов ставьте {country}.

Плейсхолдеры работают прямо в HTML локального лендинга: в документации по локальным лендингам так же предлагают передавать {subid} через скрытое поле формы. Если лендинг открыть не через трекер, макрос не подставится и в поле останется текст {country}. Скрипт это видит: берёт запасную страну из DEFAULT_COUNTRY, а если она не задана, включает выбор страны из списка.

Выбор страны в маске телефона: поиск по коду +44 и поле на экране телефона
Без кода страны посетитель выбирает её сам, поиск понимает и код. Справа то же поле на телефоне

Без Keitaro страну можно определить по IP: есть скрипт определения страны пользователя через ipinfo, который пишет код в то же поле country.

Полный код phone-mask.js

/* Универсальная маска телефона для лендингов Keitaro.
   intl-tel-input 29.5.3, без jQuery.
   Страна берётся из поля <input type="hidden" name="country" value="{country}"> */
(function () {
  var VERSION = '29.5.3';
  var CDN = 'https://cdn.jsdelivr.net/npm/intl-tel-input@' + VERSION + '/dist/';
  var DEFAULT_COUNTRY = '';  // запасная страна, если макрос пустой, например 'ru'
  var LOCK_COUNTRY = true;   // true: принимать номера только страны из Keitaro

  var TEXTS = {
    ru: { empty: 'Введите номер телефона', short: 'Номер слишком короткий',
          long: 'Номер слишком длинный', invalid: 'Проверьте номер телефона' },
    en: { empty: 'Enter your phone number', short: 'The number is too short',
          long: 'The number is too long', invalid: 'Check the phone number' }
  };
  var lang = (document.documentElement.lang || 'en').slice(0, 2).toLowerCase();
  var T = TEXTS[lang] || TEXTS.en;
  var utilsReady = false;

  var css = document.createElement('link');
  css.rel = 'stylesheet';
  css.href = CDN + 'css/intlTelInput.css';
  document.head.appendChild(css);

  var style = document.createElement('style');
  style.textContent = '.iti{display:block;width:100%}';
  document.head.appendChild(style);

  var script = document.createElement('script');
  script.src = CDN + 'js/intlTelInput.min.js';
  script.onload = function () {
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', init);
    } else {
      init();
    }
  };
  document.head.appendChild(script);

  function getCountry(phone) {
    var field = (phone.form && phone.form.querySelector('input[name="country"]')) ||
      document.querySelector('input[name="country"]');
    var code = field ? field.value.trim().toLowerCase() : '';
    // если макрос не подставился, в поле останется "{country}": берём запасную страну
    return /^[a-z]{2}$/.test(code) ? code : DEFAULT_COUNTRY;
  }

  function errorText(iti, phone) {
    if (!phone.value.trim()) return T.empty;
    var error = iti.getValidationError();
    if (error === 'TOO_SHORT') return T.short;
    if (error === 'TOO_LONG') return T.long;
    return T.invalid;
  }

  function init() {
    document.querySelectorAll('input[name="phone"]').forEach(function (phone) {
      if (phone.type === 'text') phone.type = 'tel';
      var cs = getComputedStyle(phone); // CSS библиотеки обнуляет вертикальные отступы поля
      var mt = cs.marginTop, mb = cs.marginBottom;
      var country = getCountry(phone);
      var options = {
        initialCountry: country,
        separateDialCode: true,
        placeholderNumberPolicy: 'AGGRESSIVE',
        loadUtils: function () { return import(CDN + 'js/utils.js'); }
      };
      if (country && LOCK_COUNTRY) {
        options.onlyCountries = [country];
        options.countrySelectorMode = 'OFF';
      }
      var iti = window.intlTelInput(phone, options);
      var wrap = phone.closest('.iti');
      wrap.style.marginTop = mt;
      wrap.style.marginBottom = mb;
      iti.promise.then(function () { utilsReady = true; }, function () {});
      phone.addEventListener('input', function () { phone.setCustomValidity(''); });
    });
  }

  // capture: срабатывает раньше обработчиков самого лендинга
  document.addEventListener('submit', function (e) {
    var phones = e.target.querySelectorAll('input[name="phone"]');
    for (var i = 0; i < phones.length; i++) {
      var phone = phones[i];
      var iti = window.intlTelInput && window.intlTelInput.getInstance(phone);
      if (!iti || !utilsReady) continue; // utils не загрузились: лид не теряем
      if (!iti.isValidNumber()) {
        phone.setCustomValidity(errorText(iti, phone));
        phone.reportValidity();
        e.preventDefault();
        e.stopPropagation();
        return;
      }
      phone.value = iti.getNumber(); // E.164, например +79161234567
    }
  }, true);
})();

Как работает скрипт

Подключение библиотеки

Скрипт сам добавляет на страницу CSS и ядро intl-tel-input с jsDelivr, как в официальной инструкции. Версия закреплена в константе VERSION, поэтому обновление библиотеки не сломает лендинг без вашего ведома. На cdnjs, откуда грузила старая маска, на 1 октября 2026 года последняя версия 25.11.2, а актуальная 29.5.3 вышла 24 сентября 2026 года.

Форматирование и проверку делает отдельный файл утилит, сборка libphonenumber от Google. По документации он весит около 260 КБ, ядро около 30 КБ. Утилиты подключаются через опцию loadUtils и грузятся после события load, чтобы не тормозить первую отрисовку. Опцию utilsScript из старого кода библиотека больше не поддерживает, её заменил loadUtils.

Страна и код

  • initialCountry выставляет страну из скрытого поля. Значение приводится к нижнему регистру, как в примерах документации, и проверяется: это должны быть ровно две латинские буквы.
  • separateDialCode выводит +7 слева от поля. Стереть его посетитель не может, а в старой версии код вписывался в само значение и удалялся одной клавишей.
  • При LOCK_COUNTRY = true список стран отключён (countrySelectorMode: 'OFF'), а onlyCountries оставляет одну страну. Если вставить из буфера номер с чужим кодом, проверка не пройдёт. Для офферов, которые принимают номера только своего ГЕО, это то, что нужно. Чтобы посетитель мог выбрать страну, поставьте false.
  • placeholderNumberPolicy: 'AGGRESSIVE' заменяет плейсхолдер лендинга примером номера страны. С 'POLITE' пример появится только там, где плейсхолдера нет.

Проверка и отправка

Режим strictMode включён по умолчанию: по документации он отбрасывает всё, кроме цифр и плюса в начале, и не даёт ввести номер длиннее максимального для страны. Сама проверка идёт через isValidNumber(). Метод сверяет длину номера с правилами страны и по умолчанию пропускает мобильные и городские номера. Существует ли номер на самом деле, на стороне браузера не узнать, для этого нужна проверка номеров телефонов по API.

Обработчик submit висит на document в фазе перехвата (третий аргумент true). При обычной отправке он срабатывает раньше обработчиков самой формы. Но прямой вызов form.submit() из другого кода не создаёт событие submit и обойдёт эту проверку. Используйте requestSubmit() вне обработчика отправки или явно проверяйте номер перед своим запросом, как описано в документации MDN. Неверный номер останавливает отправку и показывает подсказку браузера. Верный номер заменяется на E.164 через getNumber(), и в api.php приходит +79161234567, как бы посетитель его ни вводил. Это помогает и при отсечении дублей лидов по номеру: один и тот же телефон, введённый по-разному, превращается в одну строку.

Если утилиты не загрузились, например CDN недоступен у посетителя, форма уйдёт без проверки. Лучше получить лид с неотформатированным номером, чем потерять его.

Вёрстка

Библиотека оборачивает поле в блок .iti. Её CSS обнуляет вертикальные отступы поля, и в разделе Troubleshooting советуют задавать их обёртке. Скрипт так и делает: переносит отступы поля на .iti и растягивает обёртку на всю ширину. В старой маске флаги прятали стилями, чтобы не задеть вёрстку лендингов. В новой они оставлены: в нашей проверке поле с флагом и кодом страны встало в форму без правок CSS. Убрать флаги можно одной опцией, она есть в таблице ниже.

Настройки, которые чаще всего меняют

ЗадачаЧто поменять в скрипте
Запасная страна, если макрос не подставилсяDEFAULT_COUNTRY = 'ru'
Дать посетителю выбрать странуLOCK_COUNTRY = false
Убрать флагидобавить в options строку showFlags: false, вместо флага будет значок глобуса
Принимать только мобильные номерадобавить allowedNumberTypes: ['MOBILE']
Оставить плейсхолдер лендингаplaceholderNumberPolicy: 'POLITE'
Ошибки на языке ГЕОдобавить язык в объект TEXTS, язык берётся из <html lang>
Поле телефона с другим именемзаменить input[name="phone"] в двух местах
Таблица прокручивается вбок

Как держать файлы библиотеки у себя

Если не хотите зависеть от CDN, скачайте из пакета папку dist: два JS-файла, CSS и картинки флагов (CSS ссылается на них по пути ../img/). Положите их в папку лендинга и замените одну строку в начале скрипта:

// файлы лежат в папке intl-tel-input рядом с index.html:
// intl-tel-input/js/intlTelInput.min.js, intl-tel-input/js/utils.js,
// intl-tel-input/css/intlTelInput.css, intl-tel-input/img/flags.webp, flags@2x.webp
var CDN = new URL('intl-tel-input/', document.baseURI).href;

В нашей проверке с такой строкой страница не сделала ни одного запроса к внешним доменам.

Как мы проверили код

1 октября 2026 года скрипт прогнали в headless Chromium через Playwright на тестовой странице с двумя формами. Вторая форма отправлялась собственным обработчиком лендинга через form.submit(). Результаты:

  • При value="RU" поле получает +7 и плейсхолдер 912 345-67-89, при DE получает +49.
  • Ввод 9161234567 превращается в 916 123-45-67, буквы не вводятся, лишние цифры отбрасываются.
  • 91612 не отправляется, браузер показывает «Номер слишком короткий». Обработчик лендинга при этом не вызывается.
  • Верный номер приходит на сервер как +79161234567, немецкий как +4915123456789.
  • Вставка из буфера +7 (916) 123-45-67 и 8 916 123 45 67 даёт тот же +79161234567. Вставка +44 7400 123456 в российскую форму не проходит проверку.
  • Если макрос не подставился, включается выбор страны: ввод +44 открывает поиск, Enter выбирает Великобританию.
  • При заблокированных утилитах форма отправляется без проверки, скрипт не падает.

Старую маску 2024 года проверили так же. В Chromium она ещё работает, но тянет три файла с cdnjs, включая jQuery на 87 КБ, держит код страны внутри значения поля и пишет ошибку только по-английски: «Invalid phone number format».

Что изменилось по сравнению с версией 2024 года

ЧтоВерсия 2024Версия 2026
Библиотекаядро 17.0.8, утилиты 19.2.16ядро и утилиты 29.5.3
jQueryзагружалась, не использоваласьнет
Подключение утилитutilsScriptloadUtils
Код страныв значении поля, можно стеретьотдельно от поля, стереть нельзя
Номер в заявкекак ввёл посетитель, с пробеламиE.164: +79161234567
Текст ошибкитолько английскийна языке лендинга
Номер чужой страныне ограниченотклоняется при LOCK_COUNTRY
Таблица прокручивается вбок

Первая версия маски была доработкой кода от YellowWeb. Внешний скрипт на GitHub Pages, который мы давали в старой статье, остался версией 2024 года, поэтому новую маску сохраните к себе в лендинг.

После отправки формы посетитель попадает на страницу благодарности. Готовый вариант с переводом на 25 языков есть в статье про мультиязычную страницу «Спасибо за заказ», она умеет показать номер, который ушёл в заявку.

Частые вопросы

Как сделать маску телефона с автоматическим определением кода страны?

В Keitaro код страны даёт макрос {country} в скрытом поле, скрипт выставит страну по нему. Без трекера у intl-tel-input есть опция initialCountryLookup: она вызывает ваш сервис геолокации по IP, а в документации пример с ipapi.co, у которого ограниченный бесплатный тариф.

Можно ли использовать маску без Keitaro?

Да. Впишите код страны в скрытое поле вручную, например value="ru", или задайте DEFAULT_COUNTRY. Если не задать ничего, посетитель выберет страну из списка сам.

Что будет, если посетитель введёт номер с восьмёркой?

Для России ввод 89161234567 библиотека понимает как национальный формат с префиксом 8. В поле останется 8916 1234 567, а в заявку уйдёт +79161234567. Мы проверили это в тесте.

Проверяет ли маска, что номер существует?

Нет, isValidNumber() проверяет длину номера для страны. Есть isValidNumberPrecise() с точными правилами по кодам операторов, но документация предупреждает: эти правила меняются каждый месяц, и без регулярного обновления библиотеки он начнёт отклонять настоящие номера.

Почему не работает маска на поле с другим именем?

Скрипт ищет поля input[name="phone"]. Если в лендинге поле называется tel или phone_number, замените селектор в функции init и в обработчике submit.

Как получить номер, если лендинг отправляет форму своим скриптом без события submit?

Возьмите номер у экземпляра библиотеки: intlTelInput.getInstance(поле).getNumber(). Метод вернёт E.164, но вызывать его стоит после isValidNumber().

Редакция BoostClicks

Материалы о настройке рекламы, трекинге и автоматизации: инструкции, примеры кода и расчёты с указанием условий проверки.