Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/carousel #18

Merged
merged 6 commits into from
Apr 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
// IntelliSense를 사용하여 가능한 특성에 대해 알아보세요.
// 기존 특성에 대한 설명을 보려면 가리킵니다.
// 자세한 내용을 보려면 https://go.microsoft.com/fwlink/?linkid=830387을(를) 방문하세요.
"version": "0.2.0",
"configurations": [
{
"type": "pwa-chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}"
}
]
}
21 changes: 21 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@jjunyjjuny/react-carousel": "0.0.2",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-icons": "^4.2.0",
"react-scripts": "4.0.3",
"styled-components": "^5.2.3",
"web-vitals": "^1.0.1"
Expand Down
197 changes: 114 additions & 83 deletions src/js/util/Carousel.js
Original file line number Diff line number Diff line change
@@ -1,163 +1,194 @@
import { useRef, useState } from "react";
import styled, { css, keyframes } from "styled-components";

// 1. 진행 방향에 요소가 있는가?
// - 있다면 리렌더 없이 slideContainer
// 2. 없다면, VList가 비어있는가?
// - 비어있다면, 반대변에서 가져와서 새로 리렌더링 후에, slideContainer
// - VList에 요소가 남았다면 pop or shift해서 방향에 맞게 추가하고 리렌더링 후에 slideContanier

