#162 - Change Horizontal Tabs on Page Scroll

Auto‑switch horizontal tabs in Webflow as you scroll, locking the scroll inside the tab section.

Video Tutorial

tutorial.mov

Watch the video for step-by-step implementation instructions

The Code

115 lines
Paste this into Webflow
<!-- 💙 MEMBERSCRIPT #162 v0.1 💙 - CHANGE HORIZONTAL TABS WHEN PAGE IS SCROLLED -->

<script>
(function() {
  // Disable on tablet and funcmobile(only run on desktop ≥ 992px)
  if (window.matchMedia('(max-width: 991px)').matches) return;

  const tabSection = document.querySelector('[data-ms-code="tab-section"]');
  if (!tabSection) return;

  const tabButtons = Array.from(tabSection.querySelectorAll('[data-ms-code^="tab-"]'))
    .filter(btn => !btn.hasAttribute('data-ms-code') || !btn.getAttribute('data-ms-code').startsWith('tab-content-'));
  const tabContents = Array.from(tabSection.querySelectorAll('[data-ms-code^="tab-content-"]'));

  if (!tabButtons.length || !tabContents.length) return;

  let isLocked = true;
  let isTouching = false;
  let touchStartY = 0;
  let lastTabChange = 0;
  const cooldown = 500; // ms

  function getCurrentTabIndex() {
    return tabButtons.findIndex(btn => btn.classList.contains('w--current'));
  }

  function activateTab(index) {
    if (index < 0 || index >= tabButtons.length) return;
    tabButtons[index].click();
  }

  function isTabSectionInView() {
    const rect = tabSection.getBoundingClientRect();
    return rect.top < window.innerHeight && rect.bottom > 0;
  }

  function tryTabChange(direction, event) {
    const now = Date.now();
    if (now - lastTabChange < cooldown) return;
    let currentTab = getCurrentTabIndex();
    if (direction > 0 && currentTab < tabButtons.length - 1) {
      event.preventDefault();
      activateTab(currentTab + 1);
      isLocked = true;
    } else if (direction < 0 && currentTab > 0) {
      event.preventDefault();
      activateTab(currentTab - 1);
      isLocked = true;
    } else {
      isLocked = false;
      const rect = tabSection.getBoundingClientRect();
      window.scrollBy({
        top: direction > 0 ? rect.bottom - 1 : rect.top - window.innerHeight + 1,
        left: 0,
        behavior: 'smooth'
      });
    }
    lastTabChange = now;
  }

  function onWheel(e) {
    if (!isTabSectionInView()) return;
    if (e.deltaY > 0) {
      tryTabChange(1, e);
    } else if (e.deltaY < 0) {
      tryTabChange(-1, e);
    }
  }

  function onTouchStart(e) {
    if (!isTabSectionInView()) return;
    isTouching = true;
    touchStartY = e.touches[0].clientY;
  }

  function onTouchMove(e) {
    if (!isTabSectionInView() || !isTouching) return;
    const now = Date.now();
    if (now - lastTabChange < cooldown) return;
    const deltaY = touchStartY - e.touches[0].clientY;
    if (Math.abs(deltaY) > 30) {
      tryTabChange(deltaY > 0 ? 1 : -1, e);
      isTouching = false;
    }
  }

  function onTouchEnd() {
    isTouching = false;
  }

  function preventScroll(e) {
    if (isLocked && isTabSectionInView()) {
      e.preventDefault();
      e.stopPropagation();
      return false;
    }
  }

  window.addEventListener('scroll', () => {
    const currentTab = getCurrentTabIndex();
    isLocked = isTabSectionInView() && currentTab > 0 && currentTab < tabButtons.length - 1;
  });

  tabSection.addEventListener('wheel', onWheel, { passive: false });
  tabSection.addEventListener('touchstart', onTouchStart, { passive: false });
  tabSection.addEventListener('touchmove', onTouchMove, { passive: false });
  tabSection.addEventListener('touchend', onTouchEnd, { passive: false });

  document.addEventListener('wheel', preventScroll, { passive: false });
  document.addEventListener('touchmove', preventScroll, { passive: false });

  const initialTab = getCurrentTabIndex();
  isLocked = initialTab > 0 && initialTab < tabButtons.length - 1;
})();
</script>

Script Info

Versionv0.1
PublishedNov 11, 2025
Last UpdatedNov 11, 2025

Need Help?

Join our Slack community for support, questions, and script requests.

Join Slack Community
Back to All Scripts

Related Scripts

More scripts in UX