Sticky Effects

17424 단어 React후크
CSS에는 position: stickey라는 지정이 있습니다. 지정 요소가 스크롤 위치까지 도달했을 경우, 요소를 화면 위치 고정하는 style 입니다. 이 stickey 입니다만, 브라우저 서포트 상황이 이마이치입니다. 또, 피터와 고정된 타이밍을, javascript 로 파악하는 방법이 없습니다. 오늘은 이 동작을 구현하면서 스크롤 위치에 따라 외부 통지하는 방법을 소개합니다.
code: github/$ yarn 1209







Context API / Provider 구성



오늘도 Context API를 사용합니다. 현재 표시 영역에 들어 있는 Section 의 index 를 current 에 보관 유지합니다. 우선은 형태 첨부 빈 context 를 생성합니다.

components/context.ts
import { createContext, Dispatch, SetStateAction } from 'react'
export const CTX = createContext({} as {
  current: number
  setCurrent: Dispatch<SetStateAction<number>>
})

그런 다음 useState의 반환 값을 Provider의 초기 값으로 주입합니다. 요 전날과 함께합니다.

components/provider.tsx
export default (props: Props) => {
  const [current, setCurrent] = useState(0)
  return (
    <Provider value={{ current, setCurrent }}>
      {props.children}
    </Provider>
  )
}

App index에서 Provider를 최고층에 설치합니다. count 는 더미 작성용이므로, 신경쓰지 말아 주세요.

components/index.tsx
const count = 5
export default () => (
  <Provider>
    <Sections count={count} />
    <Indicate count={count} />
  </Provider>
)

useStickyWrapper



Custom Hooks는 이전 샘플과 거의 다르지 않습니다. 화면내 판정 로직이 다소 달라, 이번은 화면 상부보다 위에 section 상변이 위치하는 경우 「화면내」라고 판정하고 있습니다.

sections/section/useStickyWrapper.ts
const useStickyWrapper = (props: Props) => {
  const [state, setState] = useState<State>(...)
  const options = useMemo(...)
  useEffect(...) // 画面スクロール時処理
  useEffect(...) // 画面内判定処理
}

이번에는 wrapper 컴포넌트와 Custom Hooks를 분리하고 있습니다.

components/sections/section/stickyWrapper.tsx
export default (props: Props) => {
  const ref = useRef({} as HTMLDivElement)
  useStickyWrapper({
    ref,
    onEnter: props.onEnter,
    onLeave: props.onLeave,
    throttleInterval: props.throttleInterval
  })
  return (
    <div
      className={props.className}
      id={props.id}
      ref={ref}
    >
      {props.children}
    </div>
  )
}

Context 상태 변경 Section



지금까지 준비한 StickyWrapper Component 를 이용해, Section 를 조립합니다. 화면 내외 판정시의 callback props 는, 부모로부터 주입되는 핸들러를 바인드 합니다.

components/sections/section/index.tsx
const View = (props: Props) => (
  <StickyWrapper
    id={`section${props.index}`}
    className={props.className}
    onEnter={props.onEnter}
    onLeave={props.onLeave}
  >
    ...
  </StickyWrapper>
)

다음은 부모 Container입니다. onEnter, onLeave에서 Context current를 업데이트하고 있음을 알 수 있습니다. 동시에, 자신이 화면내에 들어가 있는지 어떤지의 플래그를 useState 로 확보해, 아이 컴퍼넌트에 주입합니다.

components/sections/section/index.tsx
export default (props: ContainerProps) => {
  const [isEnter, updateState] = useState(false)
  const { setCurrent } = useContext(CTX)
  const onEnter = useCallback(() => {
    updateState(true)
    setCurrent(props.index)
  }, [])
  const onLeave = useCallback(() => {
    updateState(false)
  }, [])
  return useMemo(
    () => (
      <StyledView
        index={props.index}
        title={props.title}
        onEnter={onEnter}
        onLeave={onLeave}
        isEnter={isEnter}
        isLast={props.isLast}
        isFirst={props.isFirst}
      />
    ),
    [isEnter]
  )
}

Indicate



화면 오른쪽에 표시되는 현재 표시입니다. Context 상태를 참조합니다.



components/indicate/item.tsx
export default (props: ContainerProps) => {
  const { current } = useContext(CTX)
  const isCurrent = useMemo(() => current === props.index, [
    current
  ])
  const style = useMemo(
    () =>
      isCurrent
        ? {
            transform: 'scale(1.5)',
            backgroundColor: styles.blue
          }
        : {
            transform: 'scale(1)'
          },
    [isCurrent]
  )
  return useMemo(
    () => (
      <StyledView index={props.index} style={style} />
    ),
    [style]
  )
}

여기도 memoize를 사보하지 않고 실시합니다. 시각 효과로는 수수합니다만, 아이디어 나름으로 여러가지 기능에 응용할 수 있을 것 같습니다.

components/indicate/item.tsx
const StyledView = styled(View)`...`

components/indicate/item.tsx
const View = (props: Props) => (
  <span className={props.className} style={props.style} />
)

좋은 웹페이지 즐겨찾기