๐ŸงŸโ€โ™‚๏ธ [React] position: sticky๋กœ ๋”ฐ๋ผ๋‹ค๋‹ˆ๋Š” Top ๋ฒ„ํŠผ (scroll to top) ๋งŒ๋“ค๊ธฐ

leehyunjuยท2023๋…„ 2์›” 9์ผ
3
post-custom-banner

Top ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ๋งจ ์œ„๋กœ ์˜ฌ๋ผ๊ฐ€๋Š” ๊ฒƒ์„ ๊ตฌํ˜„ํ•ด๋ดค์Šต๋‹ˆ๋‹ค.

๐Ÿ‘‹ ๋จผ์ €, ๋ฒ„ํŠผ ๋งŒ๋“ค์–ด์ฃผ๊ธฐ

  1. react-icons ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ•ด์„œ ์œ„๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š” ํ™”์‚ดํ‘œ ์•„์ด์ฝ˜์„ ๊ฐ€์ ธ์™”๋‹ค.
    https://react-icons.github.io/react-icons/
$yarn add react-icons

๊ฒ€์ƒ‰๋ฐ”์— arrow ์น˜๋ฉด ๊ต‰์žฅํžˆ ๋งŽ์ด๋‚˜์˜ด ๐Ÿ‘€
๊ทธ์ค‘์—์„œ ๋ง˜์— ๋“œ๋Š” ํ™”์‚ดํ‘œ๋ฅผ ๊ณจ๋ผ์„œ ๊ฐ€์ ธ๋‹ค ์“ฐ๋ฉด ๋œ๋‹ค.

import { BsFillArrowUpCircleFill } from 'react-icons/bs'

์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ•˜๋ฉด ๋˜๋Š”๋ฐ ์•„์ด์ฝ˜๋ช… ์•ž๊ธ€์ž๋ฅผ ๋”ฐ์„œ from์—๋‹ค ์“ฐ๋ฉด ๋œ๋‹ค. ๋‚ด๊ฐ€ ์„ ํƒํ•œ ์•„์ด์ฝ˜์˜ ์•ž๊ธ€์ž๋Š” bs๋ผ react-icons/bs๋กœ ๋ถˆ๋Ÿฌ์™”๋‹ค.

๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ’ป JSX์™€ Scroll to Top ๋กœ์ง

import styles from './TopButton.module.scss'
import { BsFillArrowUpCircleFill } from 'react-icons/bs'

export default function TopButton() {

   // ๐Ÿ‘‰ ํด๋ฆญ ์‹œ ๋งจ ์œ„๋กœ ์˜ฌ๋ผ๊ฐ€๋„๋ก
  const handleScroll = () => {
    if (!window.scrollY) return

    window.scrollTo({
      top: 0,
      behavior: 'smooth',
    })
  }

  return (
    <div className={styles.topBtn_wrap}>
      <button className={styles.topBtn} onClick={handleScroll}>
        <BsFillArrowUpCircleFill />
      </button>
    </div>
  )
}

์ •๋ง ๊ฐ„๊ฒฐํ•œ ๋กœ์ง์œผ๋กœ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด์ œ ๋ฒ„ํŠผ ํด๋ฆญํ•˜๋ฉด ์Š ์˜ฌ๋ผ๊ฐ„๋‹ค.

