Проверка формы на JavaScript

Проверка формы перед отправкой на JavaScript обнаруживает пустые поля и неверные значения. Атрибуты HTML5 закрывают базовые случаи. JavaScript добавляет свои правила и сообщения, а сервер повторяет проверку.

В статье вы соберете форму с именем, email, телефоном и сообщением.

Код проверит пробелы и форматы, покажет ошибки рядом с полями. Локальный обработчик PHP позволит проверить отправку через fetch.

Что проверять на клиенте, а что на сервере

Клиентская валидация формы улучшает интерфейс. Она показывает ошибки сразу и сокращает число лишних запросов.

Сервер получает окончательные данные после запроса. Пользователь может отключить JavaScript или отправить запрос напрямую.

Клиентская проверка не заменяет серверную.

Сервер повторяет правила обязательности, формата и длины. Он также проверяет права пользователя и содержимое запроса.

Клиент отвечает за удобство заполнения. Сервер решает, какие данные принять и сохранить.

Встроенная проверка HTML5

HTML проверяет базовые ограничения без отдельного скрипта. Добавьте атрибуты к полям. Браузер остановит стандартную отправку при ошибке.

  • required запрещает отправку пустого обязательного поля.
  • type="email" проверяет базовую структуру адреса.
  • type="tel" помогает браузеру выбрать телефонную клавиатуру. Сам формат номера этот тип не проверяет.
  • type="url" проверяет значение как URL.
  • minlength и maxlength ограничивают длину текста.
  • min и max ограничивают числовое значение.
  • pattern проверяет строку по регулярному выражению.

Атрибут novalidate отключает интерактивную проверку браузера при отправке формы.

Используйте его, когда JavaScript должен выводить собственные сообщения. Ограничения полей при этом остаются доступны через API.

Документация MDN описывает синтаксис атрибута pattern.

Псевдокласс :invalid выбирает поле с нарушенным ограничением. Он может выделить пустое обязательное поле еще до взаимодействия.

Псевдокласс :user-invalid учитывает взаимодействие пользователя с полем при отображении ошибки. Собственная проверка нужна для своих текстов ошибок и доступного интерфейса.

<form action="/send" method="post">
  <label>
    Email
    <input type="email" name="email" required>
  </label>

  <label>
    Сайт
    <input type="url" name="site">
  </label>

  <label>
    Сообщение
    <textarea name="message" minlength="10" maxlength="500"></textarea>
  </label>

  <button type="submit">Отправить</button>
</form>

Откройте страницу и отправьте форму с пустым email. Браузер остановит отправку и покажет встроенное сообщение.

Текст и внешний вид сообщения зависят от браузера и системы.

CSS страницы не дает надежного способа оформить встроенную подсказку. Для единого дизайна создайте собственный элемент ошибки.

Этот фрагмент демонстрирует проверку браузера, но не содержит серверного обработчика.

Проверка формы перед отправкой на JavaScript

Слушайте событие submit у формы. Такой обработчик сработает и при нажатии Enter. Проверка не зависит от клика по кнопке.

Вызов preventDefault() останавливает обычную отправку.

Без JavaScript браузер отправляет форму стандартным способом. Для этого задайте рабочие action и method.

Список обязательных полей держите отдельно от логики. Так вы измените набор полей и русские подписи в одном месте.

<form id="demo-form">
  <label>Имя <input name="name"></label>
  <button type="submit">Проверить</button>
</form>
<p id="result"></p>

<script>
  const form = document.querySelector("#demo-form");
  const result = document.querySelector("#result");
  const requiredFields = [
    { name: "name", label: "имя" }
  ];

  form.addEventListener("submit", (event) => {
    event.preventDefault();
    const firstEmpty = requiredFields.find(({ name }) => {
      return form.elements[name].value.trim() === "";
    });

    if (firstEmpty) {
      result.textContent = `Введите ${firstEmpty.label}`;
      form.elements[firstEmpty.name].focus();
      return;
    }

    result.textContent = "Форма готова к отправке";
  });
</script>

Метод trim() убирает пробелы с начала и конца строки. Поэтому строка из пробелов не проходит проверку обязательного поля.

После поиска первой ошибки вызовите focus(). Курсор переместится к нужному полю.

Регулярные выражения для email и телефона

Проверка email регулярным выражением отсекает очевидные опечатки. Она не доказывает, что ящик существует и принимает письма.

Идеального короткого шаблона для всех допустимых email нет.

Берите практичное правило под задачу. Подтверждайте адрес отдельным письмом, если это важно.

Для базовой проверки подойдет выражение /^[^\s@]+@[^\s@]+\.[^\s@]+$/. Оно требует текст до @ и домен с точкой после него.

