문제 설명

두 정수 left와 right가 매개변수로 주어집니다. left부터 right까지의 모든 수들 중에서, 약수의 개수가 짝수인 수는 더하고, 약수의 개수가 홀수인 수는 뺀 수를 return 하도록 solution 함수를 완성해주세요.

 

제한사항

1 ≤ left ≤ right ≤ 1,000

 

ex)

left: 13, right: 17 👉 result: 43

left: 24, right: 27 👉 result: 52

 

function solution(left, right) {
    var answer=0;
    for(let i = left; i<right+1; i++) {
        if (Number.isInteger(Math.sqrt(i))) {
            answer -= i
        } else {
            answer +=i
        }
    }
    return answer;
}

 

제곱근이 정수면 약수의 개수는 홀수인 것을 이용했다.

 

Number.isInteger(n)

숫자(n)가 정수면 true, 아니면 false를 return한다

 

Math.sqrt(i)

숫자(i)의 제곱근을 return한다

문제 설명

길이가 n이고, "수박수박수박수...."와 같은 패턴을 유지하는 문자열을 리턴하는 함수, solution을 완성하세요. 예를들어 n이 4이면 "수박수박"을 리턴하고 3이라면 "수박수"를 리턴하면 됩니다.

 

제한 조건

n은 길이 10,000이하인 자연수입니다.

 

ex)

n = 2인 경우, '수박'을 출력

n = 3인 경우, '수박수'를 출력

 

// 주어진 n번만큼 '수박'을 반복하고 0번째부터 n번째 전까지 잘라낸다
function solution(n) {
    return "수박".repeat(n).substring(0, n)
}

repeat(n)

주어진 문자열을 주어지는 n만큼 반복하는 함수

 

substring(a, b)

문자열을 a번째부터 b번째 전까지 잘라내는 함수(slice 함수와 동일하다)

 

substr(a, b)

a번째부터 b길이만큼 잘라내는 함수

문제 설명

영어에선 a, e, i, o, u 다섯 가지 알파벳을 모음으로 분류합니다. 문자열 my_string이 매개변수로 주어질 때 모음을 제거한 문자열을 return하도록 solution 함수를 완성해주세요.

 

제한사항

  • my_string은 소문자와 공백으로 이루어져 있습니다.
  • 1 ≤ my_string의 길이 ≤ 1,000

 

입출력 예

  • "bus"에서 모음 u를 제거한 "bs"를 return합니다.
  • "nice to meet you"에서 모음 i, o, e, u를 모두 제거한 "nc t mt y"를 return합니다.

 

나의 풀이

function solution(my_string) {
	// 알파벳 모음
    const str = 'aeiou'
    // split으로 분해, filter로 str에 속한 el 제거 후, join
    let answer = my_string.split('').filter((el)=>(!str.includes(el))).join('')
    return answer;
}

문제 설명

순서쌍이란 두 개의 숫자를 순서를 정하여 짝지어 나타낸 쌍으로 (a, b)로 표기합니다. 자연수 n이 매개변수로 주어질 때 두 숫자의 곱이 n인 자연수 순서쌍의 개수를 return하도록 solution 함수를 완성해주세요.

 

제한사항

1 ≤ n ≤ 1,000,000

 

입출력 예

  • 입출력 예 #1

n이 20 이므로 곱이 20인 순서쌍은 (1, 20), (2, 10), (4, 5), (5, 4), (10, 2), (20, 1) 이므로 6을 return합니다.

  • 입출력 예 #2

n이 100 이므로 곱이 100인 순서쌍은 (1, 100), (2, 50), (4, 25), (5, 20), (10, 10), (20, 5), (25, 4), (50, 2), (100, 1) 이므로 9를 return합니다.

 

나의 풀이

function solution(n) {
	// answer 배열 초기화
    const answer = [];
    // 1 ~ n까지 약수를 answer 배열에 push
    for(let i =0; i <= n; i++) {
        if(n%i==0) { answer.push(i) }
    }
    return answer.length;
}

 

 

Django - 드쟝고 아님 주의

 

1. 가장 먼저 Python을 다운로드한다.

https://www.python.org/downloads/

 

Download Python

The official home of the Python Programming Language

www.python.org

환경변수 path에 넣는 것을 잊지 말자!

python --version
# Python 3.10.11

 

2. pip 다운로드

python get-pip.py
python -m pip install --upgrade pip

pip --version
# pip 23.1 from C:\Users\USER\AppData\Roaming\Python\Python310\site-packages\pip (python 3.10)

 

3. 가상환경 생성&실행

# 가상환경 생성
python -m venv venv

# 가상환경 실행
venv\Scripts\activate

 

4. 가상환경 내에서 Django 설치&프로젝트 생성

python -m pip install Django
pip install djangorestframework

