feat: add swipeable sidebar drawer for anime details on mobile

This commit is contained in:
2026-06-25 21:47:21 +02:00
parent e9917730b8
commit adcae02c18
2 changed files with 129 additions and 2 deletions

View File

@@ -90,5 +90,115 @@ const initThemesDialog = (): void => {
});
};
const initAnimeSidebarDrawer = (): void => {
onReady(() => {
const drawer = document.querySelector<HTMLElement>("[data-anime-sidebar-drawer]");
const openButton = document.querySelector<HTMLButtonElement>("[data-anime-sidebar-open]");
const closeButton = document.querySelector<HTMLButtonElement>("[data-anime-sidebar-close]");
const backdrop = document.querySelector<HTMLButtonElement>("[data-anime-sidebar-backdrop]");
if (!drawer || !openButton || !closeButton || !backdrop) {
return;
}
let startX = 0;
let startY = 0;
let activeTouchId: number | null = null;
let isOpen = false;
const syncState = (): void => {
drawer.classList.toggle("hidden", !isOpen);
drawer.classList.toggle("translate-x-0", isOpen);
drawer.classList.toggle("translate-x-full", !isOpen);
backdrop.classList.toggle("hidden", !isOpen);
document.body.classList.toggle("overflow-hidden", isOpen);
openButton.setAttribute("aria-expanded", String(isOpen));
};
const open = (): void => {
isOpen = true;
syncState();
};
const close = (): void => {
isOpen = false;
syncState();
};
openButton.addEventListener("click", open);
closeButton.addEventListener("click", close);
backdrop.addEventListener("click", close);
document.addEventListener("keydown", (event) => {
if (event.key === "Escape" && isOpen) {
close();
}
});
const getTouch = (event: TouchEvent): Touch | null => {
if (activeTouchId === null) {
return event.touches[0] ?? event.changedTouches[0] ?? null;
}
return (
Array.from(event.touches).find((touch) => touch.identifier === activeTouchId) ??
Array.from(event.changedTouches).find((touch) => touch.identifier === activeTouchId) ??
null
);
};
document.addEventListener(
"touchstart",
(event) => {
if (window.innerWidth >= 1024) {
return;
}
const touch = event.touches[0];
if (!touch) {
return;
}
if (!isOpen && touch.clientX < window.innerWidth - 28) {
return;
}
activeTouchId = touch.identifier;
startX = touch.clientX;
startY = touch.clientY;
},
{ passive: true },
);
document.addEventListener(
"touchend",
(event) => {
if (window.innerWidth >= 1024 || activeTouchId === null) {
return;
}
const touch = getTouch(event);
activeTouchId = null;
if (!touch) {
return;
}
const deltaX = touch.clientX - startX;
const deltaY = Math.abs(touch.clientY - startY);
if (deltaY > 60) {
return;
}
if (!isOpen && startX >= window.innerWidth - 28 && deltaX < -48) {
open();
return;
}
if (isOpen && deltaX > 48) {
close();
}
},
{ passive: true },
);
syncState();
});
};
initSynopsisToggle();
initThemesDialog();
initAnimeSidebarDrawer();