유연한 박스 컴포넌트

choiuhana·2022년 11월 11일
0

커스텀 컴포넌트

목록 보기
1/1
post-thumbnail

프론트엔드 개발을 하다보면 정해지지 않은 요소를 정렬하게될때가 있습니다.
아래와같은 박스를 구현한다고 생각해볼께요,

여기엔 동물과 조류, 공룡 그리고 판다가 있내요.(판다는 귀여우니까 판다로 할게요! 🐼)

이 디자인은 480 가로길이에 맞춰 디자인되었기 때문에 430의 가로길이를 가진 스크린에서는 Owl 밖으로 삐져나가기 때문에 아래로 내려가게되고 Velociraptor 또한 아래로 내려가게 됩니다.

아마 대부분 이렇게 생각할것같아요.

"이게 뭐? 어렵나? flex 속성 주고 wrap 걸면되는거 아니야?"
맞습니다, 저도 그렇게 했습니다.

그런데 혹시 디자이너는 박스 패딩을 5를 주고싶고 요소의 간격은 10을 주려고 의도했는데, 박스에 패딩 5를 주고 요소에 우측 마진 10을 주는 형태로 구현하지 않으셨을까요?(아니었다면 방법좀..🙏)

저도 마찬가지로 구현을 하다 문득 든 생각이 패딩과 간격과 요소의 합이 박스사이즈와 동일할때는 어떻게 하지? 라는 생각이 들더군요 아래와 같은 상황 이겠죠?

grid와 같이 gap을 10을 줬을때는 문제가 없겠지만 지금 저희가(아니라면 죄송 😛)구현한 방법인 우측 마진은 Owl의 우측 마진과 박스 패딩이 만나 다음칸으로 밀리게 될거에요, 아래와 같이 말이죠 😭

이런 문제를 해결하기위해 열심히 찾아보던 와중 아주 멋진 소스를 발견하게되어 "오! 멋진데? 나도 만들어야지!" 하고 뚝딱뚝딱 FlexItemGapBox를 만들게 되었습니다.

일정하지 않은 요소가 일정한 사이 간격을 갖으며 박스에 꽉 차게되었을때 아래로 내려가지 않고 박스를 꽉 채우게 할수 있고 위아래 간격, 박스 패딩, 박스 사이즈 및 기본적인 style을 모두 대응할 수 있게 구현을 했고 필요하다면 정렬방법까지? 추가해볼수도 있을것 같내요.

방법은 ✨아주 멋진 트릭을 가진 박스를 만들고 안에 children으로 요소를 넣어주는 것입니다. 이제 트릭을 설명해볼게요!


먼저 overflow hidden 속성을 가진 최상위 View(주황색 선)를 만들어주고 이 안에 사이간격의 1/2만큼의 -마진을 가진 View(파란색 선)를 넣어줍니다.
그리고 사이간격의 1/2만큼의 상하좌우 마진을 가진 view로 children을 감싸줍니다.

그러면 완성!!! 참 쉽죠? 😙

interface FlexItemsGapBoxProps extends ViewProps {
    // 아이템 가로 사이 간격, Default: 0
    row_gap?: number;

    // 아이템 세로 사이 간격, Default: 0
    column_gap?: number;
}

const FlexItemsGapBox = (props: FlexItemsGapBoxProps) => {
    const {style, row_gap, column_gap, children} = props;

    const measured_vertical_gap = row_gap ? row_gap / 2 : 0;
    const measured_horizontal_gap = column_gap ? column_gap / 2 : 0;

    const styles = createStyle(measured_vertical_gap, measured_horizontal_gap);

    const visibleItems = useMemo(() => {
        if (!children) {
            return null;
        } else if (Array.isArray(children)) {
            return children.map((item, index: number) => {
                return (
                    <View key={index} style={styles.item}>
                        {item}
                    </View>
                );
            });
        } else {
            return children;
        }
    }, [children, styles.item]);

    return (
        <View
            {...props}
            style={[
                style,
                {
                    overflow: "hidden",
                },
            ]}>
            <View style={styles.box}>{visibleItems}</View>
        </View>
    );
};

const createStyle = (measured_horizontal_gap: number, measured_vertical_gap: number) => {
    return StyleSheet.create({
        box: {
            flexDirection: "row",
            flexWrap: "wrap",

            marginHorizontal: -measured_horizontal_gap,
            marginVertical: -measured_vertical_gap,
        },
        item: {
            marginHorizontal: measured_horizontal_gap,
            marginVertical: measured_vertical_gap,
        },
    });
};

export default FlexItemsGapBox;

제가 개발했던 '애드캠퍼스'의 투표를 하게되면 이유를 남길 수 있는데 이 항목을 해당 코드를 사용해 개발했었습니다.

프론트엔드라면 흔하게 마주하게되는 상황인데 다른분들은 어떻게 구현하고 계시는지 궁금하기도 하내요! 기회가 된다면 공유해 주셔도 좋고 제 코드에 더 발전시킬 부분을 알려주셔도 너무 좋을것 같아요!

부족한 글 읽어주셔서 감사합니다 🙇‍♂️

profile
만드는 사람도 사용하는 사람도 편하고 만족하는 '것'을 추구하는 프론트엔드 개발자

0개의 댓글