Суть проблемы
Клиент поставил новый плагин формы обратной связи, и «всё сломалось»: три независимых интерактив-элемента перестали реагировать. Поверхностно симптомы выглядят как три разных бага, но корневая причина одна — нарушенный порядок загрузки скриптов и дублирование jQuery.
- !Кнопка «Заказать звонок» в шапке не реагирует на кликОбработчик
$('.callback-btn').on('click', ...)молча не срабатывает — визуально кнопка живая, но ничего не происходит. - !Слайдер на главной крутится криво и застреваетSlick инициализируется, стрелки мёртвые, перелистывание в одну сторону,autoplay не работает.
- !Модальное окно формы не открывается по клику на CTAКласс
.openне toggglится, окно остаётся вdisplay:none. - !В DevTools Console — россыпь ошибок
Uncaught TypeError: $(...).slick is not a function,Cannot read properties of null,$ is not a function— шесть записей.
6 ошибок в Console, 3 нерабочих интерактив-элемента, jQuery подключён дважды (header + footer), slick.min.js грузится раньше jQuery, кастомный JS в <head> без ожидания DOM.
0 ошибок в Console, все 3 элемента работают, jQuery подключён один раз, зависимости выстроены через ['jquery'], кастомный JS обёрнут в jQuery(function($){ ... }) с делегированием событий.
Как диагностировал
Сначала собрал все ошибки из консоли — они указали на два разных класса проблем: потерю плагина slick и обращение к несуществующему $. Дальше пошёл по Network и Sources, чтобы понять, почему $.fn оказалась пустой.
- Собрал ошибки в Console
$(...).slick is not a function— плагин slick недоступен на момент вызова.$ is not a function— глобальный$перезаписан или отсутствует.Cannot read properties of null— селектор вернул пустой набор, обращение к.style/.classListпадает. - Проверил вкладку Network → JSУвидел, что
jquery.jsгрузится дважды: версия 1.12 из темы в<head>и версия 3.6 из нового плагина в<footer>. Вторая загрузка перезаписываетwindow.jQueryиwindow.$, и все плагины, навешенные на первую jQuery (включая slick), теряются —$.fn.slickстановитсяundefined. - Разобрался с кнопкой «Заказать звонок»Кастомный скрипт лежал в
<head>безdeferи безDOMContentLoaded. На момент выполнения$('.callback-btn')элемента ещё не было в DOM — обработчик навешивался на пустой набор. Плюс после второй загрузки jQuery$уже указывал на новую версию. - Разобрался с модалкойВ коде был селектор
$(.modal)— без кавычек. JavaScript трактовал.modalкак обращение к глобальной переменной (числовой литерал.modal→ ReferenceError в строгом режиме, в нестроком —undefined). На деле$уже не был функцией, поэтому падало ещё раньше. - Проверил порядок загрузки slickВ
functions.phpтемы slick регистрировался черезwp_enqueue_script('slick', ...)без указания зависимости['jquery']. Из-за этогоslick.min.jsмог оказаться в выдаче раньшеjquery.js— плагин подключался к несуществующему$и молча не регистрировался. - Сверил с моментом поломкиКлиент подтвердил: всё сломалось ровно после активации нового плагина формы. Плагин через
wp_enqueue_script('jquery', ...)подтягивал свою jQuery 3.6 в footer — это и была вторая загрузка, перетёршая$.fn.
Что исправил
Чинил снизу вверх: сначала выстроил правильный порядок и убрал дубликат jQuery, затем починил регистрацию slick с зависимостью, потом переписал кастомный JS под безопасную обёртку и делегирование событий.
1. Регистрация скриптов в functions.php
Тема подключала jQuery вручную тегом <script> в header, а slick регистрировался без зависимости. Новый плагин подтягивал свою jQuery в footer — итого два экземпляра.
// jQuery подключён вручную в header.php:
// <script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
function theme_enqueue_assets() {
// slick без зависимости от jquery — может загрузиться раньше
wp_enqueue_script(
'slick',
get_template_directory_uri() . '/js/slick.min.js',
array(), // <-- нет ['jquery']
'1.8.1',
true
);
wp_enqueue_script(
'theme-custom',
get_template_directory_uri() . '/js/custom.js',
array('slick'), // зависит от slick, но не от jquery
'1.0.0',
false // грузится в <head> -- ДО DOM
);
}
add_action('wp_enqueue_scripts', 'theme_enqueue_assets');
// Убрали ручной <script src jquery> из header.php.
// WordPress сам подключит jQuery из ядра один раз.
function theme_enqueue_assets() {
// jQuery из ядра WP — одна общая версия, без дублей
wp_enqueue_script('jquery');
wp_enqueue_script(
'slick',
get_template_directory_uri() . '/js/slick.min.js',
array('jquery'), // slick ждёт jquery
'1.8.1',
true
);
wp_enqueue_script(
'theme-custom',
get_template_directory_uri() . '/js/custom.js',
array('jquery', 'slick'), // ждёт и jquery, и slick
'1.0.1',
true // в footer -- после DOM
);
}
add_action('wp_enqueue_scripts', 'theme_enqueue_assets');
// Гарантированно снимаем дубль jQuery, который тянет плагин формы
function theme_dequeue_plugin_jquery() {
// Плагин регистрировал свою jquery в footer -- снимаем
wp_dequeue_script('plugin-form-jquery');
wp_deregister_script('plugin-form-jquery');
}
add_action('wp_enqueue_scripts', 'theme_dequeue_plugin_jquery', 100);
window.jQuery и window.$. Все плагины (.slick, .fancybox, .select2 …), навешенные на первый экземпляр через $.fn, исчезают — вызовы падают с ... is not a function. Правильный путь — одна jQuery из ядра WordPress, все остальные скрипты через ['jquery'].2. Кастомный JS: обёртка, делегирование и guard
Скрипт в <head> выполнялся до построения DOM, использовал глобальный $ (который после второй загрузки jQuery уже не та функция) и обращался к элементам напрямую. Переписал под безопасный паттерн.
// Скрипт в <head>, без ожидания DOM:
// Элемента ещё нет в DOM -- обработчик навешивается в никуда
$('.callback-btn').on('click', function() {
$(.modal).addClass('open'); // .modal без кавычек -- ReferenceError
});
// $ после второй загрузки jQuery уже не содержит .slick
$('.hero-slider').slick({
arrows: true,
autoplay: true,
autoplaySpeed: 4000
});
// Закрытие модалки -- тоже на прямой селектор
$('.modal-close').on('click', function() {
$('.modal').removeClass('open');
});
// Локальный $ внутри замыкания -- не зависит от того,
// что лежит в window.$ на момент выполнения.
// Срабатывает по DOMContentLoaded (jQuery готовит DOM сам).
jQuery(function($) {
// Делегирование: работает даже если кнопка отрендерится
// плагином позже или перерисуется.
$(document).on('click', '.callback-btn', function(e) {
e.preventDefault();
var $modal = $('.modal');
if ($modal.length === 0) return; // guard
$modal.addClass('open');
});
// Инициализация slick только если элемент есть
var $slider = $('.hero-slider');
if ($slider.length && $.fn.slick) {
$slider.slick({
arrows: true,
autoplay: true,
autoplaySpeed: 4000
});
}
// Закрытие -- тоже через делегирование
$(document).on('click', '.modal-close', function() {
$('.modal').removeClass('open');
});
});
Что именно починило каждое
Слайдер: зависимость ['jquery'] гарантирует, что slick грузится после jQuery, а $.fn.slick проверка защищает от тихого падения. Кнопка и модалка: обёртка jQuery(function($){ ... }) ждёт DOM и даёт локальный $; делегирование через $(document).on(...) переживает перерисовку; guard if ($modal.length === 0) return убирает Cannot read properties of null.
Результат
После правок порядка загрузки и переписывания кастомного JS консоль стала чистой, а весь интерактив заработал стабильно. Повторное подключение jQuery исчезло, объём передаваемого JS в шапке уменьшился.
Слайдер, кнопка «Заказать звонок» и модальное окно формы работают стабильно, перерисовка DOM плагином перестала ломить обработчики. JS-зависимости выстроены через wp_enqueue_script с корректными массивами ['jquery'], так что новый плагин больше не может «перетереть» jQuery незаметно.