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

Маска ввода телефона на лендинге делает две вещи: подставляет код страны и не даёт отправить заведомо битый номер. Ниже готовый скрипт на библиотеке intl-tel-input 29.5.3 без jQuery. Страну он берёт из макроса Keitaro, форматирует номер по мере ввода, проверяет длину для этой страны и отправляет в партнёрку номер в формате E.164, например +79161234567.
Это обновлённая версия маски, которую мы выкладывали в 2024 году. Старый код подключал intl-tel-input 17.0.8, брал утилиты от версии 19.2.16 и загружал jQuery, которой нигде не пользовался. Что поменялось, сведено в таблицу в конце статьи.
- Что умеет маска
- Как подключить маску к лендингу в Keitaro
- {country} или {country_code}: какой макрос ставить
- Полный код phone-mask.js
- Как работает скрипт
- Подключение библиотеки
- Страна и код
- Проверка и отправка
- Вёрстка
- Настройки, которые чаще всего меняют
- Как держать файлы библиотеки у себя
- Как мы проверили код
- Что изменилось по сравнению с версией 2024 года
- Частые вопросы
- Как сделать маску телефона с автоматическим определением кода страны?
- Можно ли использовать маску без Keitaro?
- Что будет, если посетитель введёт номер с восьмёркой?
- Проверяет ли маска, что номер существует?
- Почему не работает маска на поле с другим именем?
- Как получить номер, если лендинг отправляет форму своим скриптом без события submit?
Что умеет маска
- Подставляет код страны из макроса Keitaro, посетитель вводит только сам номер.
- Форматирует номер по правилам страны прямо при вводе:
916 123-45-67для России,1512 3456789для Германии. - Показывает в поле пример номера для страны вместо надписи «Телефон».
- Не пускает в поле буквы и лишние цифры.
- Перед отправкой проверяет номер и пишет ошибку на языке лендинга.
- Отдаёт в форму номер в E.164: одинаковый формат проще проверять на дубли и передавать в партнёрку.
- Работает сразу со всеми формами на странице.

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

Без 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 | загружалась, не использовалась | нет |
| Подключение утилит | utilsScript | loadUtils |
| Код страны | в значении поля, можно стереть | отдельно от поля, стереть нельзя |
| Номер в заявке | как ввёл посетитель, с пробелами | 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().



