Управление якорями в Off-Canvas Elementor

Каждый кто решил будет круто использовать виджет Off-Canvas Elementor в качестве мобильного меню или полноэкранного меню в десктопной версии (и такое случается). Да использовать вне холста лучше для SEO, в отличие от Popup навигация доступна для индексирования. Наталкивался на проблему якорных ссылок, при переходе на которые холст остается на месте. Раньше я пользовался костылями, которые дергали кнопку закрытия, что иногда приводило к конфликтам, или работало не так я хотел. Так вот совместно с цифровыми помощниками написал вот такой универсальный код, который работает даже если ан странице несколько Off-Canvas.

Проблема

Когда в Elementor используется Off-Canvas (всплывающая боковая панель, часто применяемая для меню), клики по якорным ссылкам (#section) часто ведут себя некорректно:

  • страница не прокручивается к нужному блоку;
  • окно Off-Canvas остаётся открытым, перекрывая контент;
  • плавный скролл не работает, или срабатывает до того, как панель закроется.

Эта проблема особенно заметна на мобильных устройствах, где UX сильно зависит от плавности перехода.

Решение: скрипт для корректной работы якорей

Ниже приведён код, который устраняет эти проблемы и делает работу Off-Canvas более «умной» и удобной:

<style>
.e-off-canvas-hidden {
  display: none;
}

/* Анимация оверлея */
.e-off-canvas-overlay-hidden {
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s ease;
}
.e-off-canvas__overlay {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.3s ease;
}

/* Плавная анимация крестика (SVG иконки) */
.eicon-close,
.offcanvas-close svg {
  transition: transform 0.4s ease;
  transform-origin: center;
}

/* Когда off-canvas открыт */
.elementor-off-canvas--active .offcanvas-close svg {
  transform: rotate(180deg);
}

/* Эффект при наведении */
.offcanvas-close:hover svg {
  transform: rotate(90deg);
}

</style>

<script>
jQuery(function ($) {
  const CLOSE_DELAY = 800; // задержка, пока Off Canvas закрывается
  const initializedCanvases = new Set();

  /** Плавная прокрутка с fallback для Firefox/Safari */
  function smoothScrollTo(element) {
    if (!element) return;
    try {
      // Современные браузеры
      element.scrollIntoView({ behavior: "smooth", block: "start" });
    } catch (e) {
      // Fallback для мобильного Firefox и старых браузеров
      const target = element.getBoundingClientRect().top + window.scrollY;
      window.scrollTo({ top: target, behavior: "smooth" });
    }
  }

  function initOffCanvas($offCanvas) {
    const id = $offCanvas.attr("data-id") || $offCanvas.index();
    if (initializedCanvases.has(id)) return;
    initializedCanvases.add(id);

    const $overlay = $offCanvas.find(".e-off-canvas__overlay");
    const $body = $("body");

    function hideOffCanvas() {
      $overlay.addClass("e-off-canvas-overlay-hidden");
      $body.removeClass("e-off-canvas__no-scroll");

      // Отложенное скрытие элемента, чтобы transition завершился
      setTimeout(() => {
        $offCanvas.addClass("e-off-canvas-hidden");
      }, 300);
    }

    function showOffCanvas() {
      $offCanvas.removeClass("e-off-canvas-hidden");
      $overlay.removeClass("e-off-canvas-overlay-hidden");
      $body.addClass("e-off-canvas__no-scroll");
    }

    function triggerOffCanvasClose() {
      const $btn = $offCanvas.find(
        'a[href*="elementor-action%3Aaction%3Doff_canvas%3Aclose"], [data-dismiss="dialog"], .eicon-close, .dialog-widget-close-button'
      ).first();
      if ($btn.length) {
        $btn.trigger("click");
        return true;
      }
      hideOffCanvas();
      return false;
    }

    // Обработка кликов по ссылкам внутри Off Canvas
    $offCanvas.on(
      "click",
      'a:not([href*="elementor-action%3Aaction%3Doff_canvas%3Aclose"]):not([data-dismiss="dialog"]):not(.eicon-close):not(.dialog-widget-close-button)',
      function (e) {
        const href = $(this).attr("href") || "";
        const [path, hash] = href.split("#");
        const samePage = !path || path === window.location.pathname;

        if (hash && samePage) {
          e.preventDefault();
          const anchor = document.getElementById(hash);
          if (triggerOffCanvasClose()) {
            setTimeout(() => smoothScrollTo(anchor), CLOSE_DELAY);
          } else {
            smoothScrollTo(anchor);
          }
        } else {
          triggerOffCanvasClose();
        }
      }
    );

    // Кнопка открытия
    $(document).on(
      "click",
      `[data-target="#${$offCanvas.attr("id")}"], .off-canvas-toggle-button`,
      showOffCanvas
    );

    console.info(`[OffCanvas] initialized →`, id);
  }

  // Инициализация всех Off Canvas
  function initAll() {
    $('[class^="e-off-canvas"]').each(function () {
      initOffCanvas($(this));
    });
  }

  initAll();

  // Автоматическая инициализация новых Off Canvas (через AJAX / Elementor)
  const observer = new MutationObserver((mutations) => {
    for (const m of mutations) {
      for (const node of m.addedNodes) {
        if (node.nodeType === 1 && node.matches('[class^="e-off-canvas"]')) {
          initOffCanvas($(node));
        } else if (node.nodeType === 1) {
          $(node)
            .find('[class^="e-off-canvas"]')
            .each(function () {
              initOffCanvas($(this));
            });
        }
      }
    }
  });

  observer.observe(document.body, { childList: true, subtree: true });
});
</script>

Как использовать

Вставьте код в виджет «HTML» на странице, где используется Off-Canvas (первым блоком в контейнере, в подвал сайта или с помощью плагина сниппетов кода).

→ В Elementor: HTML → Вставить код → Обновить страницу.

Как это работает

jQuery находит все Off-Canvas элементы Elementor (.e-off-canvas…).

Для каждого:

Отслеживаются клики по ссылкам.

  1. Если ссылка ведёт к якорю на текущей странице — окно Off-Canvas плавно закрывается.
  2. После закрытия (через setTimeout) страница прокручивается к нужному элементу (scrollIntoView с behavior: &#8217;smooth&#8217;).

Для новых Off-Canvas, созданных динамически (например, через AJAX или Elementor Popups), работает MutationObserver — автоматическая инициализация без перезагрузки страницы.

Улучшение UX

Можно добавить лёгкую анимацию иконки «крестика» при закрытии меню:

.eicon-close {
transition: transform 0.3s ease;
}
.eicon-close:hover {
transform: rotate(90deg);
}

Если используется SVG-иконка, можно применить тот же эффект к контейнеру SVG:

.off-canvas-close svg {
transition: transform 0.3s ease;
}
.off-canvas-close svg:hover {
transform: rotate(90deg);
}

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

Global

Если у вас есть вопрос?

По вопросу статьи или что-то связанно свашим цифровым проектом, просто свяжитесь со мной?

Связаться сейчас

Похожие записи

Перезвонить?

Хотите развивать бизнес онлайн, мы можем вам помочь!