์ž‘์—…ํ•˜๋ฉด์„œ ์ œ์ผ ๊ณจ์น˜ ์•„ํŒ ๋˜๊ฒŒ ์Šคํƒ€์ผ์ด ์ œ์ผ ๋นก์…Œ๋‹ค..
๋Œ€๋ถ€๋ถ„ ์ž‘์—…์ž๋“ค์ด position: fixed๋กœ ๊ตฌํ˜„ ํ•œ ๊ฑธ ๋ดค๋Š”๋ฐ, fixed๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋„“์ด ๊ธฐ์ค€์ ์„ ์žก์•„์ค˜์•ผ๋ผ์„œ ์ƒ๊ฐ์น˜ ๋ชปํ–ˆ๋˜ ๋ฒ„๊ทธ(?)๊ฐ€ ๋ฐœ์ƒ ๋๋‹ค. ์–ด๋–ค ๋ฌธ์ œ๋ƒ๋ฉด ๋„“์ด ๊ธฐ์ค€์ (950px) ๋•Œ๋ฌธ์— ์ด ๋ฒ„ํŠผ์„ ๋ถ€๋ชจ ํƒœ๊ทธ๋กœ ๊ฐ์‹ธ ๋„“์ด ๊ธฐ์ค€์ ์„ ์ฃผ์–ด์„œ ๋‹ค๋ฅธ ๋งํฌ๋กœ ๋„˜์–ด ๊ฐ€๊ณ ์‹ถ์„ ๋•Œ TOP ๋ฒ„ํŠผ๊ณผ ๋™์ผํ•œ ์œ„์น˜์— ์žˆ์„ ๊ฒฝ์šฐ ๋งํฌ๊ฐ€ ์•ˆ๋ˆŒ๋ฆฌ๋Š” ํ˜„์ƒ๊ฐ™์€ ๋ฒ„๊ทธ.. ๊ทธ๋ž˜์„œ ์ด๊ฑธ ๋˜ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ถ€๋ชจ ํƒœ๊ทธ๋ฅผ ์—†์• ๊ณ  right๋ฅผ %๋กœ ์ฃผ์ž๋‹ˆ ๋ธŒ๋ผ์šฐ์ € ์ฐฝ์„ ์ค„์ด๋ฉด ์ค„์ผ์ˆ˜๋ก ๋ฒ„ํŠผ์ด ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ์œ„์น˜๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ ์œ„์น˜๋กœ ๋„๋ง๊ฐ„๋‹ค. ์•„๋ฌดํŠผ ์—ฌ๋Ÿฌ ์‹œํ–‰์ฐฉ์˜ค๋ฅผ ๊ฑฐ์นœ ๋’ค ์—ฌ๋Ÿฌ ์ˆ˜์ •์„ ํ†ตํ•ด ์ง„์งœ ์‹ฌํ”Œํ•œ ๋ฐฉ์‹์œผ๋กœ ํ•ด๊ฒฐํ–ˆ๋‹ค.

.topBtn_wrap {
  position: sticky;
  bottom: 70px;
  float: right;
}
.topBtn {
  font-size: 3.8rem;
  color: #ffc949 !important;
}

position:sticky๋กœ ํ•ด๋„ ๋ฒ„ํŠผ์ด ์Šคํ‹ฐํ‚คํ•˜๊ฒŒ ์ž˜ ๋ถ™์–ด ๋”ฐ๋ผ๋‹ค๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๊ฑธ ์‚ฌ์šฉํ•ด์„œ float:right๋ฅผ ํ•ด์„œ ๋งจ ์˜ค๋ฅธ์ชฝ์— ์œ„์น˜ํ•˜๋„๋ก ํ–ˆ๋‹ค. ์ด์ œ ๋ถ€๋ชจํƒœ๊ทธ์— ๊ธฐ์ค€ ๋„ˆ๋น„๊ฐ’ ์žก์„ ํ•„์š”๋„ ์—†๊ณ  ๋‚ด๊ฐ€ ๊ฐ€๊ณ ์ž ํ•˜๋Š” ๋งํฌ์™€ ๋™์ผํ•œ ์œ„์น˜์— ์žˆ์„ ๋•Œ ๋งํฌ ํด๋ฆญ๋„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋๋‹ค. ํ•ด๊ฒฐ ์™„๋ฃŒ ๐Ÿ‘

๋‚ด์šฉ ์ถ”๊ฐ€

์Šคํฌ๋กค์„ ๋‚ด๋ ธ์„ ๋•Œ๋งŒ scroll to top ๋ฒ„ํŠผ์ด ๋…ธ์ถœ๋˜๊ฒŒ ํ•˜๊ณ  ์‹ถ์œผ๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ์ฝ”๋“œ๋กœ ์ž‘์„ฑํ•˜๋ฉด ๋œ๋‹ค.

import styles from './TopButton.module.scss'
import { useState, useEffect } from 'react'
import { BsFillArrowUpCircleFill } from 'react-icons/bs'

export default function TopButton() {
  const [showButton, setShowButton] = useState<boolean>(false)

  const handleScroll = () => {
    if (!window.scrollY) return

    window.scrollTo({
      top: 0,
      behavior: 'smooth',
    })
  }

  useEffect(() => {
    const handleShowButton = () => {
      if (window.scrollY > window.innerHeight) {
        setShowButton(true)
      } else {
        setShowButton(false)
      }
    }

    window.addEventListener('scroll', handleShowButton)
    return () => {
      window.removeEventListener('scroll', handleShowButton)
    }
  }, [])

  return (
    <div className={styles.topBtn_wrap}>
      {showButton && (
        <button className={styles.topBtn} onClick={handleScroll}>
          <BsFillArrowUpCircleFill />
        </button>
      )}
    </div>
  )
}

๊ฒฐ๊ณผ๋ฌผ

profile
์•„๋Š‘ํ•œ ๋‡Œ๊ณต๊ฐ„ ๐Ÿง 
post-custom-banner

0๊ฐœ์˜ ๋Œ“๊ธ€