본문 바로가기

Learning/Javascript

마우스 스크롤과 메뉴 하이라이트 주기

velog를 보면 마우스를 내리면 오른쪽에 목차가 같이 따라 내려가는것이 보인다.

이전부터 한번 만들어 보자 라고 생각만 하다가

우연히 창원 개발자 단톡방에서 어떤분이 질문을 하여 

이참에 구현해 보자라고 생각이 되어 코딩을 시작한다.

(우리회사 부장은 코딩이란 단어를 싫어한다 왜인지 모르겠는데 

코딩쟁이가 코딩이라고 업무보고 적으면 안되나??? 쳇)

 

자! 구현해 보자!

우선 깃에서 자바스크립트 공부내용을 끌고 오고

[.]             [..]            [api]           [bridge]        [drag-and-drop] [dropdown]      [egoing]        [export-import] modal.js        README.md
study.css       test.html
               4개 파일               3,234 바이트
               8개 디렉터리  222,409,691,136 바이트 남음

F:\dittybox\study\javascript>git pull origin main
From https://github.com/dittyBox/study-javascript
 * branch            main       -> FETCH_HEAD
Already up to date.

F:\dittybox\study\javascript>

음... 공부 안한지 좀 됬나...

code . 으로 vscode를 열어 코딩 시작~

 

우선 로직을 생각해 보자

벨로그는 H태그가 화면에 보일때 같은 항목의 메뉴에 하이라이트 효과를 준다 

효과는 padding-left를 -주고 볼트체를 준다는것 정도?

querySelectorAll로 H태그를 다 가져온다?..

그럼 H태그와 메뉴와 1:1 매칭은 어떻게 할까?

메뉴 구성은 div로 우선 하고 포지션을 줘서 최상단에서 스크롤에 영향이 없도록 위치 고정

본문은 div 안에 h와 p태그 사용

아... 본문의 내용은 불러 와 지는것이니...

 

 

html구성은 초간단

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta  charset="utf-8">
    <link href="./style.css" type="text/css" rel="stylesheet" />
</head>
<body>
    <div id="viewDiv" class="viewDiv">
    </div>
    <div id="menuDiv" class="menuDiv">
        <div>아래에 메뉴가 들어 가겠지</div>
    </div>
    <script src="app.js"></script>
</body>
</html>

기본 CSS

html,body{
    margin: 0;
    padding: 0;
}

p{
    padding-top:100px;
    padding-bottom:100px;
}
.viewDiv {
    height: 9000px;
    padding-left: 250px;
}

.menuDiv{
    position:fixed;
    top:0;
    padding:10px;
    margin: 0;
    height: auto;
    width: 200px;
    background-color: pink;
}

.highlights{
    color:red;
    font-weight: bolder;
}

본문은 대략 아래처럼 너무 길어서 p에 패딩을 주고 아래에 h가 하나인데 여러개 넣기를...

let context=`
<H1>블락1</H1>
<p>아아아아아앙아아아아</p>
<p>아아아아아앙아아아아</p>`

 

일단 스크롤 이벤트 생성

//로드되면 문서를 넣어야지
window.addEventListener('load',()=>{
    document.querySelector('#viewDiv').innerHTML=context;
})

//스크롤이 일어 날때
window.addEventListener('scroll', e=>{})

여기까지 하면 이런게 됨

이제 load 이벤트를 수정을 하자

load될때 본문을 viewDiv에 넣기 전에 

H태그 내용을 메뉴로 만들고 ID는 같이 동일하게 만들자


//메뉴 자리를 가져오고
let menuDiv = document.querySelector('#menuDiv')
//본문의 자리를 가져오고
let viewDiv = document.querySelector('#viewDiv')

window.addEventListener('load',()=>{
    //본문공간에 본문을 넣고
    viewDiv.innerHTML=context;

    //본문에 H태그를 가져와서
    let viewDivHtage = viewDiv.querySelectorAll('h1,h2,h3');
    
    viewDivHtage.forEach((item,index)=>{
        //새로운 div를 만들고
        let addMenuSub = document.createElement('div');
        //내용과 ID는 H태그안의 내용으로 넣고 H태그의 ID도 동일하게 주자
        //지금은 그냥 하는데 공백인 경우 특문 처리 작업이 필요하다.
        addMenuSub.innerHTML = item.innerText;
        addMenuSub.setAttribute('id',item.innerText);
        item.setAttribute('id',item.innerText);
        //h1MenuList에 push하자
        h1MenuList.push();
        //index가 0이면 첫번째 항목이니 메뉴에 하이라이트 효과를 주자 
        if(index===0) addMenuSub.classList.add('highlights') ;
        //메뉴를 만들자
        menuDiv.appendChild(addMenuSub);
    })
    
})