Формат телефона зависит от страны и проекта.

Для российских номеров подойдет учебный шаблон /^(?:\+7|8)\s?(?:\(\d{3}\)|\d{3})[\s-]?\d{3}[\s-]?\d{2}[\s-]?\d{2}$/. Он допускает пробелы, парные скобки и дефисы.

Шаблон принимает начало +7 или 8 и 10 цифр после него.

Для другой страны задайте отдельное правило. Совпадение с шаблоном не подтверждает существование номера.

Constraint Validation API

Constraint Validation API дает JavaScript доступ к ограничениям HTML. Документация MDN описывает методы и свойства API.

  • checkValidity() возвращает true, если поле или форма проходит ограничения.
  • reportValidity() проверяет ограничения и показывает встроенное сообщение при ошибке.
  • validity.valueMissing сообщает о пустом обязательном поле.
  • validity.typeMismatch указывает на неподходящий формат, например email.
  • validity.patternMismatch показывает нарушение правила pattern.
  • validity.tooShort сообщает о недостаточной длине пользовательского ввода.

Метод setCustomValidity() задает собственный текст ошибки. Перед новой проверкой передайте пустую строку. Иначе старое сообщение сохранит поле невалидным.

<form id="api-form" novalidate>
  <label for="email">Email</label>
  <input id="email" name="email" type="email" required>
  <button type="submit">Проверить</button>
</form>

<script>
  const form = document.querySelector("#api-form");
  const email = document.querySelector("#email");

  form.addEventListener("submit", (event) => {
    event.preventDefault();
    email.setCustomValidity("");

    if (email.validity.valueMissing) {
      email.setCustomValidity("Введите электронный адрес");
    } else if (email.validity.typeMismatch) {
      email.setCustomValidity("Введите корректный электронный адрес");
    }

    if (form.checkValidity()) {
      console.log("Email подходит");
    } else {
      form.reportValidity();
    }
  });
</script>

Атрибут novalidate позволяет обработчику получить управление при неверном email.

Метод checkValidity() учитывает сообщение, заданное через setCustomValidity(). Пустая строка снимает только пользовательскую ошибку, сохраняя ограничения HTML.

Сообщения об ошибках рядом с полем

Создайте отдельный элемент ошибки для каждого поля. Свяжите его с контролом через aria-describedby. При ошибке установите aria-invalid="true".

<label for="email">Email</label>
<input id="email" name="email" aria-describedby="email-error">
<span id="email-error"></span>

Класс ошибки меняет рамку через CSS.

Событие blur запускает проверку после ухода из поля. Событие input обновляет сообщение во время исправления. Пользователь видит результат сразу, без повторной отправки формы.

Показывайте текст рядом с контролом. Окно alert() перекрывает форму и прерывает заполнение.

Универсальная функция проверки любой формы

Универсальная функция получает правила из объекта настроек. Каждое правило содержит проверку и сообщение. Добавление поля не меняет общий цикл.

Простые ограничения можно хранить в data-атрибутах. Например, data-required обозначит обязательное поле, а data-rule="phone" выберет проверку телефона. Сам атрибут ничего не проверяет: скрипт должен прочитать его значение.

Объект настроек удобнее для функций проверки и разных текстов ошибок.

Итоговый листинг объединяет HTML, CSS и JavaScript. Сохраните его в файл index.html.

Открытый локальный файл позволит проверить поля, но не серверную отправку. Для отправки запустите его вместе с обработчиком contact.php. Код обработчика и команда запуска приведены в разделе про fetch.

<!doctype html>
<html lang="ru">
<head>
  <meta charset="utf-8">
  <title>Проверка формы</title>
  <style>
    .field-error {
      display: block;
      min-height: 1.4em;
      color: #b42318;
    }

    .has-error {
      border: 2px solid #b42318;
    }
  </style>