# 장고 프로젝트 생성
django-admin startproject <프로젝트 이름>

 

프로젝트 생성 완료!

사용 프레임워크: React.js

배포 링크: https://kosy0907.github.io/Portfolio/

소스코드 링크: https://github.com/kosy0907/Portfolio (Public)

타입스크립트로 제작할까 하다가 TS는 아직 부족한 거 같아서 그냥 JS로 제작

언제나 그랬듯이 디자인 -> 구현 순서로 진행

 

디자인 못해서 여러 번 수정했다(지금도 수정중)
하지만 재밌죠?

Section은 3개로 나눴다.

  • Section1(Home) - Parallax Effect, Scroll Effect, Bounce Animation
  • Section2(About) - FadeIn Animation
  • Section3(Project) - Click-FadeIn Animation

시작은 npx create-react-app

미리 보는 component

 

1. Custom Cursor

웹사이트를 서치하다보면 가끔 둥글거나 다른 모양이 적용된 Cursor를 확인할 수 있다.

내심 어떻게 구현한 건지 궁금했는데 웹 사이트를 제작하면서 확실하게 알게 되었다.

import React, { useEffect, useRef } from 'react';
import './Cursor.css';

const Cursor = () => {
    const mainCursorRef = useRef(null);
    const subCursorRef = useRef(null);

    useEffect(() => {
        const onMouseMove = (e) => {
            const { clientX, clientY } = e

            mainCursorRef.current.style.transform = `translate3d(${clientX}px, ${clientY}px, 0)`;
            subCursorRef.current.style.transform = `translate3d(${clientX}px, ${clientY}px, 0)`;
            subCursorRef.current.style.transition = `all 0.15s`;
        }
        document.addEventListener('mousemove', onMouseMove);

        return () => {
            document.removeEventListener('mousemove', onMouseMove);
        }
    }, [])

    return (
        <div>
            <div className='mainCursor' ref={mainCursorRef} />
            <div className='subCursor' ref={subCursorRef} />
        </div>
    );
}

export default Cursor;

1. div에 useRef Hook을 사용해 mainCursorRef, subCursorRef를 걸고

2. useEffect Hook으로 마우스가 이동할 때마다 커서의 위치(clientX, clientY)를 업데이트 하는 이벤트 리스너를 추가한다. 

3. subCursor(조금 더 큰 원)이 커서를 천천히 따라올 수 있도록 transition animation을 추가한다.

이렇게 하면 Component가 마운트되면 이벤트 리스너가 추가되고 언마운트되면 제거된다.

Custom Cursor 완성!

 

2. Section1 - Home

Parallax Effect를 넣고 싶어서 직접 제작한 픽셀아트 gif의 레이어를 밤하늘, 별, 건물, 산 등등으로 나눴다.

아이패드 없었으면 어쩔 뻔했어

예전에는 Steam에서 구매한 Aseprite 썼는데 아이패드로 하는 게 훨씬 편하고 빠르다.

진작에 프로크리에이트 쓸걸

잠깐! 포토샵을 사용한 이유는?

픽셀아트는 캔버스 사이즈가 작기 때문에 (보통 200x200) 막 늘려서 웹페이지에 적용했다가는 이미지가 깨질 수 있다.

포토샵으로 레이어 하나하나 크기를 늘려준다. 귀찮은 작업일 수 있지만 픽셀아트는 이것마저 재밌다.

image 폴더에 쏙

// Section1
import React, { useEffect, useState } from 'react';
...

function Section1(props) {
    // parallax
    const [position, setPosition] = useState(0);
    
    const onScroll = () => {
        setPosition(window.scrollY);
    }
    const toAbout = () => {
        props.aboutRef.current?.scrollIntoView({ behavior: 'smooth' });
    }

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

    return (
        <div className='home'>
            <div className='introBg'>
                <div className='intro bg1' style={{ backgroundPositionY: position }} />
                <div className='intro bg2' style={{ backgroundPositionY: position }} />
                <div className='intro bg3' style={{ backgroundPositionY: position / 2 }} />
                <div className='intro bg4' style={{ backgroundPositionY: position / 2 }} />
                <div className='intro bg5' style={{ backgroundPositionY: position / 3 }} />
                <div className='intro bg6' style={{ backgroundPositionY: position / 10 }} />

                <div className='intro cover'>
                    <div className='text'>
                    // Vertical Scrolling Text
                        <div className='scrollContainer'>
                            <div className='scrollBox'>
                                ...
                            </div> 
                        </div >

                        <div className='fixed-container'>
                            ...
                        </div>
                        // Arrow Bounce Animation
                        <div style={{ marginTop: "3rem" }}>
                            <FontAwesomeIcon className='bounceArrow' icon={faAngleDown} size="2x" />
                        </div>
                       	<div className='toAboutBtn' onClick={toAbout}>About Me</div>
                    </div >
                </div >

            </div >
        </div >
    );
}

