dm: 사례 콘텐츠
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import type { ContentsDetail } from "@/src/entities/content/model/contents-interface";
|
||||
|
||||
type ContentsPageClientProps = {
|
||||
@@ -7,9 +9,170 @@ type ContentsPageClientProps = {
|
||||
};
|
||||
|
||||
function ContentsPageClient({ initialContent }: ContentsPageClientProps) {
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const root = rootRef.current;
|
||||
if (!root) return;
|
||||
|
||||
const cleanups: Array<() => void> = [];
|
||||
const sliders = Array.from(root.querySelectorAll(".image-slider"));
|
||||
|
||||
sliders.forEach((sliderElement) => {
|
||||
const slider = sliderElement as HTMLElement;
|
||||
const viewport = slider.querySelector(
|
||||
".image-slider-viewport",
|
||||
) as HTMLElement | null;
|
||||
const track = slider.querySelector(
|
||||
".image-slider-track",
|
||||
) as HTMLElement | null;
|
||||
const dotsWrap = slider.querySelector(
|
||||
".image-slider-dots",
|
||||
) as HTMLElement | null;
|
||||
const pages = Array.from(
|
||||
slider.querySelectorAll(".image-slider-page"),
|
||||
) as HTMLElement[];
|
||||
|
||||
if (!viewport || !track || !dotsWrap || pages.length === 0) return;
|
||||
|
||||
let page = 0;
|
||||
let dragging = false;
|
||||
let isHover = false;
|
||||
let startX = 0;
|
||||
let deltaX = 0;
|
||||
let timer: number | null = null;
|
||||
|
||||
const autoPlayMs = Number(slider.dataset.autoplay ?? "4500");
|
||||
const canSlide = pages.length > 1;
|
||||
|
||||
if (!canSlide) {
|
||||
dotsWrap.style.display = "none";
|
||||
} else {
|
||||
dotsWrap.style.display = "";
|
||||
}
|
||||
|
||||
const render = () => {
|
||||
track.style.transform = `translateX(-${page * 100}%)`;
|
||||
|
||||
Array.from(dotsWrap.querySelectorAll("button")).forEach((dot, index) => {
|
||||
dot.classList.toggle("is-active", index === page);
|
||||
});
|
||||
};
|
||||
|
||||
const goTo = (nextPage: number) => {
|
||||
if (!canSlide) return;
|
||||
|
||||
if (nextPage < 0) page = pages.length - 1;
|
||||
else if (nextPage >= pages.length) page = 0;
|
||||
else page = nextPage;
|
||||
|
||||
render();
|
||||
};
|
||||
|
||||
const stopTimer = () => {
|
||||
if (timer !== null) {
|
||||
window.clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
};
|
||||
|
||||
const startTimer = () => {
|
||||
stopTimer();
|
||||
if (!canSlide || dragging || isHover) return;
|
||||
|
||||
timer = window.setInterval(() => {
|
||||
page = page + 1 >= pages.length ? 0 : page + 1;
|
||||
render();
|
||||
}, autoPlayMs);
|
||||
};
|
||||
|
||||
const dotButtons = pages.map((_, index) => {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.className = "image-slider-dot";
|
||||
|
||||
const onClick = () => {
|
||||
goTo(index);
|
||||
startTimer();
|
||||
};
|
||||
|
||||
button.addEventListener("click", onClick);
|
||||
dotsWrap.appendChild(button);
|
||||
|
||||
cleanups.push(() => {
|
||||
button.removeEventListener("click", onClick);
|
||||
});
|
||||
|
||||
return button;
|
||||
});
|
||||
|
||||
const onMouseEnter = () => {
|
||||
isHover = true;
|
||||
stopTimer();
|
||||
};
|
||||
|
||||
const onMouseLeave = () => {
|
||||
isHover = false;
|
||||
startTimer();
|
||||
};
|
||||
|
||||
const onPointerDown = (event: PointerEvent) => {
|
||||
if (!canSlide) return;
|
||||
|
||||
dragging = true;
|
||||
startX = event.clientX;
|
||||
deltaX = 0;
|
||||
viewport.setPointerCapture(event.pointerId);
|
||||
};
|
||||
|
||||
const onPointerMove = (event: PointerEvent) => {
|
||||
if (!dragging) return;
|
||||
deltaX = event.clientX - startX;
|
||||
};
|
||||
|
||||
const onPointerUp = () => {
|
||||
if (!dragging) return;
|
||||
|
||||
dragging = false;
|
||||
|
||||
if (deltaX > 50) goTo(page - 1);
|
||||
if (deltaX < -50) goTo(page + 1);
|
||||
|
||||
deltaX = 0;
|
||||
startTimer();
|
||||
};
|
||||
|
||||
viewport.addEventListener("mouseenter", onMouseEnter);
|
||||
viewport.addEventListener("mouseleave", onMouseLeave);
|
||||
viewport.addEventListener("pointerdown", onPointerDown);
|
||||
viewport.addEventListener("pointermove", onPointerMove);
|
||||
viewport.addEventListener("pointerup", onPointerUp);
|
||||
viewport.addEventListener("pointercancel", onPointerUp);
|
||||
|
||||
cleanups.push(() => {
|
||||
stopTimer();
|
||||
viewport.removeEventListener("mouseenter", onMouseEnter);
|
||||
viewport.removeEventListener("mouseleave", onMouseLeave);
|
||||
viewport.removeEventListener("pointerdown", onPointerDown);
|
||||
viewport.removeEventListener("pointermove", onPointerMove);
|
||||
viewport.removeEventListener("pointerup", onPointerUp);
|
||||
viewport.removeEventListener("pointercancel", onPointerUp);
|
||||
dotButtons.forEach((button) => button.remove());
|
||||
});
|
||||
|
||||
render();
|
||||
startTimer();
|
||||
});
|
||||
|
||||
return () => {
|
||||
cleanups.forEach((cleanup) => cleanup());
|
||||
};
|
||||
}, [initialContent?.contsCn]);
|
||||
|
||||
return (
|
||||
<div className="card card-border !mt-6">
|
||||
<div
|
||||
ref={rootRef}
|
||||
className="contents-page text-[16px] leading-[1.5]"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: initialContent?.contsCn ?? "",
|
||||
|
||||
Reference in New Issue
Block a user