</head>
<body>
  <form id="contact-form" action="contact.php" method="post" novalidate>
    <label for="name">Имя (обязательно)</label>
    <input id="name" name="name" type="text" autocomplete="name" required aria-describedby="name-error">
    <span id="name-error" class="field-error"></span>

    <label for="email">Email (обязательно)</label>
    <input id="email" name="email" type="email" autocomplete="email" required aria-describedby="email-error">
    <span id="email-error" class="field-error"></span>

    <label for="phone">Телефон (обязательно)</label>
    <input id="phone" name="phone" type="tel" autocomplete="tel" required aria-describedby="phone-error">
    <span id="phone-error" class="field-error"></span>

    <label for="message">Сообщение (обязательно, минимум 10 символов)</label>
    <textarea id="message" name="message" required aria-describedby="message-error"></textarea>
    <span id="message-error" class="field-error"></span>

    <button type="submit">Отправить</button>
    <p id="form-status" role="status"></p>
  </form>

  <script>
    const form = document.querySelector("#contact-form");
    const submitButton = form.querySelector("button[type=submit]");
    const status = document.querySelector("#form-status");
    let isSending = false;

    const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    const phonePattern = /^(?:\+7|8)\s?(?:\(\d{3}\)|\d{3})[\s-]?\d{3}[\s-]?\d{2}[\s-]?\d{2}$/;

    const rules = {
      name: [
        { check: (value) => value.trim() !== "", message: "Введите имя" }
      ],
      email: [
        { check: (value) => value.trim() !== "", message: "Введите email" },
        { check: (value) => emailPattern.test(value.trim()), message: "Введите корректный email" }
      ],
      phone: [
        { check: (value) => value.trim() !== "", message: "Введите телефон" },
        { check: (value) => phonePattern.test(value.trim()), message: "Введите телефон в формате +7 900 123-45-67" }
      ],
      message: [
        { check: (value) => value.trim() !== "", message: "Введите сообщение" },
        { check: (value) => Array.from(value.trim()).length >= 10, message: "Сообщение должно содержать минимум 10 символов" }
      ]
    };

    function showError(field, message) {
      const error = document.querySelector(`#${field.id}-error`);
      const hasError = Boolean(message);

      error.textContent = message;
      field.classList.toggle("has-error", hasError);
      field.setAttribute("aria-invalid", String(hasError));
      field.setCustomValidity(message);
    }

    function validateField(fieldName) {
      const field = form.elements.namedItem(fieldName);
      const failedRule = rules[fieldName].find((rule) => !rule.check(field.value));
      const message = failedRule ? failedRule.message : "";

      showError(field, message);
      return !message;
    }

    function validateForm() {
      let firstInvalidField = null;
      let isValid = true;

      for (const fieldName of Object.keys(rules)) {
        const fieldIsValid = validateField(fieldName);
        const field = form.elements.namedItem(fieldName);

        if (!fieldIsValid) {
          isValid = false;
          firstInvalidField ??= field;
        }
      }

      firstInvalidField?.focus();
      return isValid;
    }

    for (const fieldName of Object.keys(rules)) {
      const field = form.elements.namedItem(fieldName);
      field.addEventListener("blur", () => validateField(fieldName));
      field.addEventListener("input", () => validateField(fieldName));
    }

    form.addEventListener("submit", async (event) => {
      event.preventDefault();
      if (isSending) return;
      status.textContent = "";

      if (!validateForm()) {
        return;
      }

      isSending = true;
      submitButton.disabled = true;

      try {
        const response = await fetch(form.action, {
          method: "POST",
          body: new FormData(form),
          headers: { Accept: "application/json" }
        });
        const result = await response.json();

        if (result.errors && typeof result.errors === "object") {
          let firstInvalidField = null;

          for (const fieldName of Object.keys(rules)) {
            const message = result.errors[fieldName];
            if (typeof message !== "string" || !message) continue;

            const field = form.elements.namedItem(fieldName);
            showError(field, message);
            firstInvalidField ??= field;
          }

          firstInvalidField?.focus();
          status.textContent = "Исправьте ошибки в форме";
          return;
        }

        if (!response.ok || result.ok !== true) {
          throw new Error("Сервер не подтвердил прием данных");
        }

        status.textContent = "Сервер принял данные";
        form.reset();
        for (const fieldName of Object.keys(rules)) {
          showError(form.elements.namedItem(fieldName), "");
        }
      } catch (error) {
        status.textContent = "Не удалось отправить форму. Попробуйте еще раз.";
      } finally {
        isSending = false;
        submitButton.disabled = false;
      }
    });
  </script>
</body>
</html>

Функция проходит правила по порядку. Она показывает первое сообщение для каждого поля. Первый ошибочный контрол получает фокус.

После успешной проверки функция возвращает true.

Обработчик блокирует кнопку до завершения запроса. Флаг isSending дополнительно останавливает повторный запуск обработчика.

Отправка формы после проверки через fetch

Объект FormData собирает значения полей с атрибутом name. Передайте его в fetch как тело POST-запроса.

Документация MDN описывает FormData и Fetch API.

Для FormData не задавайте вручную заголовок Content-Type. Браузер добавляет границу multipart-запроса сам.

Пример использует локальный PHP-обработчик contact.php. Он принимает 4 поля, повторяет проверки и возвращает JSON. Обработчик демонстрирует прием данных без сохранения и отправки писем.

Сохраните следующий код рядом с index.html под именем contact.php.

