Skip to content

Instantly share code, notes, and snippets.

@ryangittings
Last active September 24, 2024 14:31
Show Gist options
  • Save ryangittings/c46f0c55853c922c23f4978a7d3156ea to your computer and use it in GitHub Desktop.
Save ryangittings/c46f0c55853c922c23f4978a7d3156ea to your computer and use it in GitHub Desktop.
class Carouscroll extends HTMLElement {
static tagName = "carou-scroll";
static register(tagName, registry) {
if (!registry && ("customElements" in globalThis)) {
registry = globalThis.customElements;
}
registry?.define(tagName || this.tagName, this);
}
static attr = {
orientation: "orientation",
disabled: "disabled",
prev: "data-carousel-previous",
next: "data-carousel-next",
output: "data-carousel-output",
outputCurrent: "data-carousel-output-current",
outputTotal: "data-carousel-output-total",
};
connectedCallback() {
this.content = this;
this.content.setAttribute("tabindex", "0");
this.slides = Array.from(this.content.children);
this.activePage = 0;
this.initializeButtons();
this.initializeOutput();
this.renderMetadata(this.findActivePage());
// Manual scrolling detection
this.content.addEventListener("scroll", this.debounce(() => {
this.updateActivePageOnScroll();
}, 100));
}
// Method to calculate and update the active page on scroll
updateActivePageOnScroll() {
let closestSlideIndex = this.activePage;
let minDistance = Infinity;
let scrollLeft = this.content.scrollLeft;
this.slides.forEach((slide, index) => {
const distance = Math.abs(slide.offsetLeft - scrollLeft);
if (distance < minDistance) {
minDistance = distance;
closestSlideIndex = index;
}
});
if (closestSlideIndex !== this.activePage) {
this.activePage = closestSlideIndex;
this.renderMetadata(this.slides[this.activePage]);
}
}
// The rest of your code remains unchanged
get id() {
return this.getAttribute("id");
}
get align() {
return this.getAttribute("align") ?? "start";
}
debounce(func, wait) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
initializeButtons() {
const { next, prev } = Carouscroll.attr;
this.nextButton = document.querySelector(`[${next}="${this.id}"]`);
this.prevButton = document.querySelector(`[${prev}="${this.id}"]`);
this.prevButton?.addEventListener("click", this.handleButtonClick.bind(this, "previous"));
this.nextButton?.addEventListener("click", this.handleButtonClick.bind(this, "next"));
}
initializeOutput() {
const { output, outputCurrent, outputTotal } = Carouscroll.attr;
this.output = document.querySelector(`[${output}="${this.id}"]`);
if (!this.output) return;
if (this.output.childElementCount === 0) {
this.output.innerHTML = `<span ${outputCurrent}></span> of <span ${outputTotal}></span>`;
}
this.outputCurrent = this.output.querySelector(`[${outputCurrent}]`);
this.outputTotal = this.output.querySelector(`[${outputTotal}]`);
this.output.setAttribute("aria-live", "polite");
this.output.setAttribute("aria-atomic", "true");
}
renderOutput() {
if (this.outputCurrent && this.outputTotal) {
this.outputCurrent.textContent = this.activePage + 1;
this.outputTotal.textContent = this.slides.length;
}
}
findActivePage() {
return this.slides[this.activePage];
}
isLooping() {
return this.hasAttribute("loop");
}
renderMetadata(page) {
this.renderOutput();
this.updateButtonState(page);
}
updateButtonState(page) {
if (!this.nextButton || !this.prevButton) return;
if (this.isLooping()) {
this.prevButton.removeAttribute("disabled");
this.nextButton.removeAttribute("disabled");
return;
}
this.prevButton.toggleAttribute("disabled", !page.previousElementSibling);
this.nextButton.toggleAttribute("disabled", !page.nextElementSibling);
}
handleButtonClick(direction, event) {
event.preventDefault();
const isLoop = this.isLooping();
const isAtEnd = this.activePage === this.slides.length - 1;
const isAtStart = this.activePage === 0;
if (direction === "next") {
this.activePage = isAtEnd && isLoop ? 0 : Math.min(this.activePage + 1, this.slides.length - 1);
} else {
this.activePage = isAtStart && isLoop ? this.slides.length - 1 : Math.max(this.activePage - 1, 0);
}
this.scrollToSlide();
}
scrollToSlide() {
const elem = this.slides[this.activePage];
elem.scrollIntoView({ block: "nearest", inline: this.align });
this.renderMetadata(elem);
}
}
Carouscroll.register();
export { Carouscroll };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment