Skip to content

Instantly share code, notes, and snippets.

@tak-dcxi
Created September 25, 2024 19:20
Show Gist options
  • Select an option

  • Save tak-dcxi/dddf0347d97c2d8787b83c8f4f95f9aa to your computer and use it in GitHub Desktop.

Select an option

Save tak-dcxi/dddf0347d97c2d8787b83c8f4f95f9aa to your computer and use it in GitHub Desktop.
initializeCarousel
// @sample https://codepen.io/tak-dcxi/pen/dyBbBgd
export type CarouselSelectors = {
scrollerSelector: string | undefined;
dirButtonSelector: string | undefined;
progressSelector: string | undefined;
};
const DEFAULT_MOVE = 1;
const DEFAULT_GAP = 0;
const initializeCarousel = (
element: HTMLElement,
selectors: CarouselSelectors
): void => {
const scroller = element.querySelector<HTMLElement>(
`${selectors.scrollerSelector}`
);
if (!scroller) return;
const items = [...scroller.children] as HTMLElement[];
if (items.length === 0) return;
const dirButtons = element.querySelectorAll<HTMLButtonElement>(
`${selectors.dirButtonSelector}`
);
if (dirButtons.length === 0) return;
const progressBar = element.querySelector<HTMLProgressElement>(
`${selectors.progressSelector}`
);
if (!progressBar) return;
const controller = new AbortController();
const { signal } = controller;
const debouncedFunctions = debounce(() => {
detectScrollEdge(scroller);
updateDirButtonState(scroller, dirButtons);
updateInertAttribute(scroller, items);
updateProgressBar(scroller, progressBar);
});
scroller.addEventListener("scroll", debouncedFunctions, { signal });
window.addEventListener("resize", debouncedFunctions, { signal });
dirButtons.forEach((button) =>
button.addEventListener(
"click",
() => {
scroller.scrollLeft = calcScrollLeft(element, scroller, items, button);
},
{ signal }
)
);
setAttributes(element, items);
debouncedFunctions();
};
const setAttributes = (element: HTMLElement, items: HTMLElement[]): void => {
element.setAttribute("aria-roledescription", "カルーセル");
const totalItems = items.length;
items.forEach((item, index) => {
item.setAttribute("role", "group");
item.setAttribute("aria-roledescription", "スライド");
item.setAttribute("aria-label", `${index + 1} of ${totalItems}`);
});
};
const detectScrollEdge = (scroller: HTMLElement): void => {
const scrollLeft = scroller.scrollLeft;
const scrollRight =
scroller.scrollWidth - (scrollLeft + scroller.clientWidth);
const edge = scrollLeft <= 0 ? "start" : scrollRight <= 1 ? "end" : "";
if (scroller.getAttribute("data-edge") !== edge) {
scroller.setAttribute("data-edge", edge);
}
};
const calcScrollLeft = (
element: HTMLElement,
scroller: HTMLElement,
items: HTMLElement[],
button: HTMLElement
): number => {
const dir = button.getAttribute("data-dir") === "prev" ? -1 : 1;
const styles = getComputedStyle(element);
const move = parseInt(styles.getPropertyValue("--_move"), 10) || DEFAULT_MOVE;
const gap = parseInt(styles.getPropertyValue("--_gap"), 10) || DEFAULT_GAP;
const itemWidth = items[0].getBoundingClientRect().width;
const totalItemsSize = itemWidth * move;
const totalGap = gap * move;
return scroller.scrollLeft + dir * (totalItemsSize + totalGap);
};
const updateDirButtonState = (
scroller: HTMLElement,
dirButtons: NodeListOf<HTMLButtonElement>
): void => {
const edge = scroller.getAttribute("data-edge");
dirButtons.forEach((button) => {
const dir = button.getAttribute("data-dir");
const isDisabled =
(edge === "start" && dir === "prev") ||
(edge === "end" && dir === "next");
button.setAttribute("aria-disabled", String(isDisabled));
});
};
const updateInertAttribute = (
scroller: HTMLElement,
items: HTMLElement[]
): void => {
const scrollerRect = scroller.getBoundingClientRect();
items.forEach((item) => {
const itemRect = item.getBoundingClientRect();
const isVisible =
itemRect.left < scrollerRect.right && itemRect.right > scrollerRect.left;
item.inert = !isVisible;
});
};
const updateProgressBar = (
scroller: HTMLElement,
progressBar: HTMLProgressElement
): void => {
const scrollPercentage =
(scroller.scrollLeft / (scroller.scrollWidth - scroller.clientWidth)) * 100;
progressBar.value = scrollPercentage;
};
const debounce = <T extends any[], R>(
callback: (...args: T) => R
): ((...args: T) => void) => {
let timeout: number | undefined;
return (...args: T): void => {
if (timeout !== undefined) cancelAnimationFrame(timeout);
timeout = requestAnimationFrame(() => callback.apply(this, args));
};
};
// export default initializeCarousel
document.addEventListener("DOMContentLoaded", () => {
const carouselElement = document.querySelector<HTMLElement>(".carousel");
if (carouselElement) {
initializeCarousel(carouselElement, {
scrollerSelector: ".scroller",
dirButtonSelector: ".dir-button",
progressSelector: ".progress-bar"
});
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment