chunk-006-back-to-top.js

I’ll make this as a strong, self-contained chunk-006-back-to-top.js file: fast, accessible, mobile-friendly, customizable, and safe for WordPress theme loading.

For this file:

assets/static-js/static-chunks-js/chunk-006-back-to-top.js

Use this advanced Back To Top chunk. It creates the button automatically, supports scroll progress, accessibility, keyboard control, reduced-motion, mobile safe behavior, custom CSS variables, footer collision protection, and WordPress-friendly loading.

/*!
 * RX Theme - Back To Top Advanced Chunk
 * File: assets/static-js/static-chunks-js/chunk-006-back-to-top.js
 * Author: RxHarun
 * Purpose: Advanced, accessible, performance-friendly Back To Top button
 */

(function () {
  "use strict";

  /**
   * ============================================================
   * RX Back To Top Configuration
   * ============================================================
   */

  const RX_BACK_TO_TOP_CONFIG = {
    buttonId: "rx-back-to-top",
    progressId: "rx-back-to-top-progress",
    wrapperClass: "rx-back-to-top-wrap",
    visibleClass: "is-visible",
    activeClass: "is-active",
    scrollingClass: "is-scrolling",
    footerAwareClass: "is-footer-aware",

    showAfter: 320,
    hideBelow: 200,

    scrollDuration: 550,
    scrollOffset: 0,

    rightDesktop: 24,
    bottomDesktop: 24,
    rightMobile: 16,
    bottomMobile: 16,

    zIndex: 9999,

    buttonSizeDesktop: 52,
    buttonSizeMobile: 46,

    enableProgressRing: true,
    enableKeyboardShortcut: true,
    enableFooterCollisionProtection: true,
    enableAutoCreateStyle: true,
    enableReducedMotionRespect: true,
    enableScrollSpyClass: true,

    keyboardShortcutKey: "Home",
    keyboardShortcutAlt: true,

    footerSelectors: [
      "footer",
      ".site-footer",
      "#colophon",
      ".rx-footer",
      "[data-rx-footer]"
    ],

    ignoreWhenSelectorsExist: [
      ".wp-admin",
      ".block-editor-page"
    ],

    ariaLabel: "Back to top",
    title: "Back to top",

    iconSvg:
      '<svg class="rx-back-to-top-icon" width="22" height="22" viewBox="0 0 24 24" aria-hidden="true" focusable="false">' +
      '<path d="M12 5.25c.28 0 .55.11.75.31l6 6a1.06 1.06 0 0 1-1.5 1.5L13.06 8.87V18a1.06 1.06 0 0 1-2.12 0V8.87l-4.19 4.19a1.06 1.06 0 0 1-1.5-1.5l6-6c.2-.2.47-.31.75-.31Z" fill="currentColor"></path>' +
      '</svg>'
  };

  /**
   * ============================================================
   * Safe Helpers
   * ============================================================
   */

  const doc = document;
  const win = window;
  const html = doc.documentElement;
  const body = doc.body;

  if (!body || !html) {
    return;
  }

  function qs(selector, context) {
    return (context || doc).querySelector(selector);
  }

  function qsa(selector, context) {
    return Array.prototype.slice.call(
      (context || doc).querySelectorAll(selector)
    );
  }

  function clamp(value, min, max) {
    return Math.min(Math.max(value, min), max);
  }

  function isNumber(value) {
    return typeof value === "number" && Number.isFinite(value);
  }

  function prefersReducedMotion() {
    return (
      RX_BACK_TO_TOP_CONFIG.enableReducedMotionRespect &&
      win.matchMedia &&
      win.matchMedia("(prefers-reduced-motion: reduce)").matches
    );
  }

  function isMobileViewport() {
    return win.matchMedia && win.matchMedia("(max-width: 767px)").matches;
  }

  function getScrollTop() {
    return win.pageYOffset || html.scrollTop || body.scrollTop || 0;
  }

  function getDocumentHeight() {
    return Math.max(
      body.scrollHeight,
      body.offsetHeight,
      html.clientHeight,
      html.scrollHeight,
      html.offsetHeight
    );
  }

  function getViewportHeight() {
    return win.innerHeight || html.clientHeight || 0;
  }

  function getMaxScrollTop() {
    return Math.max(0, getDocumentHeight() - getViewportHeight());
  }

  function getScrollProgress() {
    const maxScroll = getMaxScrollTop();

    if (maxScroll <= 0) {
      return 0;
    }

    return clamp((getScrollTop() / maxScroll) * 100, 0, 100);
  }

  function shouldIgnorePage() {
    return RX_BACK_TO_TOP_CONFIG.ignoreWhenSelectorsExist.some(function (selector) {
      return qs(selector);
    });
  }

  function rafThrottle(callback) {
    let ticking = false;

    return function throttled() {
      const args = arguments;
      const context = this;

      if (!ticking) {
        win.requestAnimationFrame(function () {
          callback.apply(context, args);
          ticking = false;
        });

        ticking = true;
      }
    };
  }

  /**
   * ============================================================
   * Auto CSS Injection
   * ============================================================
   */

  function injectBackToTopStyles() {
    if (!RX_BACK_TO_TOP_CONFIG.enableAutoCreateStyle) {
      return;
    }

    if (qs("#rx-back-to-top-style")) {
      return;
    }

    const style = doc.createElement("style");
    style.id = "rx-back-to-top-style";

    style.textContent = `
      :root {
        --rx-btt-size: ${RX_BACK_TO_TOP_CONFIG.buttonSizeDesktop}px;
        --rx-btt-size-mobile: ${RX_BACK_TO_TOP_CONFIG.buttonSizeMobile}px;
        --rx-btt-right: ${RX_BACK_TO_TOP_CONFIG.rightDesktop}px;
        --rx-btt-bottom: ${RX_BACK_TO_TOP_CONFIG.bottomDesktop}px;
        --rx-btt-right-mobile: ${RX_BACK_TO_TOP_CONFIG.rightMobile}px;
        --rx-btt-bottom-mobile: ${RX_BACK_TO_TOP_CONFIG.bottomMobile}px;
        --rx-btt-z-index: ${RX_BACK_TO_TOP_CONFIG.zIndex};
        --rx-btt-bg: #0f172a;
        --rx-btt-color: #ffffff;
        --rx-btt-bg-hover: #2563eb;
        --rx-btt-shadow: 0 12px 30px rgba(15, 23, 42, 0.22);
        --rx-btt-shadow-hover: 0 16px 40px rgba(37, 99, 235, 0.32);
        --rx-btt-radius: 999px;
        --rx-btt-progress: #22c55e;
        --rx-btt-ring-bg: rgba(255, 255, 255, 0.22);
      }

      .rx-back-to-top-wrap {
        position: fixed;
        right: var(--rx-btt-right);
        bottom: var(--rx-btt-bottom);
        z-index: var(--rx-btt-z-index);
        pointer-events: none;
        opacity: 0;
        transform: translate3d(0, 14px, 0) scale(0.94);
        transition:
          opacity 220ms ease,
          transform 220ms ease,
          bottom 220ms ease;
      }

      .rx-back-to-top-wrap.is-visible {
        opacity: 1;
        transform: translate3d(0, 0, 0) scale(1);
        pointer-events: auto;
      }

      .rx-back-to-top-wrap.is-active {
        transform: translate3d(0, -2px, 0) scale(0.98);
      }

      .rx-back-to-top-button {
        position: relative;
        width: var(--rx-btt-size);
        height: var(--rx-btt-size);
        display: inline-flex;
        align-items: center;
        justify-content: center;
        border: 0;
        border-radius: var(--rx-btt-radius);
        background: var(--rx-btt-bg);
        color: var(--rx-btt-color);
        box-shadow: var(--rx-btt-shadow);
        cursor: pointer;
        overflow: hidden;
        outline: none;
        isolation: isolate;
        -webkit-tap-highlight-color: transparent;
        transition:
          background-color 180ms ease,
          color 180ms ease,
          box-shadow 180ms ease,
          transform 180ms ease;
      }

      .rx-back-to-top-button::before {
        content: "";
        position: absolute;
        inset: 4px;
        border-radius: inherit;
        background:
          radial-gradient(circle at 35% 30%, rgba(255, 255, 255, 0.24), transparent 38%),
          linear-gradient(145deg, rgba(255, 255, 255, 0.10), rgba(255, 255, 255, 0));
        z-index: -1;
        opacity: 0.9;
      }

      .rx-back-to-top-button:hover {
        background: var(--rx-btt-bg-hover);
        box-shadow: var(--rx-btt-shadow-hover);
        transform: translateY(-2px);
      }

      .rx-back-to-top-button:active {
        transform: translateY(0) scale(0.96);
      }

      .rx-back-to-top-button:focus-visible {
        outline: 3px solid rgba(37, 99, 235, 0.35);
        outline-offset: 4px;
      }

      .rx-back-to-top-icon {
        position: relative;
        z-index: 2;
        width: 22px;
        height: 22px;
        display: block;
      }

      .rx-back-to-top-progress {
        position: absolute;
        inset: 0;
        width: 100%;
        height: 100%;
        transform: rotate(-90deg);
        pointer-events: none;
        z-index: 1;
      }

      .rx-back-to-top-progress circle {
        fill: none;
        stroke-width: 2.5;
      }

      .rx-back-to-top-progress .rx-btt-progress-bg {
        stroke: var(--rx-btt-ring-bg);
      }

      .rx-back-to-top-progress .rx-btt-progress-value {
        stroke: var(--rx-btt-progress);
        stroke-linecap: round;
        transition: stroke-dashoffset 120ms linear;
      }

      .rx-back-to-top-text {
        position: absolute !important;
        width: 1px !important;
        height: 1px !important;
        padding: 0 !important;
        margin: -1px !important;
        overflow: hidden !important;
        clip: rect(0, 0, 0, 0) !important;
        white-space: nowrap !important;
        border: 0 !important;
      }

      html.rx-is-scrolling .rx-back-to-top-button {
        will-change: transform;
      }

      @media (max-width: 767px) {
        .rx-back-to-top-wrap {
          right: var(--rx-btt-right-mobile);
          bottom: var(--rx-btt-bottom-mobile);
        }

        .rx-back-to-top-button {
          width: var(--rx-btt-size-mobile);
          height: var(--rx-btt-size-mobile);
        }

        .rx-back-to-top-icon {
          width: 20px;
          height: 20px;
        }
      }

      @media (prefers-reduced-motion: reduce) {
        .rx-back-to-top-wrap,
        .rx-back-to-top-button,
        .rx-back-to-top-progress .rx-btt-progress-value {
          transition: none !important;
          animation: none !important;
          scroll-behavior: auto !important;
        }
      }

      @media print {
        .rx-back-to-top-wrap {
          display: none !important;
        }
      }
    `;

    doc.head.appendChild(style);
  }

  /**
   * ============================================================
   * Button Creation
   * ============================================================
   */

  function createBackToTopButton() {
    let existingButton = qs("#" + RX_BACK_TO_TOP_CONFIG.buttonId);

    if (existingButton) {
      return existingButton;
    }

    const wrapper = doc.createElement("div");
    wrapper.className = RX_BACK_TO_TOP_CONFIG.wrapperClass;
    wrapper.setAttribute("data-rx-back-to-top", "true");

    const button = doc.createElement("button");
    button.type = "button";
    button.id = RX_BACK_TO_TOP_CONFIG.buttonId;
    button.className = "rx-back-to-top-button";
    button.setAttribute("aria-label", RX_BACK_TO_TOP_CONFIG.ariaLabel);
    button.setAttribute("title", RX_BACK_TO_TOP_CONFIG.title);

    if (RX_BACK_TO_TOP_CONFIG.enableProgressRing) {
      const progressSvg = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
      progressSvg.setAttribute("class", "rx-back-to-top-progress");
      progressSvg.setAttribute("id", RX_BACK_TO_TOP_CONFIG.progressId);
      progressSvg.setAttribute("viewBox", "0 0 52 52");
      progressSvg.setAttribute("aria-hidden", "true");
      progressSvg.setAttribute("focusable", "false");

      const bgCircle = doc.createElementNS("http://www.w3.org/2000/svg", "circle");
      bgCircle.setAttribute("class", "rx-btt-progress-bg");
      bgCircle.setAttribute("cx", "26");
      bgCircle.setAttribute("cy", "26");
      bgCircle.setAttribute("r", "23");

      const valueCircle = doc.createElementNS("http://www.w3.org/2000/svg", "circle");
      valueCircle.setAttribute("class", "rx-btt-progress-value");
      valueCircle.setAttribute("cx", "26");
      valueCircle.setAttribute("cy", "26");
      valueCircle.setAttribute("r", "23");

      progressSvg.appendChild(bgCircle);
      progressSvg.appendChild(valueCircle);
      button.appendChild(progressSvg);
    }

    const iconWrap = doc.createElement("span");
    iconWrap.className = "rx-back-to-top-icon-wrap";
    iconWrap.innerHTML = RX_BACK_TO_TOP_CONFIG.iconSvg;

    const text = doc.createElement("span");
    text.className = "rx-back-to-top-text";
    text.textContent = RX_BACK_TO_TOP_CONFIG.ariaLabel;

    button.appendChild(iconWrap);
    button.appendChild(text);
    wrapper.appendChild(button);
    body.appendChild(wrapper);

    return button;
  }

  /**
   * ============================================================
   * Progress Ring Setup
   * ============================================================
   */

  function setupProgressRing() {
    const circle = qs(".rx-btt-progress-value");

    if (!circle) {
      return null;
    }

    const radius = Number(circle.getAttribute("r")) || 23;
    const circumference = 2 * Math.PI * radius;

    circle.style.strokeDasharray = String(circumference);
    circle.style.strokeDashoffset = String(circumference);

    return {
      circle: circle,
      circumference: circumference
    };
  }

  function updateProgressRing(progressData) {
    if (!progressData || !progressData.circle) {
      return;
    }

    const progress = getScrollProgress();
    const offset =
      progressData.circumference -
      (progress / 100) * progressData.circumference;

    progressData.circle.style.strokeDashoffset = String(offset);
  }

  /**
   * ============================================================
   * Smooth Scroll To Top
   * ============================================================
   */

  function easeOutCubic(t) {
    return 1 - Math.pow(1 - t, 3);
  }

  function smoothScrollToTop() {
    const startPosition = getScrollTop();
    const targetPosition = Math.max(0, RX_BACK_TO_TOP_CONFIG.scrollOffset);
    const distance = startPosition - targetPosition;

    if (distance <= 0) {
      return;
    }

    if (prefersReducedMotion()) {
      win.scrollTo(0, targetPosition);
      return;
    }

    const duration = RX_BACK_TO_TOP_CONFIG.scrollDuration;
    const startTime = performance.now();

    function animationStep(currentTime) {
      const elapsed = currentTime - startTime;
      const progress = clamp(elapsed / duration, 0, 1);
      const eased = easeOutCubic(progress);
      const nextPosition = startPosition - distance * eased;

      win.scrollTo(0, nextPosition);

      if (progress < 1) {
        win.requestAnimationFrame(animationStep);
      } else {
        win.scrollTo(0, targetPosition);
      }
    }

    win.requestAnimationFrame(animationStep);
  }

  /**
   * ============================================================
   * Footer Collision Protection
   * ============================================================
   */

  function getFooterElement() {
    const selectors = RX_BACK_TO_TOP_CONFIG.footerSelectors;

    for (let i = 0; i < selectors.length; i++) {
      const footer = qs(selectors[i]);

      if (footer) {
        return footer;
      }
    }

    return null;
  }

  function updateFooterCollision(wrapper, footer) {
    if (!RX_BACK_TO_TOP_CONFIG.enableFooterCollisionProtection) {
      return;
    }

    if (!wrapper || !footer) {
      return;
    }

    const viewportHeight = getViewportHeight();
    const footerRect = footer.getBoundingClientRect();
    const isMobile = isMobileViewport();

    const baseBottom = isMobile
      ? RX_BACK_TO_TOP_CONFIG.bottomMobile
      : RX_BACK_TO_TOP_CONFIG.bottomDesktop;

    const overlap = viewportHeight - footerRect.top;

    if (overlap > 0) {
      const safeBottom = baseBottom + overlap;
      wrapper.style.bottom = safeBottom + "px";
      wrapper.classList.add(RX_BACK_TO_TOP_CONFIG.footerAwareClass);
    } else {
      wrapper.style.bottom = "";
      wrapper.classList.remove(RX_BACK_TO_TOP_CONFIG.footerAwareClass);
    }
  }

  /**
   * ============================================================
   * Visibility Logic
   * ============================================================
   */

  function updateVisibility(wrapper) {
    if (!wrapper) {
      return;
    }

    const scrollTop = getScrollTop();
    const shouldShow = scrollTop >= RX_BACK_TO_TOP_CONFIG.showAfter;

    if (shouldShow) {
      wrapper.classList.add(RX_BACK_TO_TOP_CONFIG.visibleClass);
      wrapper.setAttribute("aria-hidden", "false");
    } else if (scrollTop <= RX_BACK_TO_TOP_CONFIG.hideBelow) {
      wrapper.classList.remove(RX_BACK_TO_TOP_CONFIG.visibleClass);
      wrapper.setAttribute("aria-hidden", "true");
    }
  }

  function updateHtmlScrollClass() {
    if (!RX_BACK_TO_TOP_CONFIG.enableScrollSpyClass) {
      return;
    }

    if (getScrollTop() > 8) {
      html.classList.add("rx-page-scrolled");
    } else {
      html.classList.remove("rx-page-scrolled");
    }
  }

  /**
   * ============================================================
   * Events
   * ============================================================
   */

  function bindButtonEvents(button, wrapper) {
    if (!button || !wrapper) {
      return;
    }

    button.addEventListener(
      "click",
      function (event) {
        event.preventDefault();

        wrapper.classList.add(RX_BACK_TO_TOP_CONFIG.activeClass);
        smoothScrollToTop();

        win.setTimeout(function () {
          wrapper.classList.remove(RX_BACK_TO_TOP_CONFIG.activeClass);
        }, 220);
      },
      { passive: false }
    );

    button.addEventListener("keydown", function (event) {
      if (event.key === "Enter" || event.key === " ") {
        event.preventDefault();
        smoothScrollToTop();
      }
    });
  }

  function bindKeyboardShortcut() {
    if (!RX_BACK_TO_TOP_CONFIG.enableKeyboardShortcut) {
      return;
    }

    doc.addEventListener("keydown", function (event) {
      const activeElement = doc.activeElement;
      const tagName = activeElement ? activeElement.tagName : "";

      const isTyping =
        tagName === "INPUT" ||
        tagName === "TEXTAREA" ||
        tagName === "SELECT" ||
        (activeElement && activeElement.isContentEditable);

      if (isTyping) {
        return;
      }

      const keyMatch = event.key === RX_BACK_TO_TOP_CONFIG.keyboardShortcutKey;
      const altMatch = RX_BACK_TO_TOP_CONFIG.keyboardShortcutAlt
        ? event.altKey
        : true;

      if (keyMatch && altMatch) {
        event.preventDefault();
        smoothScrollToTop();
      }
    });
  }

  function addScrollingState() {
    if (!RX_BACK_TO_TOP_CONFIG.enableScrollSpyClass) {
      return function () {};
    }

    let scrollTimer = null;

    return function () {
      html.classList.add("rx-is-scrolling");

      if (scrollTimer) {
        win.clearTimeout(scrollTimer);
      }

      scrollTimer = win.setTimeout(function () {
        html.classList.remove("rx-is-scrolling");
      }, 180);
    };
  }

  /**
   * ============================================================
   * Public API
   * ============================================================
   */

  function exposePublicApi(button, wrapper) {
    win.RXBackToTop = {
      button: button,
      wrapper: wrapper,

      show: function () {
        wrapper.classList.add(RX_BACK_TO_TOP_CONFIG.visibleClass);
        wrapper.setAttribute("aria-hidden", "false");
      },

      hide: function () {
        wrapper.classList.remove(RX_BACK_TO_TOP_CONFIG.visibleClass);
        wrapper.setAttribute("aria-hidden", "true");
      },

      scroll: function () {
        smoothScrollToTop();
      },

      refresh: function () {
        const event = new Event("scroll");
        win.dispatchEvent(event);
      },

      config: RX_BACK_TO_TOP_CONFIG
    };
  }

  /**
   * ============================================================
   * Init
   * ============================================================
   */

  function initBackToTop() {
    if (shouldIgnorePage()) {
      return;
    }

    if (body.classList.contains("rx-back-to-top-disabled")) {
      return;
    }

    injectBackToTopStyles();

    const button = createBackToTopButton();
    const wrapper = button.closest("." + RX_BACK_TO_TOP_CONFIG.wrapperClass);

    if (!button || !wrapper) {
      return;
    }

    const progressData = setupProgressRing();
    const footer = getFooterElement();
    const setScrollingState = addScrollingState();

    const updateAll = rafThrottle(function () {
      updateVisibility(wrapper);
      updateProgressRing(progressData);
      updateFooterCollision(wrapper, footer);
      updateHtmlScrollClass();
      setScrollingState();
    });

    bindButtonEvents(button, wrapper);
    bindKeyboardShortcut();
    exposePublicApi(button, wrapper);

    wrapper.setAttribute("aria-hidden", "true");

    win.addEventListener("scroll", updateAll, { passive: true });
    win.addEventListener("resize", updateAll, { passive: true });
    win.addEventListener("orientationchange", updateAll, { passive: true });

    updateAll();

    doc.dispatchEvent(
      new CustomEvent("rxBackToTopReady", {
        detail: {
          button: button,
          wrapper: wrapper,
          config: RX_BACK_TO_TOP_CONFIG
        }
      })
    );
  }

  /**
   * ============================================================
   * DOM Ready
   * ============================================================
   */

  if (doc.readyState === "loading") {
    doc.addEventListener("DOMContentLoaded", initBackToTop);
  } else {
    initBackToTop();
  }
})();

To load it in your RX Theme, enqueue it like this in your theme enqueue file, for example:

inc/core/enqueue.php

<?php
/**
 * RX Theme Enqueue Assets
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

function rx_theme_enqueue_back_to_top_chunk() {
	wp_enqueue_script(
		'rx-chunk-006-back-to-top',
		get_template_directory_uri() . '/assets/static-js/static-chunks-js/chunk-006-back-to-top.js',
		array(),
		wp_get_theme()->get( 'Version' ),
		true
	);
}
add_action( 'wp_enqueue_scripts', 'rx_theme_enqueue_back_to_top_chunk' );

Also check your folder slash. In WordPress/PHP always use forward slash:

assets/static-js/static-chunks-js/chunk-006-back-to-top.js

Not this:

assets\static-js\static-chunks-js/chunk-006-back-to-top.js

This file can work alone without writing HTML manually. It will automatically create the Back To Top button on the front end.

Leave a Reply

Your email address will not be published. Required fields are marked *