<?php
header('Content-Type: application/json; charset=utf-8');

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    http_response_code(405);
    header('Allow: POST');
    echo json_encode(['ok' => false]);
    exit;
}

$labels = [
    'name' => 'Введите имя',
    'email' => 'Введите email',
    'phone' => 'Введите телефон',
    'message' => 'Введите сообщение'
];
$data = [];
$errors = [];

foreach ($labels as $field => $message) {
    $raw = $_POST[$field] ?? '';
    $value = is_string($raw)
        ? preg_replace('/^[\s\p{Z}\x{FEFF}]+|[\s\p{Z}\x{FEFF}]+$/u', '', $raw)
        : null;
    $data[$field] = $value ?? '';

    if ($value === null || $value === '') {
        $errors[$field] = $message;
    }
}

if (!isset($errors['email']) &&
    !preg_match('/^[^\s@]+@[^\s@]+\.[^\s@]+$/u', $data['email'])) {
    $errors['email'] = 'Введите корректный email';
}

if (!isset($errors['phone']) &&
    !preg_match('/^(?:\+7|8)\s?(?:\(\d{3}\)|\d{3})[\s-]?\d{3}[\s-]?\d{2}[\s-]?\d{2}$/u', $data['phone'])) {
    $errors['phone'] = 'Введите телефон в формате +7 900 123-45-67';
}

if (!isset($errors['message']) &&
    preg_match_all('/./us', $data['message']) < 10) {
    $errors['message'] = 'Сообщение должно содержать минимум 10 символов';
}

if ($errors) {
    http_response_code(422);
    echo json_encode(['errors' => $errors], JSON_UNESCAPED_UNICODE);
    exit;
}

echo json_encode(['ok' => true]);

Для запуска нужен установленный PHP с доступной командой php. Порядок работы встроенного сервера описывает документация PHP.

  1. Откройте терминал в папке с двумя файлами. Команда php -v должна вывести сведения об установленном PHP.
  2. Запустите php -S 127.0.0.1:8000. Оставьте терминал открытым: он обслуживает запросы браузера.
  3. Введите в адресной строке браузера http://127.0.0.1:8000/index.html. На странице появится форма с 4 полями.
  4. Отправьте пустую форму. Под полями появятся сообщения, а первое поле получит фокус.
  5. Введите имя «Анна», email anna@example.com и телефон +7 900 123-45-67. В сообщении напишите «Прошу связаться со мной».
  6. Нажмите «Отправить». Текст «Сервер принял данные» подтвердит успешный запрос, а поля очистятся.

Чтобы остановить локальный сервер, нажмите Ctrl+C в его терминале.

Встроенный сервер предназначен для разработки. Не используйте этот учебный обработчик как готовый публичный сервис.

Сервер возвращает HTTP 422 при ошибках полей. Например, ответ {"errors":{"email":"Введите корректный email"}} привяжет сообщение к email. Клиент разбирает JSON до проверки HTTP-статуса, поэтому покажет эту ошибку.

Ответ {"ok":true} вместе с успешным HTTP-статусом подтверждает прием.

Обработчик выводит сообщения через textContent. Общий результат попадает в элемент с role="status".

Для проверки серверной ошибки увеличьте минимум сообщения в PHP до 20. Отправьте текст «Проверка формы». Клиент его пропустит, а сервер вернет ошибку. Затем верните порог 10.

Для публичного сайта добавьте ограничения размера запроса и защиту от спама. Сохранение данных или отправку письма выполняйте после серверной проверки.

Ошибки при проверке формы

  • Проверка по click. Форма может отправиться с клавиатуры. Слушайте submit самой формы.
  • Пробелы проходят как значение. Сравнение с пустой строкой пропускает строку из пробелов. Используйте trim().
  • return false в addEventListener. Это значение не отменяет отправку. Вызовите event.preventDefault().
  • Нет серверной проверки. Клиентский код можно обойти. Сервер повторяет правила перед обработкой.
  • alert для каждого поля. Окно скрывает форму и прерывает заполнение. Выводите текст рядом с полем.

Совет от нашей редакции: проверьте форму с пустыми полями и отключенным сервером. Пользователь должен увидеть понятное сообщение в обоих случаях.

Комментарии

Оставьте свой комментарий
G
Guest579

Ничего не пойму. я "Чайник"

N
NIKA

Что-то разобраться не могу

С
Стас

Тут ошибок дохрена!

В форме нету имени

<form action="/cgi-bin/formmail.cgi" onsubmit="return sendform();">
когда тут ясно прописано mailform

document.mailform.name.focus();

A
Alik007

А можете подсказать где найти урок по устоновке скрипта антибота.