[nextJS] 포트폴리오 만들기(4)
1.react-mixitup 사용법
MixItUp 을 써보려 했으나, JQuery에서만 지원하는 것 같아 여러 방법을 찾아보다가
react-mixitup 을찾게되었다. 해당 공식 문서를 찬찬히 보면 알겠지만 내가 알게된것들을 한번 정리해보자!
function Shuffle() {
const NUM_CELLS = 9;
const [keys, setKeys] = React.useState(() => range(NUM_CELLS));
const _shuffle = () => {
setKeys(shuffle(range(NUM_CELLS)));
};
const TRANSITION_DURATION = 250;
return (
<div>
<button onClick={_shuffle}>Shuffle</button>
<ReactMixitup
keys={keys}
dynamicDirection="vertical"
transitionDuration={TRANSITION_DURATION}
renderCell={(key, style, ref) => (
<div
key={key}
ref={ref}
style={{
width: 48,
height: 48,
border: '1px solid black',
margin: 4,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
// transition-duration must be
// same as transitionDuration prop
transition: `transform ${TRANSITION_DURATION}ms ease`,
...style
}}
>
{key}
</div>
)}
renderWrapper={(style, ref, children) => {
const squareWidth = (48 + 4 * 2);
const wrapperWidth = squareWidth *
Math.ceil(Math.sqrt(NUM_CELLS));
return (
<div
style={{
display: 'flex',
flexWrap: 'wrap',
boxSizing: 'content-box',
width: wrapperWidth,
height: wrapperWidth,
padding: '12px 0',
...style
}}
ref={ref}
>
{children}
</div>
);
}}
/>
</div>
);
}
보통 이렇게 쓰면 된다.
ReactMixitup 의 props부터 하나씩 확인해보자
1.1 keys
List of unique numbers or strings identifying and representing each cell. 역) 각 셀을 식별하고 나타내는 고유한 숫자 또는 문자열 목록입니다.
keys: (string | number)[];
라는데, 좀있다 설명할 renderCell의 key값이 되는것들의 집합이다. object면, key값이 string일테고, array면 key값이 number일것이다.
그래서 보통 이 keys 값을 useState로 정의해놓고 (ex. const [keys, setKeys] = useState(초기값))
setKeys를 통해 리렌더링해서 넣는다.
1.2 renderCell
Is called to render each cell. 역) 각 셀을 렌더링하기 위해 호출됩니다.
type RenderCell = (
key: string | number,
style: ICellStyle,
ref: React.Ref<any>,
frame: IFrame,
activeFrame: boolean
) => React.ReactNode | JSX.Element;
얘는 이제 각 셀이 된다. 함수의 형태이며, 해당 콜백함수의 return값이 셀의 JSX가 된다.
- key - 키(prop)의 대응 키
- style - 각 셀에 적용할 CSS 변형을 나타내는 ICellStyle여야 함.
- ref - 각 셀의 위치(오프셋 왼쪽, 오프셋 위)를 읽기 위해 외부 요소에 전달되어야 함.
- frame - IFrame
- activeFrame - 다음에 렌더링 될 프레임인 경우 Terminology을 참조하세요.
인데 key밖에 안써봐서 잘 모르겠다...
1.3 renderWrapper
Is called to render the wrapper [optional]. Apply styling for how the cells should be positioned, e.g. flex or css grid 역) wrapper를 렌더링하기 위해 호출됩니다(선택 사항). 셀 위치 지정 방법에 대한 스타일을 적용합니다. 예: flex 또는 CSS grid
type RenderWrapper = (
style: IWrapperStyle,
ref: React.Ref<any>,
cells: JSX.Element[],
frame: IFrame,
activeFrame: boolean
) => React.ReactNode | JSX.Element;
flex혹은 grid를 쓰려면 부모에서 설정을해줘야 하는데 그역할을 해준다. 얘도 콜백함수의 return값이 부모의 JSX가 된다.
grid를 써보려고 했으나, grid를 쓰면 내가 잘못쓴건지 애니메이션이 이상하게 적용되었다. 따라서 이글에서는 flex를 이용할 것
- style - IWrapperStyle는 높이를 업데이트하기 위해 요소에 전달되어야 함
- ref - 외부 요소에 전달되어야 함. 래퍼의 높이와 너비(오프셋 너비, 오프셋 높이)를 읽는 데 사용됩니다.
- cells - 렌더링될 JSX 요소의 목록
- frame - IFrame
- activeFrame - 다음에 렌더링 될 프레임인 경우 Terminology를 참조하세요.
라는데 얘도 잘 모르겠다...
1.4 dynamicDirection
셀을 추가하거나 제거할 때 wrapper가 세로로 또는 가로로 확장/축소되어야 하는 경우입니다.
셀을 추가하거나 제거하지 않는 경우 dynamicDirection을 'off'로 설정할 수 있습니다. 이렇게 하면 COMMIT 단계가 건너뛰어져 약간의 성능 이점이 있습니다. COMMIT 단계에서는 wrapper가 측정되고 전환 효과가 적용됩니다.
dynamicDirection: 'horizontal' | 'vertical' | 'off';
보통 필터링이라 하면 필터를 변경될때마다 셀이 추가/제거가 될텐데, 그때 셀을 확대/축소 시켜주는것 같다.
켜놓는게 에니메이션상으로 더 이쁘다.
1.5 transitionDuration
Should match the longest css transition-duration used. If 0 transitions are disabled. 역)사용된 가장 긴 CSS 전환 기간과 일치해야 합니다. 0개이면 전환이 비활성화됩니다.
transitionDuration: number;
2. 사용
pages/portfolio/index.tsx
import Image from "next/image";
import Container from "../../components/Container";
import portfolios, { filters } from "../../data/portfolios";
import { useState } from "react";
const getFiltered = () => {
const ret: { [key: string]: any[] } = {};
let indexes = portfolios.map((_, idx) => idx);
filters.forEach((filter) => {
const filteredIndexes = indexes.filter((idx) =>
portfolios[idx].filter.includes(filter)
);
ret[filter] = filteredIndexes;
});
return ret;
};
const Portfolio = () => {
const [selectedFilter, setSelectedFilter] = useState("all");
const [filtered, setFiltered] = useState(getFiltered());
const [keys, setKeys] = useState(portfolios.map((_, idx) => idx));
const handleFilterClick = (selectedType: string) => {
setSelectedFilter(selectedType);
setKeys(filtered[selectedType]);
};
return (
...
);
};
export default Portfolio;
getFiltered 함수는 각 필터에 해당하는 portfolios의 index들의 배열을 value로 갖는 object를 return 한다.
그럴 object를 useState를 통해 마운트될때 처음 계산하고, 그뒤로는 수정하지 않는다.
텍스트
<Container>
<div className="mt-10 flex flex-col shadow-[0_1px_1px_0px_rgba(0,0,0,0.08)]">
<ul className="flex justify-around">
{filters.map((filter) => (
<li
className={
"h-14 flex items-center px-4 uppercase font-bold " +
(selectedFilter === filter
? "text-purple-900 shadow-[0_2px_0px_0px_rgba(65,48,124,1)]"
: "")
}
key={filter}
onClick={() => handleFilterClick(filter)}
>
{filter}
</li>
))}
</ul>
</div>
<section className="cd-gallery mt-10 pt-10">
<ReactMixitup
keys={keys}
dynamicDirection="vertical"
transitionDuration={TRANSITION_DURATION}
renderCell={(key, style, ref) => (
<div
key={key}
ref={ref}
className={
"w-full sm:w-1/2 lg:w-1/3 my-4 flex items-center justify-center"
}
style={{
transition: `transform ${TRANSITION_DURATION}ms ease-out`,
...style,
}}
>
<div className="shadow-[0_1px_2px_0px_rgba(0,0,0,0.1)]">
<Image
src={portfolios[key].imgsrc}
alt={`Image_${key}`}
width={300}
height={390}
/>
</div>
</div>
)}
renderWrapper={(style, ref, children, stage, frame) => {
return (
<div
className="flex flex-wrap justify-start box-content max-w-screen-lg"
style={{
transition: `height ${TRANSITION_DURATION}ms ease-out`,
...style,
}}
ref={ref}
>
{children}
</div>
);
}}
/>
</section>
</Container>
wrapper랑 shell의 JSX를 넣었다.
다음번에는 md파일을 넣는 blog도 만들어보자.