thumbnail
TopButton ๊ตฌํ˜„
๋ธ”๋กœ๊ทธ
2023.09.07.

๊นƒํ—ˆ๋ธŒ ๋ธ”๋กœ๊ทธ๋ฅผ ๊ตฌํ˜„ํ•˜๋˜ ์ค‘. ๋‹ค๋ฅธ ๋ธ”๋กœ๊ทธ๋‚˜ ํŽ˜์ด์ง€์—์„œ ํ”ํ•˜๊ฒŒ ๋ณผ ์ˆ˜ ์žˆ๋Š” โ€˜ํƒ‘๋ฒ„ํŠผโ€™์„ ๊ตฌํ˜„ํ•ด๋ณด๊ธฐ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ €๋Š” ๊ธฐ์กด์— ๋‹ค๋ฅธ ํ™ˆํŽ˜์ด์ง€๋ฅผ ํด๋ก ์ฝ”๋”ฉํ•˜๋ฉด์„œ ๊ตฌํ˜„ํ•ด ๋†“์€ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ, ์ œ์ด์ฟผ๋ฆฌ ์ฝ”๋“œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

1. ๊ธฐ์กด์˜ ์ฝ”๋“œ (jQuery)

$(document).ready(function() {
    // main2์— ๋‚ด๋ ค๊ฐ€๊ธฐ์ „๊นŒ์ง€ ๋ฒ„ํŠผ ์ˆจ๊ธฐ๊ธฐ
    $('.t-btn').hide();

    // ์Šคํฌ๋กค ์ด๋ฒคํŠธ ๊ฐ์ง€
    $(window).scroll(function() {
        // main2์˜ ์œ„์น˜๋ฅผ ํ™•์ธํ•˜์—ฌ ๋ฒ„ํŠผ ๋ณด์ด๊ธฐ/์ˆจ๊ธฐ๊ธฐ
        var section2Offset = $('.main2').offset().top;
        if ($(window).scrollTop() > section2Offset) {
            $('.t-btn').fadeIn();
        } else {
            $('.t-btn').fadeOut();
        }
    });

    // ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ์ƒ๋‹จ์œผ๋กœ ์Šคํฌ๋กค
    $('.t-btn').click(function() {
        $('html, body').animate({ scrollTop: 0 }, 'slow');
    });
}

๊ธฐ์กด์˜ ์ฝ”๋“œ๋ฅผ ๊ฐ€๋ณ๊ฒŒ ์‚ดํŽด๋ณด๋ฉด, ์Šคํฌ๋กค ์ด๋ฒคํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŽ˜์ด์ง€์˜ ์Šคํฌ๋กค ์œ„์น˜๋ฅผ ๊ฐ์ง€ํ•˜๊ณ , ํŠน์ •์„น์…˜(main2 ํด๋ž˜์Šค)์— ๋„๋‹ฌํ•˜๋ฉด ๋ฒ„ํŠผ์„ ํ‘œ์‹œํ•˜๊ณ  ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ํŽ˜์ด์ง€ ์ƒ๋‹จ์œผ๋กœ ์Šคํฌ๋กค์•ก์…˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

ํ˜„์žฌ ์ œ ๋ธ”๋กœ๊ทธ์—์„œ๋Š” Gatsby JS์™€ TypeScript๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ์— ์†๋ด์•ผํ•  ๋ถ€๋ถ„์ด ์กฐ๊ธˆ ์žˆ์Šต๋‹ˆ๋‹ค.

2. 1์ฐจ ์ˆ˜์ • ์ฝ”๋“œ (typescript)

document.addEventListener("DOMContentLoaded", function () {
  // main2์— ๋‚ด๋ ค๊ฐ€๊ธฐ์ „๊นŒ์ง€ ๋ฒ„ํŠผ ์ˆจ๊ธฐ๊ธฐ
  const tBtn = document.querySelector('.t-btn');
  if (tBtn) {
    tBtn.style.display = 'none';
  }

  // ์Šคํฌ๋กค ์ด๋ฒคํŠธ ๊ฐ์ง€
  window.addEventListener("scroll", function () {
    // main2์˜ ์œ„์น˜๋ฅผ ํ™•์ธํ•˜์—ฌ ๋ฒ„ํŠผ ๋ณด์ด๊ธฐ/์ˆจ๊ธฐ๊ธฐ
    const main2 = document.querySelector('.main2');
    if (main2) {
      const section2Offset = main2.getBoundingClientRect().top;
      if (window.scrollY > section2Offset) {
        if (tBtn) {
          tBtn.style.display = 'block';
        }
      } else {
        if (tBtn) {
          tBtn.style.display = 'none';
        }
      }
    }
  });

  // ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ์ƒ๋‹จ์œผ๋กœ ์Šคํฌ๋กค
  if (tBtn) {
    tBtn.addEventListener("click", function () {
      window.scrollTo({ top: 0, behavior: 'smooth' });
    });
  }
});

๊ธฐ์กด์˜ ๊ธฐ๋Šฅ๊ณผ ๋™์ผํ•˜๊ฒŒ ๊ทธ๋Œ€๋กœ Typescriptํ˜•์‹์œผ๋กœ๋งŒ ๋ฐ”๊พธ์—ˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ์ถ”๊ฐ€์ ์œผ๋กœ ์ž‘์—…์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

  1. main2 ํด๋ž˜์Šค์™€ ์—ฐ๊ฒฐ๋œ ์Šคํฌ๋กค ์ด๋ฒคํŠธ๋ฅผ scrollY ๋ฅผ ์ด์šฉํ•œ ์Šคํฌ๋กค ์ด๋ฒคํŠธ๋กœ ์ˆ˜์ •
  2. ์—ฌ๋Ÿฌ querySeletor๋ฅผ ์ œ๊ฑฐํ•˜๊ณ  ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ๋กœ ๋ณ€ํ™˜

ํฌ๊ฒŒ ๋ณด๋ฉด ๋‘ ๊ฐ€์ง€์ธ๋ฐ, ์ œ๊ฐ€ ์‹ค์ œ๋กœ ๊ฑฐ์ณ๊ฐ„ ๊ณผ์ •์€ ์กฐ๊ธˆ ๋ณต์žกํ–ˆ์Šต๋‹ˆ๋‹ค.

3. 2์ฐจ ์ˆ˜์ • ์ฝ”๋“œ

// ์Šคํฌ๋กค ์ด๋ฒคํŠธ ๊ฐ์ง€ํ•˜์—ฌ ๋ฒ„ํŠผ ๋ณด์ด๊ธฐ/์ˆจ๊ธฐ๊ธฐ
function handleScroll() {
  const tBtn = document.querySelector('.tBtn')

  if (tBtn instanceof HTMLElement) {
    const scrollPosition = window.scrollY

    if (scrollPosition > 500) {
      tBtn.style.display = 'block'
    } else {
      tBtn.style.display = 'none'
    }
  }
}

// ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ์ƒ๋‹จ์œผ๋กœ ์Šคํฌ๋กค
function scrollToTop() {
  const tBtn = document.querySelector('.t-btn')
  if (!tBtn) return

  tBtn.addEventListener('click', function () {
    window.scrollTo({ top: 0, behavior: 'smooth' })
  })
}

// ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ ์ดˆ๊ธฐํ™” ๋ฐ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋“ฑ๋ก
document.addEventListener('DOMContentLoaded', function () {
  handleScroll()
  scrollToTop()

  // ์Šคํฌ๋กค ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋“ฑ๋ก
  window.addEventListener('scroll', handleScroll)
})

์ฒ˜์Œ์—๋Š” ESLint ์˜ค๋ฅ˜๊ฐ€ ๋–ด์—ˆ์Šต๋‹ˆ๋‹ค. tBtn์ด โ€˜anyโ€™ํƒ€์ž…์œผ๋กœ ๊ฐ„์ฃผ๋˜์–ด display์™€ ๊ฐ™์€ ์†์„ฑ์— ๋Œ€ํ•œ ์•ˆ์ „ํ•˜์ง€ ์•Š์€ ๋ฉค๋ฒ„ ์•ก์„ธ์Šค๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ณ  ์žˆ๋‹ค๊ณ  ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ tBtn์„ HTMLElement ๋˜๋Š” null๋กœ ์บ์ŠคํŒ…ํ•˜์—ฌ ํƒ€์ž… ์•ˆ์ •์„ฑ์„ ํ™•๋ณดํ•˜๋Š”๊ฒŒ ํ•„์š”ํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿผ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ์ง€์†์ ์œผ๋กœ ๋ฉค๋ฒ„ ์•ก์„ธ์Šค ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜์—ฌ querySelector๋กœ ์ฐพ์€ ์š”์†Œ๊ฐ€ ์‹ค์ œ๋กœ HTMLElement์ž„์„ Typescript์— ์•Œ๋ ค์ฃผ์–ด์•ผํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด์„œ if (tBtn instanceof HTMLElement){} ๋ฅผ ํ†ตํ•ด ์˜ค๋ฅ˜๋ฅผ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค.

4. ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ

import React, { useState, useEffect } from 'react';

function TopButton() {
  const [isVisible, setIsVisible] = useState(false);

  useEffect(() => {
    // ์Šคํฌ๋กค ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
    function handleScroll() {
      const scrollPosition = window.scrollY;
      setIsVisible(scrollPosition > 500);
    }

    // ์Šคํฌ๋กค ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋“ฑ๋ก
    window.addEventListener('scroll', handleScroll);

    // ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ ๋˜์—ˆ์„ ๋•Œ ์ดˆ๊ธฐ ์Šคํฌ๋กค ์œ„์น˜์— ๋”ฐ๋ผ ๋ฒ„ํŠผ ๋ณด์ด๊ธฐ/์ˆจ๊ธฐ๊ธฐ ์„ค์ •
    handleScroll();

    // ์ปดํฌ๋„ŒํŠธ ์–ธ๋งˆ์šดํŠธ ์‹œ ์Šคํฌ๋กค ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์ œ๊ฑฐ
    return () => {
      window.removeEventListener('scroll', handleScroll);
    };
  }, []);

  return (
    <button
      className="t-btn"
      style={{ display: isVisible ? 'block' : 'none' }}
      onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
    >
      Top
    </button>
  );
}

export default TopButton;

TopButton ์„ ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ๋กœ ๋ณ€ํ™˜ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ๋ฅผ ํ•œ ๋ฒˆ ๋ฆฌ๋ทฐํ•ด๋ณด์ž๋ฉด,

  1. import React, {useEffect} from โ€˜reactโ€™ ์—์„œ React์™€ useEffect๋ฅผ ์ž„ํฌํŠธ ํ–ˆ์Šต๋‹ˆ๋‹ค. ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ฑฐ๋‚˜ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋“ฑ์„ ์ถ”๊ฐ€ํ• ๋•Œ ํ•„์š”ํ•œ ๊ตฌ๋ฌธ์ž…๋‹ˆ๋‹ค.
  2. funtion TopButton(){} ํ•จ์ˆ˜ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ •์˜ํ–ˆ์Šต๋‹ˆ๋‹ค.
  3. useEffect(() โ‡’ {โ€ฆ}, []) useEffect ํ›…์„ ์‚ฌ์šฉํ•˜์—ฌ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ๋ ๋•Œ ์‹คํ–‰ํ•  ํ•จ์ˆ˜๋ฅผ ์ •์˜ ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋นˆ๋ฐฐ์—ด์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์˜์กด์„ฑ์„ ๋Œ€์‘ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๋ฐฐ์—ด์— ํฌํ•จ๋œ ๋ณ€์ˆ˜๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งŒ ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.
  4. funtion handleScroll() {} ์Šคํฌ๋กค ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
  5. funtion scrollToTop() {} ํŽ˜์ด์ง€ ์ƒ๋‹จ์œผ๋กœ ์Šคํฌ๋กคํ•˜๋Š” ๋ฒ„ํŠผ ํด๋ฆญ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.

๊ฐ„๋žตํ•˜๊ฒŒ ์œ„์™€ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด Top Button์„ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ํŽ˜์ด์ง€์˜ ์ƒ๋‹จ์œผ๋กœ ์Šคํฌ๋กค ํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ๋งŒ๋“ค์–ด์กŒ์Šต๋‹ˆ๋‹ค.

์ด์ œ ์Šคํƒ€์ผ๋งŒ ์ž…ํ˜€์ฃผ๋ฉด ์ œ๊ฐ€ ๊ณ ๋ฏผํ•œ๊ฒƒ์€ ๋์ด๋‚ฉ๋‹ˆ๋‹ค.

5. ์Šคํƒ€์ผ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑ

import React, { useState, useEffect } from 'react'

import '../styles/themeMode.css'

function TopButton() {
  const [isVisible, setIsVisible] = useState(false)

  useEffect(() => {
    // ์Šคํฌ๋กค ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
    function handleScroll() {
      const scrollPosition = window.scrollY
      setIsVisible(scrollPosition > 500)
    }

    // ์Šคํฌ๋กค ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋“ฑ๋ก
    window.addEventListener('scroll', handleScroll)

    // ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ ๋˜์—ˆ์„ ๋•Œ ์ดˆ๊ธฐ ์Šคํฌ๋กค ์œ„์น˜์— ๋”ฐ๋ผ ๋ฒ„ํŠผ ๋ณด์ด๊ธฐ/์ˆจ๊ธฐ๊ธฐ ์„ค์ •
    handleScroll()

    // ์ปดํฌ๋„ŒํŠธ ์–ธ๋งˆ์šดํŠธ ์‹œ ์Šคํฌ๋กค ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์ œ๊ฑฐ
    return () => {
      window.removeEventListener('scroll', handleScroll)
    }
  }, [])

  // ์Šคํƒ€์ผ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋Š” ํ•จ์ˆ˜
  const buttonStyle: React.CSSProperties = {
    position: 'fixed',
    bottom: '110px',
    right: '25px',
    border: 'none',
    borderRadius: '50%',
    width: '50px',
    height: '50px',
    fontSize: '24px',
    cursor: 'pointer',
    display: isVisible ? 'block' : 'none', // isVisible ์ƒํƒœ์— ๋”ฐ๋ผ ๋™์ ์œผ๋กœ ์„ค์ •
  }

  return (
    <button
      style={buttonStyle}
      className="tBtn"
      onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
    >
      ๐Ÿ‘†
    </button>
  )
}

export default TopButton

cssํŒŒ์ผ์„ ๋”ฐ๋กœ ๋งŒ๋“ค์ˆ˜๋„ ์žˆ์—ˆ์ง€๋งŒ, ๊ธฐ์กด์— ๋‹ค๋ฅธ ๊ธฐ๋Šฅ๋“ค๋„ ์ƒ๋‹น๋ถ€๋ถ„ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ปดํฌ๋„ŒํŠธ ์Šคํƒ€์ผ์„ ๋™์ ์œผ๋กœ ์„ค์ •ํ•ด์™”์—ˆ์Šต๋‹ˆ๋‹ค. ํ†ต์ผ์„ฑ์„ ์œ„ํ•ด์„œ ์ปดํฌ๋„ŒํŠธ ์Šคํƒ€์ผ ํ•จ์ˆ˜๋ฅผ ์ƒ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

์ถ”ํ›„ ๋ฆฌํŒฉํ† ๋ง์„ ํ•˜๋ฉด์„œ CSS๋ฅผ ํ•œ๋ฒˆ์— ๋บ„์ง€๋Š” ๋‹ค์‹œํ•œ ๋ฒˆ ๊ณ ๋ฏผํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•ด์„œ TopButton.tsx ์ปดํฌ๋„ŒํŠธ๋Š” ์™„์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์•„์ง ์ต์ˆ™ํ•˜์ง€ ์•Š์€ Typescript๋ผ ๊ณผ์ •๋„ ๋งค๋„๋Ÿฝ์ง€ ์•Š๊ณ  ์—ฌ์ •์ด ๊ธธ์—ˆ์ง€๋งŒ ๊ธฐ์กด์˜ QuerrySelector ์™€ ์Šคํฌ๋กค ์ด๋ฒคํŠธ๋ฅผ ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ๋กœ ์ˆ˜์ •ํ•ด๋ณผ ์ˆ˜ ์žˆ๋Š” ๊ฒฝํ—˜์„ ๊ฐ€์งˆ์ˆ˜์žˆ์–ด์„œ ๋ฟŒ๋“ฏํ–ˆ์Šต๋‹ˆ๋‹ค.

Thank You for Visiting My Blog, Have a Good Day ๐Ÿ˜†
ยฉ 2023 Developer JaeHyeon, Powered By Gatsby.