엄청나게 고생스러웠다는 것부터 말해두겠다..............
과거로 돌아간다면 그냥 바텀 시트를 직접 만들어서 쓸 것이다.
일단 자료가 너무 없어. 특히 리액트 웹이라면 그냥 만들어서 써라. react-spring-bottom-sheet 이 자식을 커스텀할 생각은 버리시길!
그래도 커스텀을 선택한 당신을 위해..ㅠ 삽질 기록을 남겨본다.
https://github.com/stipsan/react-spring-bottom-sheet/tree/main#expandoncontentdrag
import { useState } from 'react'
import { BottomSheet } from 'react-spring-bottom-sheet'
// if setting up the CSS is tricky, you can add this to your page somewhere:
// <link rel="stylesheet" href="https://unpkg.com/react-spring-bottom-sheet/dist/style.css" crossorigin="anonymous">
import 'react-spring-bottom-sheet/dist/style.css'
export default function Example() {
const [open, setOpen] = useState(false)
return (
<>
<button onClick={() => setOpen(true)}>Open</button>
<BottomSheet open={open}>My awesome content here</BottomSheet>
</>
)
}
이렇게 import해서 쓰는 바텀시트 라이브러리다.
기본적으로 이렇게 생김
하지만 우리 동아리 팀에는 디자이너가 계시지.
내 마음대로 디자인을 결정할 수 없지..........
시작할 땐 몰랐다. 여기에 이렇게 품이 많이 들 줄은. (알았다면 진작에 디자인 수정 요청했음)
하나는 바텀시트에서 드래그해서 내 피드를 볼 수 있어야 했고
하나는 댓글창을 클릭했을 때 바텀시트가 올라오고, 해당 바텀시트 사이즈를 조절할 수도 있어야 했다.
나는 헤더도 커스텀하고 내용도 커스텀하고 기능도 커스텀하고 암튼 다 뜯어고쳐야햇다는 이야기
본격적으로 시작해보자...
import 'react-spring-bottom-sheet/dist/style.css'
이놈은 자체 css를 끌어다가 쓴다.
근데 난 리액트 시작 2주차라서 postcss.config.js가 뭔지 몰랐음. 찾아봐도 없길래 그냥 시키는대로 style.css를 카피해서 customBottomSheet.css를 만들어서 썼다.
import "../css/customBottomSheet.css";
원래는 자체 CSS를 그냥 수정해서 써도 되지 않나 싶었다.
그런데 좀 찾아보니까 라이브러리에 내장되어 있는 css를 건드리는 건 굉장히 위험한 짓이라고 한다.
그리고 나는 바텀 시트가 서로 다른 디자인으로 2개를 쓸 예정이었으므로 커스텀 css를 2개 만들어서 각각 적용하면 될거라고 생각했다. (여기서 추후 엄청나게 고생을 하게 된다. 결과적으로 맞는 선택이긴 했지만..)
보면 알겠지만 나는 밑에 footer가 있었다.
기본적으로 바텀시트는 이름값을 하느라 바텀에서 올라오지만 나는 footer 위에서 올라와야 했으므로 default 시작 위치를 높여줘야 했다.
//customBottomSheet.css
[data-rsbs-root]:after {
z-index: 3;
-ms-scroll-chaining: none;
overscroll-behavior: none;
touch-action: none;
position: fixed;
right: 0;
/* 이 부분 수정해서 바텀 높이 올림 */
bottom: 88px;
left: 0;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
}
bottom: 값을 수정하면 된다.
원래는 자동으로 배경 터치가 불가능하게 설정되어 있다.
js 파일로 가서 blocking을 false로 해주자.
<BottomSheet
blocking = {false} //배경 블록 현상 해결
>
</BottomSheet>
나는 최소로 내렸을 때도 헤더가 보여야 했고, (아예 사라지면 안 됐음)
최대로 올렸을 땐 절반만 올라와야 했다.
<BottomSheet
// Allow the user to select between minimun height to avoid a scrollbar, and fullscren
snapPoints={({ minHeight, maxHeight }) => [minHeight, maxHeight]}
/>
배열에 값을 넣으면 그 값이 체크 포인트가 되어서 높이를 조정할 때 사용자가 그 부분마다 멈출 수 있다.
쌩 숫자를 넣으면 안 되고 변수를 이용해야 하는 듯...? 아마도....?
minHeight와 maxHeight는 기본적으로 제공하는 변수 같다... maybe...
maxHeight = fullScreen 이라고 생각하면 됨.
나는 항상 조금씩은 보이게 하고 싶었기 때문에 최소값을 0이 아닌, maxHeight / 15로 두었다.
snapPoints={({ maxHeight }) => [
maxHeight / 15, //최소
maxHeight /2, //최대
]}
--rsbs-overlay-h
이 부분이 드러나는 높이인데 내가 드래그하면 change 되는 값이라 함부로 바꿀 수가 없었음
css 에 가면 height: var(--rsbs-overlay-h,0px);
이 부분이 있긴 한데 var 대신 내 고정값을 박으면 높이 조절이 안되는 에러가 생김
ㅋㅋ..어차피 브라우저에서 제공하는 변수라 또 코드 뜯어봐서 defaults.json까지 갔는데도 수정이 안되더라
그리고 답을 찾았는데, 이 변수는 snapPoint
의 maxHeight / 15, //최소
이 값이었음!
근데 나는 80px라는 고정값을 쓰고 싶었다 ㅠㅠ 안 그러면 휴대폰 브라우저 크기에 따라서 % 값으로 조절 되기 때문에 헤더 안에 있는 프로필 이미지 등이 튀어나옴........
하지만 int를 쓰면 에러가 나고 꼭 maxHeight 변수를 써야햇기 때문에
snapPoints={({ maxHeight }) => [
maxHeight*0+80, //최소
maxHeight /2, //최대
]}
ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
내가 봐도 웃기네 ㅠㅋㅋㅋㅋ
좀 창피하지만 maxHeight*0+80
이런 계산식으로...ㅎ 강제 80px를 만들어줬다.
변수의 의미가 없는 변수값...........ㅎ 그래도 성공햇으니 됏다..
나는 투명하게 하고 싶었기 때문에 해당 값을 주석 처리해 주었다.
[data-rsbs-backdrop] {
top: -60px;
bottom: -60px;
/* background-color: rgba(255, 255, 255, 0.6); */
/* background-color: var(--rsbs-backdrop-bg,rgba(0, 0, 0, 0.6)); */
will-change: opacity;
cursor: pointer;
opacity: 1;
}
저기 저... 조그만 네모 박스를 없애야 했다.
CSS의 [data-rsbs-header]를 수정해주자
/* [data-rsbs-header]:before {
position: absolute;
content: '';
display: block;
width: 36px;
height: 4px;
top: calc(8px + env(safe-area-inset-top));
left: 50%;
transform: translateX(-50%);
border-radius: 20px;
background-color: hsla(0, 0%, 0%, 0.14);
background-color: var(--rsbs-handle-bg,hsla(0, 0%, 0%, 0.14));
} */
CSS의 [data-rsbs-header]를 수정해주자
[data-rsbs-header] {
text-align: center;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
box-shadow: 0 1px 0
rgba(46, 59, 66, calc(1 * 0.125));
box-shadow: 0 1px 0
rgba(46, 59, 66, calc(var(--rsbs-content-opacity,1) * 0.125));
z-index: 1;
padding-top: calc(20px + env(safe-area-inset-top));
padding-bottom: 8px;
/* 커스텀할 내용 추가 */
height: 55px;
border-radius: 20px 20px 0 0;
border-left: 1px solid black;
border-right: 1px solid black;
border-top: 5px solid black;
}
박스 외관 커스텀이다! 내부에 뭘 채워넣는 건 따로 해야한다. 여기서 하는 거 아님.
css에서 다음 부분을 수정하자.
[data-rsbs-scroll] {
flex-shrink: 1;
flex-grow: 1;
-webkit-tap-highlight-color: revert;
-webkit-touch-callout: revert;
-webkit-user-select: auto;
-ms-user-select: auto;
-moz-user-select: auto;
user-select: auto;
overflow: auto;
-ms-scroll-chaining: none;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
/* 커스텀할 내용 추가 */
border-left: 1px solid black;
border-right: 1px solid black;
}
나는.. 헤더에... 프로필 사진도 넣고... 댓글 버튼도 넣고 암튼 넣을 게 많았다.
진짜 고생햇따. ....
암튼 내용 만들고 싶으면 js 파일에 가서 header = {} 만들어서 여기다가 채워주자.
중요한 건 아직 <BottomSheet 이게 닫히지 않았다. > 닫기 전에 써야함.
<BottomSheet open skipInitialTransition snapPoints={({ maxHeight })=> [
maxHeight/11, //최소
maxHeight /2, //최대
]}
blocking = {false} //배경 블록 현상 해결
header={
<div>
header custom
</div>
}
>
</BottomSheet>
이건 닫고 쓰면 된다.
<BottomSheet>
<div>
content custom
</div>
</BottomSheet>
바텀 시트의 높이는 snapTo 를 이용해 바꿀 수 있다.
내가 설정한 snapPoints로 이동하는 건데, (해당 높이로 높이를 변경하는 건데)
<button onClick={()=> sheetRef.current.snapTo(0, { source: 'snap-to-bottom' })}>Snap to First Point</button>
<button onClick={()=>
sheetRef.current.snapTo(({ snapPoints }) => Math.max(...snapPoints), {
// Each property is optional, here showing their default values
source: 'snap-to-top',
velocity: 1,
})}>
Snap to Second Point</button>
최소로 이동할 땐 첫 번째 변수에 0을, 최대로 이동할 땐 Math.max(...snapPoints)를 넣어줬다.
진짜 포기하고 싶었다.
하................................
내가 바텀시트를 두개 만들고, CSS도 두개 만들었는데 이게 중첩적용이 됐다.
나는 CSS가 중첩적용 될 수도 있다는 걸 이날 처음 알았다. 그만큼 웹 초보였음;
온갖 해결방법을 다 써봤다.
CSS 모듈도 안되고
인라인도 안 되고
기억이 안나는데 암튼 챗지피티가 지쳐서 나가떨어져 같은 답변만 낼때까지 시도함
진짜 다 써봤는데 안됨.
이게.. 바텀시트 구조가 좀 이상했다(???)
페이지를 로딩하면
<reach-portal></reach-portal>
이게 동적으로 생성된다!!!!!!!
여기 안에 div 꽊꽊 차 있는 게 다 바텀시트임.
처음에는
아! bottomSheet에 className을 주고, 해당 클래스네임만 css 적용되게 하면 되겠다!
<BottomSheet className="homeSheet" id="parentDiv-home">
이렇게 주면 최상위 DIV에만 적용됨.. 결국 자식 DIV에는 classname적용이 안되어서 css가 다 날아갔다.
코드를 뜯어봤는데 이게 깃허브에 올라온 코드에서는 portal 생성 코드가 있는데 vs code로 넘어오면 코드를 알아볼 수 없었음.. 암호화 처리 비슷하게 되어 있는 것 같기도 하고..
코드를 짰다..
웹이 시작되자마자 자동으로 클래스 이름을 추가하는 코드였다.
useEffect(() => {
// 컴포넌트가 마운트된 후 실행될 코드
const parentDiv = document.getElementById('parentDiv');
const childDivs = parentDiv.querySelectorAll('div');
childDivs.forEach((childDiv) => {
childDiv.classList.add('commonClassName');
});
// 컴포넌트가 언마운트될 때 정리할 코드
return () => {
childDivs.forEach((childDiv) => {
childDiv.classList.remove('commonClassName');
});
};
}, []); // 빈 배열을 전달하면 컴포넌트가 처음 마운트될 때만 실행
결론부터 말하자면 망했음.
저기 저 코드 수정 100번은 했는데 망했음.
이유도 많았따. 부모 요소가 아직 렌더링되지 않아 null값이 뜨거나, ... 기억도 안 나네.
최종적으로 수정한 코드는 아무리봐도 틀린 게 없어 보였다.
마지막으로 대학원생 친구에게 부탁한 유료 버전 챗지피티에게 질문한 질문을 남긴다. (모든 고뇌가 요약되어 있음)
BottomSheet 는 index.js에서 시작할 때 < reach-portal > 를 불러오는데 나는 < reach-portal > 하위 div에 classname을 적용해주고 있어. useEffect() 부분이 그거야. useLayoutEffect()으로 바꿔도 안 돼. 내가 봐선 < reach-portal > 렌더링 시간과 하위 div에 classname을 적용하는 부분의 시간 순서가 바뀐 거 같아. 어떻게 해결해? 아니면 다른 문제가 있는거야?
그렇다..
어 안돼....
아무리 해도 reach-portal 생성과 내 코드 렌더링의 순서를 바꿀 수가 없었다.
결국;
useEffect(() => {
// setTimeout을 사용하여 portal이 생성된 후에 작업 실행
const timeoutId = setTimeout(() => {
const parentDiv = document.getElementById('parentDiv-home');
if (parentDiv) {
const childDivs = parentDiv.querySelectorAll('div');
childDivs.forEach((childDiv) => {
childDiv.classList.add('homeSheet');
});
return () => {
childDivs.forEach((childDiv) => {
childDiv.classList.remove('homeSheet');
});
};
}
}, 1); // 일정 시간 후에 실행
return () => {
clearTimeout(timeoutId); // 컴포넌트가 언마운트될 때 clearTimeout으로 타이머 해제
};
}, []);
0.001초 뒤에 내 함수가 실행되게 해서 성공했다...
너무 지친 상태에서 친 카톡이라 내용에 사소한 오류가 있을 수도 있음
암튼 난 지쳣고
- 요약
- 커스텀한 css를 쓸거면 ... 아니 그냥 쓰지마라 바텀시트 만들어서 써라
- 그래도 쓸거라면 자식 div에 className을 적용해서 css를 적용해야한다.
- 자식 div는 portal의 형태로 페이지가 생성될 때 올라오기 때문에 너도 classname을 생성 이후에 넣어주는 코드를 써야한다.
- 바텀 시트의 생성 시기와 싸울 힘이 없다면 0.001초 시간차 공격을 이용하자
근데 아무리 봐도 너무 무지성 해결이라 나중에 수정할거다 ㅠㅠ
그냥 bottomSheet css custom만 봐주세요. 저건 진짜 열심히 함;;
자료도 없어서 내가 깃허브 공식문서 뜯어보고 코드 뜯어보고 직접 했다.
자료가 없으니까 챗지피티도 모르더라.
결국 오늘은 인간이 승리했다....
리팩토링 (1) - 손쉽게 자식 div에 CSS 적용하기