이렇게 되면 메뉴가 만들어지고 첫항목은 빨간맛으로 하이라이트 

그리고 H1태그와 메뉴는 같은 ID값을 가진다.

 

이제 스크롤 시 어떻게 할것 인가... 흠....

스크롤이 움직일때마다 이벤트가 발생하기 때문에 코드가 괴랄하게 하면 

분명 버버벅 거릴꺼같다.. 아....

 

window.addEventListener('scroll', e=>{
    //화면의 높이를 가져와서 3등분 한다.
    //2등분해도 되고 항목이 3등분 지점안으로 올때 하이라이트를 줄려고 계산
    let windowHeight = (window.innerHeight/3).toFixed(0);
    //본문에 다시 h태그를 가져온다.
    let viewDivHtage = viewDiv.querySelectorAll('h1,h2,h3');
    viewDivHtage.forEach((item)=>{
        //h태그 객체가 화면의 상단에서 얼마큼 떨어져 있는지 가져와서
        let myPosition=item.getBoundingClientRect().top;
        //그 거리가 -30에서 이전 화면의 높이 3등분 한 값보다 작으면 
        //함수실행 화면 상단에서 3분지1 지점 안으로 들어오면 작동하게끔
        if (myPosition>-30 && myPosition < windowHeight){
            togglCss(item.id);
        }
    })
})

아래는 하이라이트 css를 토글 하는 소스

function togglCss(targetId){
    //class name이 highlights인걸 다 가져와서 
    let highlights = document.querySelectorAll('.highlights');
    highlights.forEach((item)=>{
        //있으면 highlights를 없에버린다.
        item.classList.remove('highlights');
    })
    //받아온 아이디값으로 메뉴에서 해당 아이디를 가진 객체에 highlights를 넣어준다.
    document.querySelector('#menuDiv #'+targetId).classList.add('highlights');
}

 

허접하다... 위치 지정이나 객체 지정 부분을 더 깔끔하게 하면 될듯 하다.

이걸로 객체가 화면에 보일때 다른 이벤트를 발생하게끔 하던가 변화를 주면 좋을것 같다.

이상 끝~

이 아니다!

메뉴를 누르면 역으로 이동을 해야 하는것을 깜빡했다...

추가 끝

 

window.addEventListener('load',()=>{
    //본문공간에 본문을 넣고
    viewDiv.innerHTML=context;

    //본문에 H1 태그를 가져와서
    let viewDivHtage = viewDiv.querySelectorAll('h1,h2,h3');
    
    viewDivHtage.forEach((item,index)=>{
        //새로운 div를 만들고
        let addMenuSub = document.createElement('div');
        //내용과 ID는 H태그안의 내용으로 넣고 H태그의 ID도 동일하게 주자
        //지금은 그냥 하는데 공백인 경우 특문 처리 작업이 필요하다.
        addMenuSub.innerHTML = item.innerText;
        addMenuSub.setAttribute('id',item.innerText);
        item.setAttribute('id',item.innerText);
        //index가 0이면 첫번째 항목이니 메뉴에 하이라이트 효과를 주자 
        if(index===0) addMenuSub.classList.add('highlights') ;
        //역으로 메뉴를 누르면 스크롤이 이동되도록
        addMenuSub.addEventListener('click',(e)=>{
            //매칭되는 본문 h값 가져 온다.
            let id = viewDiv.querySelector('#' + e.currentTarget.id);
            //해당 객체의 위치를 가져온다.
            let scrollPosition = id.offsetTop-30;
            //이동시킨다.
            window.scrollTo({top:scrollPosition, left:0, behavior:'smooth'});
        })
        //메뉴를 만들자
        menuDiv.appendChild(addMenuSub);
    })
    
})

 

깃에 올리고~

 

해당 소스의 위치는 아래 깃 주소 참고

https://dittybox.github.io/study-javascript/scroll-and-menu/

 

https://dittybox.github.io/study-javascript/scroll-and-menu/

 

dittybox.github.io

github.com/dittyBox/study-javascript/tree/main/scroll-and-menu

 

dittyBox/study-javascript

Contribute to dittyBox/study-javascript development by creating an account on GitHub.

github.com