export default Section1;

intro bg1~bg6은 각각 위에서 만든 픽셀아트 레이어들을 담는 div이다.

intro cover로 전체 화면을 어둡게 덮고 그 위에 Scrolling text 애니메이션과 Bounce Animation을 적용했다.

특정 환경에서는 PositionY의 값이 변경될 수 있다.

toAboutBtn은 클릭 시 바로 About Section으로 스크롤 되도록 구현했다.

레이어 나눈 보람이 있다

// Navbar
import React, { forwardRef } from 'react';
import './Navbar.css';

function Navbar(props) {

    const HomeClick = () => {
        props.setNavState(1);
        window.scrollTo({ top: 0, behavior: 'smooth' });
    }

    const AboutClick = () => {
        props.setNavState(2);
        props.aboutRef.current?.scrollIntoView({ behavior: 'smooth' });
    }

    const ProjectClick = () => {
        props.setNavState(3);
        props.projectRef.current?.scrollIntoView({ behavior: 'smooth' });
    }

    return (
        <nav>
            <ul>
                <li className='navBtn' onClick={HomeClick}>HOME<span>.</span></li>
                <li className='navBtn' onClick={AboutClick}>ABOUT<span>.</span></li>
                <li className='navBtn' onClick={ProjectClick}>PROJECT<span>.</span></li>
            </ul>
        </nav>
    );
}
export default forwardRef(Navbar);

App.js(부모 컴포넌트)에서 props를 통해 setNavState, 다른 Section 컴포넌트(About, Project)를 가져온다.

Navbar에 있는 버튼을 클릭할 때마다 각각 HomeClick, AboutClick, ProjectClick 함수가 호출되어 부드럽게 스크롤 되도록 구현했다. 

 

3. Section2 - About

간단한 소개와 Education, Certificate, 사용 가능한 Skill을 담은 컴포넌트이다.

Education과 Certificate를 넣는 게 맞는 선택일까 고민했지만 그냥 넣었다.

import React, { forwardRef, useEffect } from 'react';
import './Section2.css';

function Section2(props, aboutRef) {

    useEffect(() => {
        const observer = new IntersectionObserver(
            entries => {
                entries.forEach(entry => {
                    if (entry.isIntersecting) {
                        entry.target.classList.add('fadeIn');
                    } else {
                        entry.target.classList.remove('fadeIn');
                    }
                });
            },
            { threshold: 0.2 }
        );

        const targets = document.querySelectorAll('.fadeTarget');
        targets.forEach(target => observer.observe(target));

        return () => observer.disconnect();
    }, []);

    return (
        <div className='about' ref={aboutRef}>
            <div className='container'>
                <div className='title fadeTarget'>
                    <p>About Me</p>
                </div>
                <div className='aboutContent fadeTarget'>
                    ...
                </div>
            </div>
        </div>
    );
}

export default forwardRef(Section2);

Home에서 About으로 스크롤 시 fade in 애니메이션으로 나타나게끔 구현했다.

여기서는 Intersection Observer를 사용했다. 

1. 컴포넌트가 마운트될 때 IntersectionObserver를 생성하고

2. Observer가 관찰할 수 있도록 애니메이션을 줄 div에 'fadeTarget' 클래스를 추가한다.

3. 여기서 style에 animation delay를 줘서 애니메이션을 늦출 수 있다.

4. css에 fadeIn 애니메이션을 설정하면 끝!

 

4. Section3 - Project

Navbar의 Project 버튼을 클릭하거나 About Section에서 밑으로 스크롤하면 나타난다.

projectTitle을 Click했을 때, 프로젝트 정보를 담은 card가 fade in animation으로 cardContainer에 나타나게 구현하고 싶었다.

// section3(project)
	...
	const handleItemClick = (item) => {
        if (selectedItem === null) {
            setSelectedItem({ ...item, show: true });
            setIsCardVisible(true);
        } else if (selectedItem.id !== item.id) {
            setSelectedItem({ ...item, show: true });
            setIsCardVisible(false);
            setTimeout(() => setIsCardVisible(true), 500);
        }
    };
    ...
    return (
    	...
        <div className='projectTitle'>
        ...
        <div className='cardContainer>
        ...
    )
// Card.js
import React, { useEffect, useState } from 'react';
import './Card.css';

function Card(props) {
    const { item } = props;

    const [showCard, setShowCard] = useState(false);

    useEffect(() => {
        if (item.show) {
            setTimeout(() => {
                setShowCard(true);
            }, 20);
        }
    }, [item]);

    return (
        <div className={`card ${showCard ? 'show' : ''}`}>
            <div className="cardTitle">{item.title}</div>
            <div className="cardDescription">{item.description}</div>
        </div>
    );
}

