도영스 공간

상단에 닿았을 때 헤더 색 투명, 스크롤 내리면 헤더색 흰색으로 (feat. react custom hook) 본문

TIL

상단에 닿았을 때 헤더 색 투명, 스크롤 내리면 헤더색 흰색으로 (feat. react custom hook)

dogdogdodo 2023. 7. 4. 23:26
반응형

미리보기 방지용 크큭

위처럼 스크롤을 하단으로 내리면,

헤더의 색이 흰색으로 변하고 다시 상단에 닿았을 땐 투명으로 변하는 것을 훅으로 구현해보았다!

 

Scroll Event는 전체적인 스크롤에 반응한다. 이는 성능적으로 비효율적이다.

스크롤 이벤트에 쓰이는 documentElement.scrollTop과 documentElement.offsetHeight는 reflow를 일으켜서 성능상 좋지 않다.

스크롤 이벤트는 탈락!

 

내가 사용한 것은 바로 Intersection Observer API이다.

Intersection Observer API 는 루트 요소와 타겟 요소의 교차점을 관찰한다. 그리고 타겟 요소가 루트 요소와 교차하는지 아닌지를 구별하는 기능을 제공하고 있다. scroll 이벤트와 다르게 교차 시 비동기적으로 실행되며 가시성 구분 시 reflow 를 발생시키지 않는다!!! 여러모로 성능 상 유리하다.

 

“리플로우”는 모든 엘리먼트의 위치와 길이 등을 다시 계산하는 것으로 문서의 일부 혹은 전체를 다시 렌더링한다.

단일 엘리먼트 하나를 변경해도, 하위 엘리먼트나 상위 엘리먼트 등에 영향을 미칠 있다.

 

이러한 이유로 Intersection Observer API 선택!

 

우선 커스텀 훅이다!

import { useEffect, useState } from 'react';

type UseIsVisibleReturnType = [(node: HTMLElement | null) => void, boolean];

interface UseIsVisiblePropsType {
	options: {
		threshold: number; // 교차점이 어느정도 보여지는지에 따라
		rootMargin: string; // 어느시점부터 관찰을 시작할 것인지 기본값은 0px;
	};
	initialVisible: boolean; // 관찰대상의 초기 값 보인다면 true, 보이지 않는다면 false
}
const useIsVisible = (props: UseIsVisiblePropsType):UseIsVisibleReturnType => {
	const { options, initialVisible } = props;
	const [visibleRef, setVisibleRef] = useState(null);
	const [isVisible, setIsVisible] = useState(initialVisible);

	useEffect(() => {
		const observer = new IntersectionObserver(([entry]) => {
			setIsVisible(entry.isIntersecting); //관찰중이면 true , 아니면 false
		}, options);

		if (visibleRef) { //관찰대상이 있을때만 실행
			observer.observe(visibleRef);
			return;
		}

		// Clean up function
		return () => {
			if (visibleRef) { //관찰대상이 있을때만 실행
				observer.unobserve(visibleRef);
			}
		};
	}, [visibleRef]);


	const setRefCallback = (node) => {
		setVisibleRef(node); // 관찰대상을 set해주는 부분
	};

	return [setRefCallback, isVisible]; 
};

export default useIsVisible;

실제 컴포넌트에서 사용하는 방법

 

	const [visibleRef, isVisible] = useIsVisible({
		options: {
			rootMargin: '0px',
			threshold: 1.0, // visibleRef가 모두 보였을 때만 true
		},
		initialVisible: false,
	});

우선 리액트 훅을 호출!!!

<ImageWrap ref={visibleRef as HTMLElement}>
	{/*children 요소를 넣으면 됩니다.*/}
</ImageWrap>
<Header isTop={isVisible as boolean}
onClick={() => {
onClickClose(id);
}}>
	<Icon.Close color={isVisible ? '#fff' : '#000'} size='21' />
</Header>
                    
      const ImageWrap = styled.div`
		width: 100%;
		height: 230px;
		border-radius: 20px 20px 0 0;
		overflow: hidden;
		position: relative;
	`;
    const Header = styled.div<{ isTop: boolean }>`
		width: 100%;
		height: 57px;
		position: fixed;
		top: 0;
		left: 0;
		display: flex;
		align-items: center;
		justify-content: flex-end;
		z-index: 99;
		border-radius: 20px 20px 0 0;
		padding: 0 18px;
		box-sizing: border-box;
		background: ${({ isTop }) => (isTop ? 'none' : '#fff')};
		transition: background 0.25s ease-out;
		cursor: pointer;
	`;

적용은 참 쉽다! header는 fixed포지션으로 항상 고정되는 반면 ImageWrap은 스크롤을 내리면 사라진다!

그 점을 이용하여 간단하게 구현해보았다!

 

오늘의 기록 끝..

이제 꾸준히 다시 업로드하는 습관을 들이겠다!

728x90
반응형

'TIL' 카테고리의 다른 글

[NextJS] styled-components Prop `className` did not match.  (0) 2023.04.26
리액트 더보기 접기 기능구현  (0) 2023.02.08
Comments