Skip to content

Data Toggle Expand ​

dataToggleExpand.js ​

js
/**
 * @method collapseElement
 * @param {HTMLElement} element
 */
function collapseElement(element) {
  /**
   * Get the height of the element's inner content,
   * regardless of its actual size
   */
  const elementHeight = element.scrollHeight;

  // Temporarily disable all css transitions
  const elementTransition = element.style.transition;
  element.style.transition = "";

  /**
   * On the next frame (as soon as the previous style change has taken effect),
   * explicitly set the element's height to its current pixel height,
   * so we aren't transitioning out of 'auto'
   */
  requestAnimationFrame(() => {
    element.style.height = `${elementHeight}px`;
    element.style.transition = elementTransition;

    /**
     * On the next frame (as soon as the previous style change has taken effect),
     * have the element transition to height: 0
     */
    requestAnimationFrame(() => {
      element.style.height = 0 + "px";
    });
  });

  // Mark the element as 'currently not expanded'
  element.classList.remove("expanded");
}

/**
 * @method expandElement
 * @param {HTMLElement} element
 */
function expandElement(element) {
  /**
   * Get the height of the element's inner content,
   * regardless of its actual size
   */
  const elementHeight = element.scrollHeight;

  // Have the element transition to the height of its inner content
  element.style.height = `${elementHeight}px`;

  // When the next css transition finishes (which should be the one we just triggered)
  element.addEventListener("transitionend", function (e) {
    // Remove this event listener so it only gets triggered once
    element.removeEventListener("transitionend", arguments.callee);

    /**
     * Remove 'height' from the element's inline styles,
     * so it can return to its initial value
     */
    element.style.height = null;
  });

  // Mark the element as 'currently expanded'
  element.classList.add("expanded");
}

const dataToggles = document.querySelectorAll('[data-toggle="expand"]');

Array.prototype.forEach.call(dataToggles, dataToggle => {
  const targetId = dataToggle.getAttribute("data-target");
  const targetElement = document.querySelector(targetId);

  const isCollapsed = !targetElement.classList.contains("expanded");

  if (isCollapsed) {
    collapseElement(targetElement);
  }

  dataToggle.addEventListener("click", () => {
    const isExpanded = targetElement.classList.contains("expanded");

    if (isExpanded) {
      collapseElement(targetElement);
      // A11Y
      dataToggle.setAttribute("aria-expanded", "false");
    } else {
      expandElement(targetElement);
      // A11Y
      dataToggle.setAttribute("aria-expanded", "true");
    }
  });
});

Released under the MIT License.