export default Card;

하지만, 이렇게 하면 다른 projectTitle을 클릭했을 때, 가끔씩 애니메이션이 적용되지 않는 문제가 있었다.

원인은 handleItemClick의 setTimeout이 문제였다.

setTimeout은 시간을 정확하게 보장하지 않기 때문에, 대신 requestAnimationFrame을 사용하는 것이 좋다.

requestAnimationFrame은 브라우저의 리플로우, 리페인트 과정을 최적화하면서 실행되기 때문에, 애니메이션 효과를 좀 더 부드럽게 적용할 수 있다.

// section3 수정
    const handleItemClick = (item) => {
        if (selectedItem === null) {
            setSelectedItem({ ...item, show: true });
            setIsCardVisible(true);
        } else if (selectedItem.id !== item.id) {
            setSelectedItem({ ...item, show: true });
            setIsCardVisible(false);
            window.requestAnimationFrame(() => setIsCardVisible(true));
        }
    };

 

5. App.js

...

function App() {
  const [scrollIndex, setScrollIndex] = useState(1);
  const [navState, setNavState] = useState(1);
  const aboutRef = useRef(null);
  const projectRef = useRef(null);

  useEffect(() => {
    if (navState === 1) {
      setScrollIndex(1);
    } else if (navState === 2) {
      setScrollIndex(2);
    } else if (navState === 3) {
      setScrollIndex(3);
    }
  }, [navState])

  useEffect(() => {
    const wheelHandler = (e) => {
      ...
    };

    document.addEventListener("wheel", wheelHandler);
    return () => {
      document.removeEventListener("wheel", wheelHandler);
    };

  }, []);

  return (
    <div className="App">
      <Cursor />
      <Navbar aboutRef={aboutRef} projectRef={projectRef}
        setNavState={setNavState} />
      <div className='section'>
        <Dots scrollIndex={scrollIndex} />
        <Section1 />
        <div className="divider" />
        <Section2 ref={aboutRef} />
        <div className="divider" />
        <Section3 ref={projectRef} />
      </div>
      <Footer />
    </div>
  );
}

export default App;

App.js에는 Navbar에 Section을 넘겨주고 컴포넌트를 리턴하도록 구현했다.

마우스 스크롤 시 부드럽게 움직이도록 wheelHandler 함수를 추가했다.

'React.js > Project' 카테고리의 다른 글

EV-map 제작기(백업)  (0) 2023.05.03

프로젝트를 진행하다 보면 GitHub의 Repository를 정리해야 하는 순간이 있다.

나는 스터디를 진행할 때마다 새로운 Repository를 만들었기 때문에 스터디 진행 후 버려진 Repository가 너무 많았다.

이번에는 commit log를 유지하면서 ReactStudy Repository(main repo)와 reactTest Repository(sub repo)를 합쳐보겠다.

 

1. main repo clone

2. main repo 안에 새로운 폴더 ReactTest를 만들어 기존의 reactTest repo를 옮긴다

3. git add. 후 git push

 

1. main repo clone

// git clone https://github.com/사용자이름/main-repo.git

git clone https://github.com/kosy0907/ReactStudy.git

2. main repo 안에 새로운 폴더 ReactTest를 만들어 기존의 reactTest repo를 옮긴다

// git subtree add --prefix=하위폴더이름 sub-repo.git sub-repo브랜치

git subtree add --prefix=ReactTest https://github.com/kosy0907/reactTest.git master

branch 이름 주의

3. git add. 후 git push

git add.
git push

 

결과물

잘 합쳐짐
reactTest의 커밋로그도 남아있다!

 

Error!

Working tree has modifications. Cannot add.

발생원인: 해당 repo가 최신 버전이 아님

해결: git push를 통해 현재 폴더가 최신 버전이 되게 한다

'Study > Git' 카테고리의 다른 글

팀 프로젝트 이후 소스코드 내 Repository로 옮기기  (0) 2023.03.26

요약

git clone --mirror {기존 팀 프로젝트 Repository 주소}
cd {기존 팀 프로젝트 Repository 명}.git
git remote set-url --push origin {개인 리파지토리 주소}
git push --mirror

 

1. git clone

기존 팀 프로젝트의 repository에서 로컬 폴더로 clone

git clone --mirror {기존 팀 프로젝트 Repository 주소}

 

2. 로컬 폴더로 이동

cd {기존 팀 프로젝트 Repository 명}.git

 

3. 개인 repository 주소로 저장소 url 변경 - remote set-url 사용

git remote set-url --push origin {개인 리파지토리 주소}

 

4. push

git push --mirror

'Study > Git' 카테고리의 다른 글

Repository 합치기 - commit log 유지  (0) 2023.03.28

+ Recent posts