const Carousel = ({ children: items, itemsPerPeice, onClickItem }) => {
const virtureSlides = useRef(createVirtureSlides(items, itemsPerPeice));
const initialSlide = virtureSlides.current.shift();
const [realSlides, setRealSlieds] = useState([initialSlide]);
import { useEffect, useRef, useState } from "react";
import styled, { css } from "styled-components";
import { HiOutlineChevronLeft, HiOutlineChevronRight } from "react-icons/hi";

const Carousel = ({
children: items,
itemsPerPeice,
onClickItem,
gap = "0.5rem",
autoFit = false,
}) => {
const [initialSlide, virture] = createVirtureSlides(items, itemsPerPeice);
const virtureSlides = useRef(virture);
const container = useRef();
const currentSlideIndex = useRef(0);
const direction = useRef("none");
const [realSlides, setRealSlieds] = useState([initialSlide]);
const didMountRef = useRef(false);
useEffect(() => {
if (didMountRef.current) {
slideContainer(direction.current);
}
});

const move = (dir) => {
didMountRef.current = true;
direction.current = dir;
if (isThereItem(dir)) {
slideContainer(dir);
changeCurrentIndex(dir);
slideContainer();
return;
}
const newSlides = isEmptyVirtureSlides()
? getMovedSlides(dir)
: getAddedSlides();

setRealSlieds((newSlides) => newSlides);
: getAddedSlides(dir);
setRealSlieds(newSlides);
};

const getMovedSlides = (dir) => {
const newSlides = [...realSlides];
if (dir === "prev") {
currentSlideIndex.current -= 1;
direction.current = "prev";
// setRealSlieds([newSlide, ...realSlides]);
const opposite = newSlides.pop();
newSlides.unshift(opposite);
container.current.style.transition = "none";
container.current.style.transform = "translate(calc(-100%))";
} else {
currentSlideIndex.current += 1;
direction.current = "next";
// setRealSlieds([...realSlides, newSlide]);
const i = currentSlideIndex.current;
const opposite = newSlides.shift();
newSlides.push(opposite);
container.current.style.transition = "none";
container.current.style.transform = `translate(-${i * 100 - 100}%)`;
}

return newSlides;
};
const getMovedSlides = (dir) => {
let newSlides = [...realSlides];
const getAddedSlides = (dir) => {
let newSlide;
let newSlides;
if (dir === "prev") {
const opposite = newSlides.current.pop();
newSlides.unshift(opposite);
newSlide = virtureSlides.current.pop();
newSlides = [newSlide, ...realSlides];
container.current.style.transition = "none";
container.current.style.transform = "translate(-100%)";
} else {
const opposite = newSlides.current.shift();
newSlides.push(opposite);
currentSlideIndex.current++;
newSlide = virtureSlides.current.shift();
newSlides = [...realSlides, newSlide];
}

return newSlides;
};
const getAddedSlides = () => {};
const changeCurrentIndex = (dir) => {
currentSlideIndex.current =
dir === "prev"
? --currentSlideIndex.current
: ++currentSlideIndex.current;
};
const slideContainer = (dir) => {
container.current.style.transform =
dir === "prev" ? "translate(100%)" : "translate(-100%)";
const i = currentSlideIndex.current;
container.current.style.transition = `all 1s ease`;
// container.current.style.transform = `translateX(calc(-${i * 100}% ${
// dir === "next" ? `- ${gap}` : ""
// }))`;
container.current.style.transform = `translateX(-${i * 100}%)`;
};

const renderList = () => {
const list = realSlides;
return list.map((slide) => {
return list.map((slide, index) => {
return (
<Slide>
{slide.map((item) => (
<SlideItem>{item}</SlideItem>
<Slide key={index} gap={gap}>
{slide.map((item, index) => (
<SlideItem
gap={gap}
autoFit={autoFit}
key={index}
onClick={onClickItem}
itemsPerPeice={itemsPerPeice}
>
{item}
</SlideItem>
))}
</Slide>
);
});
};
const isThereItem = (direction) => {
if (direction === "prev") {
return currentSlideIndex !== 0;
return currentSlideIndex.current !== 0;
} else {
return currentSlideIndex !== realSlides.length - 1;
return currentSlideIndex.current !== realSlides.length - 1;
}
};
const isEmptyVirtureSlides = () => {
return virtureSlides.current.length === 0;
};
return (
<CarouselWrapper>
<Button prev onClick={() => move("prev")} />
<CarouselContainer ref={container} direction={direction.current}>
{renderList()}
</CarouselContainer>
<Button next onClick={() => move("next")} />
<Button prev onClick={() => move("prev")}>
<HiOutlineChevronLeft />
</Button>
<CarouselContent>
<CarouselContainer gap={gap} ref={container} dir={direction.current}>
{renderList()}
</CarouselContainer>
</CarouselContent>
<Button next onClick={() => move("next")}>
<HiOutlineChevronRight />
</Button>
</CarouselWrapper>
);
};

const createVirtureSlides = (items, itemsPerPeice) => {
return items.reduce((result, item, index) => {
const newItems = items.reduce((result, item, index) => {
const [i, j] = divmod(index, itemsPerPeice);
result[i] ? (result[i][j] = item) : (result[i] = [item]);
return result;
}, []);
return [newItems.shift(), newItems];
};

const divmod = (a, b) => {
return [parseInt(a / b), a % b];
};
const moveToPrev = keyframes`
from{
transform: translateX(0px);
}to{
transform: translateX(100%);
};
`;

const moveToNext = keyframes`
from{
transform: translateX(100%);
}to{
transform: translateX(0);
};
`;
const CarouselWrapper = styled.div`
background-color: #b5b5b5;
/* overflow: hidden; */
position: relative;
background: #b5b5b5;
display: flex;
`;
const CarouselContent = styled.div`
width: 90%;
overflow: hidden;
`;
const CarouselContainer = styled.div`
transition: all 1s ease;
display: flex;
${({ direction }) => {
if (direction === "none") return;
return direction === "prev"
? css`
animation: ${moveToPrev} 1s;
`
: css`
animation: ${moveToNext} 1s;
`;
}}
display: inline-flex;
width: 100%;
padding-right: ${({ gap }) => `${gap}`};
`;
const Slide = styled.ul`
background-color: #777777;
background: #777777;
display: flex;
width: 100%;
flex: 1 0 auto;
padding: 0;
padding-right: ${({ gap }) => `${gap}`};
&::first-child {
}
`;
const SlideItem = styled.li`
list-style-type: none;
width: 100%;
${({ autoFit, itemsPerPeice, gap }) => css`
width: ${autoFit ? `calc(100%/${itemsPerPeice})` : "auto"};
& + & {
margin-left: ${gap};
}
`}
`;
const Button = styled.div`
position: absolute;
width: 50px;
height: 50px;
background: orange;
top: 0;
flex: 1;
z-index: 2;

${(props) => css`
${props.prev &&
css`
left: 10px;
left: 10%;
`}
${props.next &&
css`
right: 10px;
right: 10%;
`}
`}
`};
`;
export default Carousel;
Loading