Ленивая загрузка и рендеринг reCAPTCHA для модальных окон Bootstrap и других

Показывать справа: 0

В современных веб-приложениях часто используется динамический контент, в частности, модальные окна Bootstrap. Если форма внутри такого окна содержит Google reCAPTCHA, возникает дилемма: либо загружать тяжелый скрипт капчи при старте страницы (влияя на скорость загрузки), либо загружать его по требованию (ленивая загрузка).

При ленивой загрузке ключевая проблема — избежать ошибки **"reCAPTCHA has already been rendered in this element"** при повторном показе окна. В этой статье мы рассмотрим, как интегрировать ленивую загрузку скрипта reCAPTCHA, используя нативные события Bootstrap, и как безопасно рендерить виджет только в открывшемся динамическом модальном окне.

Проблема: Производительность и повторный рендеринг

Скрипт Google reCAPTCHA является внешним и может замедлять начальную загрузку страницы. Если модальное окно с формой открывается редко, логично загружать этот скрипт только в момент необходимости. Однако, если мы просто добавим тег `<script>` в обработчик открытия окна, мы столкнемся с двумя сложностями:

  1. Скрипт может загружаться повторно при каждом открытии модального окна.
  2. Если скрипт уже был загружен, повторный вызов `grecaptcha.render()` для того же ID элемента вызовет фатальную ошибку.

Решение: Ленивая загрузка через событие Bootstrap

Идеальное решение заключается в следующем:

  1. Загружать внешний скрипт reCAPTCHA только один раз за сессию, используя глобальный флаг.
  2. Использовать событие Bootstrap `shown.bs.modal`, которое срабатывает, когда окно **полностью видимо**.
  3. Использовать делегирование событий на `document`, чтобы перехватывать событие от **любого** модального окна.
  4. При закрытии окна очищать DOM-элементы, созданные reCAPTCHA, чтобы разрешить повторный рендеринг при следующем показе.

Пример реализации

Для реализации нам потребуются три основные части: управление состоянием загрузки, функция загрузки скрипта и функция рендеринга с защитой от повтора.



// Переменная, чтобы знать, загружен ли уже API
let isRecaptchaApiLoaded = false;

function loadRecaptchaScript(callback) {
    if (isRecaptchaApiLoaded) {
        // Если скрипт уже загружен, просто вызываем колбэк
        callback();
        return;
    }

    const scriptUrl = "//www.google.com/recaptcha/api.js?onload=handleRecaptchaLoad&hl=ru";
    const script = document.createElement('script');
    script.src = scriptUrl;
    script.async = true;
    script.defer = true;
    
    // Устанавливаем глобальную функцию, которую вызовет Google после загрузки
    window.handleRecaptchaLoad = function() {
        isRecaptchaAfpiLoaded = true;
        callback();
    };
    
    document.head.appendChild(script);
}
function renderSpecificRecaptcha(containerId) {



    const container = $(containerId); // Или используйте document.getElementById(containerId.substring(1))
    
    if (container.length === 0) {
        console.error("Контейнер не найден:", containerId);
        return;
    }
    
    const captchaSelector = '.g-recaptcha_webalan';
    
    // Проверяем, есть ли элемент, который мы будем рендерить
    if (container.find(captchaSelector).length === 0) {
        return; // Нет нужной разметки в этом контейнере
    }
    
    const kap = container.find(captchaSelector);
    const ids = kap.attr('id');
    const sitekey = kap.data('sitekey');
    
    // ----------------------------------------------------
    // КЛЮЧЕВАЯ ЗАЩИТА ОТ ПОВТОРНОГО РЕНДЕРИНГА:
    // Проверяем, содержит ли этот конкретный div уже класс g-recaptcha
    // или какой-либо iframe, созданный Google.
    if (kap.find('.g-recaptcha').length > 0 || kap.find('iframe').length > 0) {
        console.warn('reCAPTCHA уже отрисован в:', ids, '. Пропускаем.');
        return;
    }
    // ----------------------------------------------------

    if (!ids || !sitekey) {
        console.error("Не хватает ID или Sitekey для рендеринга.");
        return;
    }
    
    // Рендеринг
    grecaptcha.render(ids, {
        'sitekey': sitekey,
        // ... другие опции
    });
    
    console.log(`reCAPTCHA успешно отрендерена в: ${ids}`);
}


 $(document).on('shown.bs.modal', function (e) {
        // e.target содержит DOM-элемент самого модального окна, которое только что показалось
        const $modalElement = $(e.target);
 
  if ($modalElement.find('.g-recaptcha_webalan').length > 0) {
            
          var id = "#"+$modalElement.attr('id');
            
            // 1. Загружаем скрипт (он загрузится только один раз)
            loadRecaptchaScript(function() {
                // 2. Рендерим капчу внутри ТОЛЬКО что показанного элемента
                renderSpecificRecaptcha(id);
            });
        }


  });
  
  
   //  вызов рекапчи
 loadRecaptchaScript(function() {
        renderSpecificRecaptcha(id);
    });
  

        

Разбор ключевых моментов

Для успешной реализации необходимо понимать роль каждого компонента:

  • Ленивая загрузка скрипта: Функция loadRecaptchaScript добавляет тег `<script>` только один раз, используя `window.handleRecaptchaLoad` как колбэк, который вызывается самой Google после готовности API.
  • Событие shown.bs.modal: Это событие гарантирует, что мы пытаемся рендерить виджет только тогда, когда модальное окно стало полностью видимым.
  • Делегирование на $(document): Позволяет отслеживать события любых модальных окон, независимо от их ID.
  • Защита от повтора: Проверка kap.find('.g-recaptcha').length > 0 в renderSpecificRecaptcha — это решающий фактор, предотвращающий ошибку "already been rendered", даже если обработчик показа срабатывает повторно.

HTML-разметка для контейнера

HTML-структура внутри любого модального окна должна содержать контейнер с нужным ID и ключом:

<div class="modal fade" id="dynamicModal1" tabindex="-1" role="dialog">
    <div class="modal-body">
        <!-- Этот контейнер будет найден JS-кодом -->
        <div class="g-recaptcha_webalan" 
             id="modal_captcha_123" 
             data-sitekey="ВАШ_ПУБЛИЧНЫЙ_SITEKEY_ЗДЕСЬ">
        </div>
    </div>
</div>

        

Заключение

Использование событий Bootstrap в сочетании с ленивой загрузкой и проверкой состояния API позволяет значительно улучшить производительность загрузки страницы, отложив инициализацию reCAPTCHA до момента, когда она действительно потребуется пользователю в динамически открытом модальном окне. При этом использование делегированных событий обеспечивает гибкость для любых модальных окон в приложении.

Покупка готового скрипта joomla 3

или просто напишите в телеграмм https://t.me/webalan