프로젝트/블로그 사이트

3. 스토리 페이지 구현하기

syleemomo 2023. 7. 8. 20:33
728x90

 

스토리 페이지 뼈대코드 구성하기

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Sunrise 블로그 - 스토리</title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
  <link href="https://fonts.googleapis.com/css?family=Material+Icons|Material+Icons+Outlined|Material+Icons+Two+Tone|Material+Icons+Round|Material+Icons+Sharp" rel="stylesheet">
  <link rel="stylesheet" href="../css/story.css">
</head>
<body>
  <script src="../js/scroll.js"></script>
  <script src="../js/story.js"></script>
</body>
</html>

html 폴더에 story.html 파일을 생성하고 위와 같이 작성한다. 폰트아우썸 아이콘도 좋지만 이번 시간에는 구글 머터리얼 아이콘도 함께 사용한다. 

<link href="https://fonts.googleapis.com/css?family=Material+Icons|Material+Icons+Outlined|Material+Icons+Two+Tone|Material+Icons+Round|Material+Icons+Sharp" rel="stylesheet">

구글 머터리얼 아이콘은 CDN 형태로 위와 같이 추가하면 사용이 가능하다.

 

헤더 디자인하기 

헤더에는 스토리(블로그)를 검색하기 위한 검색창이 위치한다. 또한, 사용자 계정을 생성하기 위한 시작하기 버튼도 있다. 

헤더

<!-- 헤더 -->
  <header>
    <a href="#" class="logo"><i class="fa-brands fa-blogger"></i>Sunblog</a>
    
    <div class="menu">
      <nav class="navbar">
        <ul>
          <li>
            <div class="search-container">
              <input type="text" class="keyword" placeholder="글을 검색하려면 검색어를 입력하세요!"/>
              <button class="search">
                <span class="material-icons">search</span>
              </button>
            </div>
          </li>
          <li><a href="#"><button>시작하기</button></a></li>
        </ul>
      </nav>
      
      <div class="mode">
        <i class="fa-solid fa-toggle-on"></i>
        <i class="fa-solid fa-toggle-off active"></i>
      </div>
    </div>
  </header>

스토리 페이지의 헤더는 랜딩페이지 헤더와 유사하다. 다만, 네비게이션 메뉴 대신에 검색창과 시작하기 버튼이 위치한다. 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Sunrise 블로그 - 스토리</title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
  <link href="https://fonts.googleapis.com/css?family=Material+Icons|Material+Icons+Outlined|Material+Icons+Two+Tone|Material+Icons+Round|Material+Icons+Sharp" rel="stylesheet">
  <link rel="stylesheet" href="../css/story.css">
</head>
<body>
  
  <!-- 헤더 -->
  <header>
    <a href="#" class="logo"><i class="fa-brands fa-blogger"></i>Sunblog</a>
    
    <div class="menu">
      <nav class="navbar">
        <ul>
          <li>
            <div class="search-container">
              <input type="text" class="keyword" placeholder="글을 검색하려면 검색어를 입력하세요!"/>
              <button class="search">
                <span class="material-icons">search</span>
              </button>
            </div>
          </li>
          <li><a href="#"><button>시작하기</button></a></li>
        </ul>
      </nav>
      
      <div class="mode">
        <i class="fa-solid fa-toggle-on"></i>
        <i class="fa-solid fa-toggle-off active"></i>
      </div>
    </div>
  </header>


  <script src="../js/scroll.js"></script>
  <script src="../js/story.js"></script>
</body>
</html>

현재까지의 story.html 파일은 위와 같다. 

@import url('reset.css');
@import url('global.css');
@import url('header.css');
@import url('footer.css');

css 폴더에 story.css 파일을 생성하고 위와 같이 작성한다. 미리 만들어둔 헤더/푸터 스타일을 재사용한다. animation.css 의 left, right 클래스는 블로그의 left, right 클래스와 충돌하기 때문에 사용하지 않도록 한다. 

/* 시작하기 버튼 */
header .navbar ul li a button{
  width: 5rem;
  height: 2rem;
  background: var(--primary-color);
  color: #fff;
  border: none;
  border-radius: 2rem;
  cursor: pointer;
  font-size: .9rem;
  transition: .2s;
}
header .navbar ul li a button:hover{
  background: var(--secondary-color);
  letter-spacing: .1rem;
}

시작하기 버튼을 디자인한다.

/* 검색창 */
header .navbar ul li .search-container{
  display: flex;
}
header .navbar ul li .search-container .search{
  background: var(--primary-color);
  color: #fff;
  border: none;
  cursor: pointer;
}
header .navbar ul li .search-container .search:hover{
  background: var(--secondary-color);
}
header .navbar ul li .search-container .search span{
  font-size: 1.5rem;
}
header .navbar ul li .search-container .keyword{
  width: 20rem;
  padding-left: .5rem;
  border: 2px solid var(--primary-color);
  outline: none;
}
header .navbar ul li .search-container .keyword::placeholder{
  color: var(--primary-color);
}
header .navbar ul li .search-container .keyword:focus{
  color: var(--secondary-color);
}

검색창을 디자인한다. 

@import url('reset.css');
@import url('global.css');
@import url('header.css');
@import url('footer.css');

/* 시작하기 버튼 */
header .navbar ul li a button{
  width: 5rem;
  height: 2rem;
  background: var(--primary-color);
  color: #fff;
  border: none;
  border-radius: 2rem;
  cursor: pointer;
  font-size: .9rem;
  transition: .2s;
}
header .navbar ul li a button:hover{
  background: var(--secondary-color);
  letter-spacing: .1rem;
}
/* 검색창 */
header .navbar ul li .search-container{
  display: flex;
}
header .navbar ul li .search-container .search{
  background: var(--primary-color);
  color: #fff;
  border: none;
  cursor: pointer;
}
header .navbar ul li .search-container .search:hover{
  background: var(--secondary-color);
}
header .navbar ul li .search-container .search span{
  font-size: 1.5rem;
}
header .navbar ul li .search-container .keyword{
  width: 20rem;
  padding-left: .5rem;
  border: 2px solid var(--primary-color);
  outline: none;
}
header .navbar ul li .search-container .keyword::placeholder{
  color: var(--primary-color);
}
header .navbar ul li .search-container .keyword:focus{
  color: var(--secondary-color);
}

현재까지의 story.css 파일은 위와 같다. 

 

카테고리 섹션 디자인하기

카테고리 섹션

<!-- 카테고리 섹션 -->
  <section class="category-container">
    <ul>
      <li><a href="#"><button>맛집</button></a></li>
      <li><a href="#"><button>라이프</button></a></li>
      <li><a href="#"><button>IT</button></a></li>
      <li><a href="#"><button>연애</button></a></li>
      <li><a href="#"><button>시사</button></a></li>
    </ul>
  </section>

story.html 파일에 위 코드를 추가한다. 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Sunrise 블로그 - 스토리</title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
  <link href="https://fonts.googleapis.com/css?family=Material+Icons|Material+Icons+Outlined|Material+Icons+Two+Tone|Material+Icons+Round|Material+Icons+Sharp" rel="stylesheet">
  <link rel="stylesheet" href="../css/story.css">
</head>
<body>

  <!-- 헤더 -->
  <header>
    <a href="#" class="logo"><i class="fa-brands fa-blogger"></i>Sunblog</a>
    
    <div class="menu">
      <nav class="navbar">
        <ul>
          <li>
            <div class="search-container">
              <input type="text" class="keyword" placeholder="글을 검색하려면 검색어를 입력하세요!"/>
              <button class="search">
                <span class="material-icons">search</span>
              </button>
            </div>
          </li>
          <li><a href="#"><button>시작하기</button></a></li>
        </ul>
      </nav>
      
      <div class="mode">
        <i class="fa-solid fa-toggle-on"></i>
        <i class="fa-solid fa-toggle-off active"></i>
      </div>
    </div>
  </header>

  <!-- 카테고리 섹션 -->
  <section class="category-container">
    <ul>
      <li><a href="#"><button>맛집</button></a></li>
      <li><a href="#"><button>라이프</button></a></li>
      <li><a href="#"><button>IT</button></a></li>
      <li><a href="#"><button>연애</button></a></li>
      <li><a href="#"><button>시사</button></a></li>
    </ul>
  </section>


  <script src="../js/scroll.js"></script>
  <script src="../js/story.js"></script>
</body>
</html>

현재까지의 story.html 파일은 위와 같다. 

/* 카테고리 섹션 */
.category-container.dark{ 
  color: #fff;
  background: #333;
}
.category-container{
  width: 100vw;  
  background: #fff;
  position: sticky; top: var(--header-height);
  z-index: 1;
  padding: 1rem 2rem;

}
.category-container ul{
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
}
.category-container ul li{
  margin: .2rem .5rem;
}
.category-container ul li button{
  /* width: 5rem; */ /* 글자수에 따라 너비가 유동적으로 변하도록*/
  height: 2rem;
  background: #333; 
  color: #fff;
  border: none;
  border-radius: 2rem;
  cursor: pointer;
  font-size: 1rem;
  font-weight: 700;
  padding: 0rem 1rem;
  box-shadow: 0 0 .3rem rgba(0, 0, 0, .3);
  transition: .2s;
}
.category-container ul li button:hover{
  background: var(--secondary-color);
  letter-spacing: .2rem;
}

story.css 파일에 위 코드를 추가한다. 무한스크롤을 통해 블로그 목록이 브라우저 화면을 벗어나더라도 카테고리 섹션은 계속 보여야 한다. 왜냐하면 사용자가 다른 카테고리를 보고 싶을수도 있기 때문이다. 그래서 카테고리 섹션에 position: sticky 와 top: var(--header-height) 을 설정해서 헤더 하단에 고정한다. 카테고리 섹션에 dark 클래스가 추가되면 다크테마가 적용된다. 카테고리 섹션의 버튼은 width 를 따로 지정하지 않아서 글자수에 따라 너비가 유동적으로 변하도록 한다. 

@import url('reset.css');
@import url('global.css');
@import url('header.css');
@import url('footer.css');

/* 시작하기 버튼 */
header .navbar ul li a button{
  width: 5rem;
  height: 2rem;
  background: var(--primary-color);
  color: #fff;
  border: none;
  border-radius: 2rem;
  cursor: pointer;
  font-size: .9rem;
  transition: .2s;
}
header .navbar ul li a button:hover{
  background: var(--secondary-color);
  letter-spacing: .1rem;
}
/* 검색창 */
header .navbar ul li .search-container{
  display: flex;
}
header .navbar ul li .search-container .search{
  background: var(--primary-color);
  color: #fff;
  border: none;
  cursor: pointer;
}
header .navbar ul li .search-container .search:hover{
  background: var(--secondary-color);
}
header .navbar ul li .search-container .search span{
  font-size: 1.5rem;
}
header .navbar ul li .search-container .keyword{
  width: 20rem;
  padding-left: .5rem;
  border: 2px solid var(--primary-color);
  outline: none;
}
header .navbar ul li .search-container .keyword::placeholder{
  color: var(--primary-color);
}
header .navbar ul li .search-container .keyword:focus{
  color: var(--secondary-color);
}

/* 카테고리 섹션 */
.category-container.dark{ 
  color: #fff;
  background: #333;
}
.category-container{
  width: 100vw;  
  background: #fff;
  position: sticky; top: var(--header-height);
  z-index: 1;
  padding: 1rem 2rem;

}
.category-container ul{
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
}
.category-container ul li{
  margin: .2rem .5rem;
}
.category-container ul li button{
  /* width: 5rem; */ /* 글자수에 따라 너비가 유동적으로 변하도록*/
  height: 2rem;
  background: #333; 
  color: #fff;
  border: none;
  border-radius: 2rem;
  cursor: pointer;
  font-size: 1rem;
  font-weight: 700;
  padding: 0rem 1rem;
  box-shadow: 0 0 .3rem rgba(0, 0, 0, .3);
  transition: .2s;
}
.category-container ul li button:hover{
  background: var(--secondary-color);
  letter-spacing: .2rem;
}

현재까지 story.css 파일은 위와 같다. 

 

푸터 디자인하기

푸터

<!-- 푸터 -->
  <section class="footer">
    <div class="icons">
      <a href="https://ko-kr.facebook.com/" class="fab fa-facebook-f"></a>
      <a href="https://twitter.com/?lang=ko" class="fab fa-twitter"></a>
      <a href="https://www.instagram.com/" class="fab fa-instagram"></a>
      <a href="https://github.com/" class="fab fa-github"></a>
      <a href="https://www.pinterest.co.kr/" class="fab fa-pinterest"></a>
      <div class="scroll-up"><i class="fa-solid fa-circle-up"></i></div>
    </div>
    <div class="credit"><i class="fa-brands fa-blogger"></i>Sunrise. All rights reserved </div>
  </section>

story.html 파일에 위 코드를 추가한다. 푸터는 재사용하므로 복사붙여넣기 한다. 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Sunrise 블로그 - 스토리</title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
  <link href="https://fonts.googleapis.com/css?family=Material+Icons|Material+Icons+Outlined|Material+Icons+Two+Tone|Material+Icons+Round|Material+Icons+Sharp" rel="stylesheet">
  <link rel="stylesheet" href="../css/story.css">
</head>
<body>

  <!-- 헤더 -->
  <header>
    <a href="#" class="logo"><i class="fa-brands fa-blogger"></i>Sunblog</a>
    
    <div class="menu">
      <nav class="navbar">
        <ul>
          <li>
            <div class="search-container">
              <input type="text" class="keyword" placeholder="글을 검색하려면 검색어를 입력하세요!"/>
              <button class="search">
                <span class="material-icons">search</span>
              </button>
            </div>
          </li>
          <li><a href="#"><button>시작하기</button></a></li>
        </ul>
      </nav>
      
      <div class="mode">
        <i class="fa-solid fa-toggle-on"></i>
        <i class="fa-solid fa-toggle-off active"></i>
      </div>
    </div>
  </header>

  <!-- 카테고리 섹션 -->
  <section class="category-container">
    <ul>
      <li><a href="#"><button>맛집</button></a></li>
      <li><a href="#"><button>라이프</button></a></li>
      <li><a href="#"><button>IT</button></a></li>
      <li><a href="#"><button>연애</button></a></li>
      <li><a href="#"><button>시사</button></a></li>
    </ul>
  </section>



  <!-- 푸터 -->
  <section class="footer">
    <div class="icons">
      <a href="https://ko-kr.facebook.com/" class="fab fa-facebook-f"></a>
      <a href="https://twitter.com/?lang=ko" class="fab fa-twitter"></a>
      <a href="https://www.instagram.com/" class="fab fa-instagram"></a>
      <a href="https://github.com/" class="fab fa-github"></a>
      <a href="https://www.pinterest.co.kr/" class="fab fa-pinterest"></a>
      <div class="scroll-up"><i class="fa-solid fa-circle-up"></i></div>
    </div>
    <div class="credit"><i class="fa-brands fa-blogger"></i>Sunrise. All rights reserved </div>
  </section>

  <script src="../js/scroll.js"></script>
  <script src="../js/story.js"></script>
</body>
</html>

현재까지의 story.html 파일은 위와 같다. 

/* 푸터 */
.footer{
  position: fixed;
  z-index: 1;
  bottom: 0;
  opacity: 0;
}
.footer:hover{
  opacity: 1;
}

스토리 페이지의 푸터는 하단에 고정한다. 이유는 수많은 블로그 글 목록을 보여주기 위하여 무한스크롤을 적용할 예정인데 푸터를 고정해서 위쪽 화살표를 이용하여 브라우저 상단으로 이동하도록 하기 위함이다. 또한, 푸터는 화면을 많이 차지하기 때문에 블로그 목록을 제대로 보지 못한다. 그래서 마우스를 올린 경우에만 나타나도록 한다. 푸터 스타일은 재사용함으로써 코드가 간결해졌다. 

 

블로그 목록 디자인하기

블로그 섹션

<!-- 블로그 목록 섹션 -->
  <section class="blog-container">
    <div class="blog">
      <div class="left">
        <ul>
          <li class="category-name"><a href="#">여행</a></li>
          <li class="posting-time">1시간전</li>
          <li><a href="#" class="likes">공감</a><span>9</span></li>
        </ul>
      </div>
      <div class="middle">
        <ul>
          <li><h3>제주도 오른 까페 방문하기</h3></li>
          <li><p>성산의 해안도로를 따라 달리다 보면 보이는 오른 카페는 제주도의 자연요소 중 하나인 오름을 모티브로 한 카페의 콘셉트인 만큼 자연과 함께 커피를 마시며 즐길 수 있었던 것 같아요.😋</p></li>
          <li>
           <ul>
             <li>
               <div class="account">
                 <img src="../imgs/avatar.jpg" alt="">
                 촌부 <span>by 농돌이</span>
               </div>
             </li>
             <li><button>구독하기</button></li>
           </ul>
          </li>
        </ul>
      </div>
      <div class="right">
        <ul>
          <li>
           <img src="../imgs/waterfall.jpg" alt="blog-thumbnail">
          </li>
        </ul>
      </div>
    </div>
  </section>

story.html 파일에 위 코드를 추가한다. 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Sunrise 블로그 - 스토리</title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
  <link href="https://fonts.googleapis.com/css?family=Material+Icons|Material+Icons+Outlined|Material+Icons+Two+Tone|Material+Icons+Round|Material+Icons+Sharp" rel="stylesheet">
  <link rel="stylesheet" href="../css/story.css">
</head>
<body>

  <!-- 헤더 -->
  <header>
    <a href="#" class="logo"><i class="fa-brands fa-blogger"></i>Sunblog</a>
    
    <div class="menu">
      <nav class="navbar">
        <ul>
          <li>
            <div class="search-container">
              <input type="text" class="keyword" placeholder="글을 검색하려면 검색어를 입력하세요!"/>
              <button class="search">
                <span class="material-icons">search</span>
              </button>
            </div>
          </li>
          <li><a href="#"><button>시작하기</button></a></li>
        </ul>
      </nav>
      
      <div class="mode">
        <i class="fa-solid fa-toggle-on"></i>
        <i class="fa-solid fa-toggle-off active"></i>
      </div>
    </div>
  </header>

  <!-- 카테고리 섹션 -->
  <section class="category-container">
    <ul>
      <li><a href="#"><button>맛집</button></a></li>
      <li><a href="#"><button>라이프</button></a></li>
      <li><a href="#"><button>IT</button></a></li>
      <li><a href="#"><button>연애</button></a></li>
      <li><a href="#"><button>시사</button></a></li>
    </ul>
  </section>

  <!-- 블로그 목록 섹션 -->
  <section class="blog-container">
    <div class="blog">
      <div class="left">
        <ul>
          <li class="category-name"><a href="#">여행</a></li>
          <li class="posting-time">1시간전</li>
          <li><a href="#" class="likes">공감</a><span>9</span></li>
        </ul>
      </div>
      <div class="middle">
        <ul>
          <li><h3>제주도 오른 까페 방문하기</h3></li>
          <li><p>성산의 해안도로를 따라 달리다 보면 보이는 오른 카페는 제주도의 자연요소 중 하나인 오름을 모티브로 한 카페의 콘셉트인 만큼 자연과 함께 커피를 마시며 즐길 수 있었던 것 같아요.😋</p></li>
          <li>
           <ul>
             <li>
               <div class="account">
                 <img src="../imgs/avatar.jpg" alt="">
                 촌부 <span>by 농돌이</span>
               </div>
             </li>
             <li><button>구독하기</button></li>
           </ul>
          </li>
        </ul>
      </div>
      <div class="right">
        <ul>
          <li>
           <img src="../imgs/waterfall.jpg" alt="blog-thumbnail">
          </li>
        </ul>
      </div>
    </div>
  </section>



  <!-- 푸터 -->
  <section class="footer">
    <div class="icons">
      <a href="https://ko-kr.facebook.com/" class="fab fa-facebook-f"></a>
      <a href="https://twitter.com/?lang=ko" class="fab fa-twitter"></a>
      <a href="https://www.instagram.com/" class="fab fa-instagram"></a>
      <a href="https://github.com/" class="fab fa-github"></a>
      <a href="https://www.pinterest.co.kr/" class="fab fa-pinterest"></a>
      <div class="scroll-up"><i class="fa-solid fa-circle-up"></i></div>
    </div>
    <div class="credit"><i class="fa-brands fa-blogger"></i>Sunrise. All rights reserved </div>
  </section>

  <script src="../js/scroll.js"></script>
  <script src="../js/story.js"></script>
</body>
</html>

현재까지의 story.html 파일은 위와 같다. 

 

이미지가 너무 크기 때문에 레이아웃을 잡을때는 잠시 html 문서의 img 요소는 주석처리해놓고 작업하기로 하자!

/* 블로그 목록 섹션 */
.blog-container{
  width: 50vw;
  margin: 0 auto;
}
.blog-container .blog{
  display: flex;
  padding: 2rem 1rem;
  border-bottom: 1px solid #eee;
}
.blog-container .blog > div > ul{
  display: flex;
  flex-flow: column;
  height: 100%;
}
.blog-container .blog .left, 
.blog-container .blog .right{
  width: 150px;
  min-width: 150px;
}

story.css 파일에 위 코드를 추가한다. 블로그 목록 섹션의 너비는 브라우저의 절반으로 하고, 수평중앙에 정렬한다. left, middle, right 으로 구분하여 수평으로 나열하고, left 와 right 의 너비는 고정폭으로 정한다. left, middle, right 내부의 요소들은 flex-flow: column 을 적용하여 세로방향으로 나열한다. 

.blog-container .blog .left .posting-time{
  margin-top: 1rem;
  margin-bottom: .3rem;
  color: #aaa;
}
.blog-container .blog .left .category-name a{
  color: var(--primary-color);
}
.blog-container .blog .left .category-name a:hover{
  color: var(--secondary-color);
  text-decoration: underline;
}
.blog-container .blog .left .likes{
  margin-right: .5rem;
}
.blog-container .blog .left span{
  color: var(--primary-color);
}

story.css 파일에 위 코드를 추가한다. 블로그 아이템의 좌측에 위치한 컨텐츠이다. 

.blog-container .blog .middle{
  flex-grow: 1;
  padding: 0rem 1.5rem;
}
.blog-container .blog .middle > ul h3{
  font-weight: 700;
  font-size: 1.5rem;
}
.blog-container .blog .middle > ul p{
  padding: 1rem 0;
  line-height: 1.3rem;
  font-size: 1rem;
  color: gray;
}
.blog-container .blog .middle .account{
  display: flex;
  align-items: center;
}
.blog-container .blog .middle .account img{
  width: 2rem;
  height: 2rem;
  margin-right: .5rem;
  border-radius: 50%;
  cursor: pointer;
  object-fit: cover;
}
.blog-container .blog .middle .account span{
  font-size: .9rem;
  color: #bbb;
  margin-left: .5rem;
}
.blog-container .blog .middle > ul ul{
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.blog-container .blog .middle > ul ul button{
  width: 5rem;
  height: 2rem;
  color: #333;
  cursor: pointer;
  border: 2px solid #eee;
  border-radius: 1rem;
  background: #fff;
  transform: .2s;
}
.blog-container .blog .middle > ul ul button:hover{
  box-shadow: 0 0 .1rem rgba(0, 0, 0, .3);
}

story.css 파일에 위 코드를 추가한다. 블로그 아이템의 중앙에 위치한 컨텐츠이다. middle 요소는 left, right 의 고정폭 150px 을 제외한 나머지 공간을 전부 차지하도록 flex-grow: 1을 적용한다. 이미지는 object-fit: cover 로 설정해서 이미지 컨테이너를 채울수 있도록 한다. 계정(account) 와 구독하기 버튼은 justify-content: space-between 을 주어 좌우측에 각각 정렬한다. 

.blog-container .blog .right ul,
.blog-container .blog .right ul li{
  height: 100%;
}
.blog-container .blog .right img{
  height: 100%;
  width: 100%;
  object-fit: cover;
  object-position: center;
}

story.css 파일에 위 코드를 추가한다. 블로그 아이템의 우측에 위치한 컨텐츠이다. 이제 html 문서의 img 요소는 주석을 해제하자!

@import url('reset.css');
@import url('global.css');
@import url('header.css');
@import url('footer.css');

/* 시작하기 버튼 */
header .navbar ul li a button{
  width: 5rem;
  height: 2rem;
  background: var(--primary-color);
  color: #fff;
  border: none;
  border-radius: 2rem;
  cursor: pointer;
  font-size: .9rem;
  transition: .2s;
}
header .navbar ul li a button:hover{
  background: var(--secondary-color);
  letter-spacing: .1rem;
}
/* 검색창 */
header .navbar ul li .search-container{
  display: flex;
}
header .navbar ul li .search-container .search{
  background: var(--primary-color);
  color: #fff;
  border: none;
  cursor: pointer;
}
header .navbar ul li .search-container .search:hover{
  background: var(--secondary-color);
}
header .navbar ul li .search-container .search span{
  font-size: 1.5rem;
}
header .navbar ul li .search-container .keyword{
  width: 20rem;
  padding-left: .5rem;
  border: 2px solid var(--primary-color);
  outline: none;
}
header .navbar ul li .search-container .keyword::placeholder{
  color: var(--primary-color);
}
header .navbar ul li .search-container .keyword:focus{
  color: var(--secondary-color);
}

/* 카테고리 섹션 */
.category-container.dark{ 
  color: #fff;
  background: #333;
}
.category-container{
  width: 100vw;  
  background: #fff;
  position: sticky; top: var(--header-height);
  z-index: 1;
  padding: 1rem 2rem;

}
.category-container ul{
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
}
.category-container ul li{
  margin: .2rem .5rem;
}
.category-container ul li button{
  /* width: 5rem; */ /* 글자수에 따라 너비가 유동적으로 변하도록*/
  height: 2rem;
  background: #333; 
  color: #fff;
  border: none;
  border-radius: 2rem;
  cursor: pointer;
  font-size: 1rem;
  font-weight: 700;
  padding: 0rem 1rem;
  box-shadow: 0 0 .3rem rgba(0, 0, 0, .3);
  transition: .2s;
}
.category-container ul li button:hover{
  background: var(--secondary-color);
  letter-spacing: .2rem;
}
/* 푸터 */
.footer{
  position: fixed;
  z-index: 1;
  bottom: 0;
  opacity: 0;
}
.footer:hover{
  opacity: 1;
}

/* 블로그 목록 섹션 */
.blog-container{
  width: 50vw;
  margin: 0 auto;
}
.blog-container .blog{
  display: flex;
  padding: 2rem 1rem;
  border-bottom: 1px solid #eee;
}
.blog-container .blog > div > ul{
  display: flex;
  flex-flow: column;
  height: 100%;
}
.blog-container .blog .left, 
.blog-container .blog .right{
  width: 150px;
  min-width: 150px;
}
.blog-container .blog .left .posting-time{
  margin-top: 1rem;
  margin-bottom: .3rem;
  color: #aaa;
}
.blog-container .blog .left .category-name a{
  color: var(--primary-color);
}
.blog-container .blog .left .category-name a:hover{
  color: var(--secondary-color);
  text-decoration: underline;
}
.blog-container .blog .left .likes{
  margin-right: .5rem;
}
.blog-container .blog .left span{
  color: var(--primary-color);
}
.blog-container .blog .middle{
  flex-grow: 1;
  padding: 0rem 1.5rem;
}
.blog-container .blog .middle > ul h3{
  font-weight: 700;
  font-size: 1.5rem;
}
.blog-container .blog .middle > ul p{
  padding: 1rem 0;
  line-height: 1.3rem;
  font-size: 1rem;
  color: gray;
}
.blog-container .blog .middle .account{
  display: flex;
  align-items: center;
}
.blog-container .blog .middle .account img{
  width: 2rem;
  height: 2rem;
  margin-right: .5rem;
  border-radius: 50%;
  cursor: pointer;
  object-fit: cover;
}
.blog-container .blog .middle .account span{
  font-size: .9rem;
  color: #bbb;
  margin-left: .5rem;
}
.blog-container .blog .middle > ul ul{
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.blog-container .blog .middle > ul ul button{
  width: 5rem;
  height: 2rem;
  color: #333;
  cursor: pointer;
  border: 2px solid #eee;
  border-radius: 1rem;
  background: #fff;
  transform: .2s;
}
.blog-container .blog .middle > ul ul button:hover{
  box-shadow: 0 0 .1rem rgba(0, 0, 0, .3);
}
.blog-container .blog .right ul,
.blog-container .blog .right ul li{
  height: 100%;
}
.blog-container .blog .right img{
  height: 100%;
  width: 100%;
  object-fit: cover;
  object-position: center;
}

현재까지의 story.css 파일은 위와 같다. 

 

반응형 웹

가끔씩 개발자 도구에서 반응형 웹 테스트를 할때 제대로 보이지 않을때가 있다. 그럴때는 모바일 아이콘을 다시 클릭하자! 반응형 웹은 태블릿은 무시하고 오로지 모바일 기기만 적용하였다. 또한, 카테고리 섹션의 sticky 는 개발자 도구에서 테스트시 잘 되지 않는 경우도 있다. 

반응형 웹

 

story.css 파일에 아래 코드를 추가한다. 디바이스 크기가 줄어들면 검색창이 너무 많은 공간을 차지하므로 헤더 하단으로 내려보낸다. 헤더와 카테고리 섹션은 position; sticky 이므로 서로의 영역을 침범하지 않는다. 하지만 검색창은 position: fixed 로 설정되어 있어서 다른 요소와 독립적으로 위치한다. 그러면 카테고리 섹션이 검색창 영역을 침범하면서 가려버린다. 이를 해결하고자 카테고리 섹션에 margin-top: 3rem 을 설정하였다. 카테고리 섹션은 position: sticky 이므로 화면에 처음 로딩되면 검색창이 보이다가 스크롤을 내리면 top: var(--header-height) 에 의하여 헤더 하단에 도달할때까지 움직인다. 

/* 반응형웹 */
@media (max-width: 420px){
  header .navbar ul li .search-container{ /* 검색창을 헤더 하단에 고정 */
    position: fixed; top: var(--header-height); 
    left: 0; right: 0;
    margin: 0 auto;
    width: 85%;
  }
  .category-container{
    margin-top: 3rem; /* 검색창 아래쪽에 배치 */
  }
  .blog-container{ /* 블로그 너비 */
    width: 90vw;
  }
  .blog-container .blog{ /* 블로그 컨텐츠 순서 변경 */
    flex-wrap: wrap;
    flex-flow: column-reverse;
    box-shadow: 0 0 .2rem rgba(0, 0, 0, .3);
    margin-bottom: 2.5rem;
    border-bottom: none;
  }
  .blog-container .blog .middle{
    margin: 2rem 0rem;
  }
  .blog-container .blog .left,  /* 이미지를 화면에 가득 채움 */
  .blog-container .blog .right{
    width: 100%;
    min-width: 100%;
  }
  .blog-container .blog .middle{
    padding: 0rem 0rem;
  }
}

블로그 아이템의 left, middle, right 컨텐츠는 flex-flow: column 으로 설정하면 세로방향으로 left, middle, right 으로 나열된다. 하지만 이미지(right)가 화면 위쪽에 보이도록 하고 싶기 때문에 flex-flow: column-reverse 로 설정한다. left, right 의 너비를 화면에 가득 채운다. 

@media (max-width: 300px){
  header{
    justify-content: center;
  }
  header .logo{
    font-size: 1rem;
  }
  header .logo i{
    font-size: 2rem;
  }
  header .mode{
    font-size: 2rem;
  }
  header .navbar ul li a button{
    width: 4rem;
    height: 1.5rem;
    border-radius: 1rem;
    font-size: .5rem;
  }
  header .navbar ul li .search-container{
    width: 90%; top: 66px;
  }
  header .navbar ul li .search-container .keyword::placeholder{
    font-size: .7rem;
  }
}

story.css 파일에 위 코드를 추가한다. 헤더 안에 있는 모든 요소들의 크기와 폰트를 이전보다 작게 변경한다. 

@import url('reset.css');
@import url('global.css');
@import url('header.css');
@import url('footer.css');

/* 시작하기 버튼 */
header .navbar ul li a button{
  width: 5rem;
  height: 2rem;
  background: var(--primary-color);
  color: #fff;
  border: none;
  border-radius: 2rem;
  cursor: pointer;
  font-size: .9rem;
  transition: .2s;
}
header .navbar ul li a button:hover{
  background: var(--secondary-color);
  letter-spacing: .1rem;
}
/* 검색창 */
header .navbar ul li .search-container{
  display: flex;
}
header .navbar ul li .search-container .search{
  background: var(--primary-color);
  color: #fff;
  border: none;
  cursor: pointer;
}
header .navbar ul li .search-container .search:hover{
  background: var(--secondary-color);
}
header .navbar ul li .search-container .search span{
  font-size: 1.5rem;
}
header .navbar ul li .search-container .keyword{
  width: 20rem;
  padding-left: .5rem;
  border: 2px solid var(--primary-color);
  outline: none;
}
header .navbar ul li .search-container .keyword::placeholder{
  color: var(--primary-color);
}
header .navbar ul li .search-container .keyword:focus{
  color: var(--secondary-color);
}

/* 카테고리 섹션 */
.category-container.dark{ 
  color: #fff;
  background: #333;
}
.category-container{
  width: 100vw;  
  background: #fff;
  position: sticky; top: var(--header-height);
  z-index: 1;
  padding: 1rem 2rem;

}
.category-container ul{
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
}
.category-container ul li{
  margin: .2rem .5rem;
}
.category-container ul li button{
  /* width: 5rem; */ /* 글자수에 따라 너비가 유동적으로 변하도록*/
  height: 2rem;
  background: #333; 
  color: #fff;
  border: none;
  border-radius: 2rem;
  cursor: pointer;
  font-size: 1rem;
  font-weight: 700;
  padding: 0rem 1rem;
  box-shadow: 0 0 .3rem rgba(0, 0, 0, .3);
  transition: .2s;
}
.category-container ul li button:hover{
  background: var(--secondary-color);
  letter-spacing: .2rem;
}
/* 푸터 */
.footer{
  position: fixed;
  z-index: 1;
  bottom: 0;
  opacity: 0;
}
.footer:hover{
  opacity: 1;
}

/* 블로그 목록 섹션 */
.blog-container{
  width: 50vw;
  margin: 0 auto;
}
.blog-container .blog{
  display: flex;
  padding: 2rem 1rem;
  border-bottom: 1px solid #eee;
}
.blog-container .blog > div > ul{
  display: flex;
  flex-flow: column;
  height: 100%;
}
.blog-container .blog .left, 
.blog-container .blog .right{
  width: 150px;
  min-width: 150px;
}
.blog-container .blog .left .posting-time{
  margin-top: 1rem;
  margin-bottom: .3rem;
  color: #aaa;
}
.blog-container .blog .left .category-name a{
  color: var(--primary-color);
}
.blog-container .blog .left .category-name a:hover{
  color: var(--secondary-color);
  text-decoration: underline;
}
.blog-container .blog .left .likes{
  margin-right: .5rem;
}
.blog-container .blog .left span{
  color: var(--primary-color);
}
.blog-container .blog .middle{
  flex-grow: 1;
  padding: 0rem 1.5rem;
}
.blog-container .blog .middle > ul h3{
  font-weight: 700;
  font-size: 1.5rem;
}
.blog-container .blog .middle > ul p{
  padding: 1rem 0;
  line-height: 1.3rem;
  font-size: 1rem;
  color: gray;
}
.blog-container .blog .middle .account{
  display: flex;
  align-items: center;
}
.blog-container .blog .middle .account img{
  width: 2rem;
  height: 2rem;
  margin-right: .5rem;
  border-radius: 50%;
  cursor: pointer;
  object-fit: cover;
}
.blog-container .blog .middle .account span{
  font-size: .9rem;
  color: #bbb;
  margin-left: .5rem;
}
.blog-container .blog .middle > ul ul{
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.blog-container .blog .middle > ul ul button{
  width: 5rem;
  height: 2rem;
  color: #333;
  cursor: pointer;
  border: 2px solid #eee;
  border-radius: 1rem;
  background: #fff;
  transform: .2s;
}
.blog-container .blog .middle > ul ul button:hover{
  box-shadow: 0 0 .1rem rgba(0, 0, 0, .3);
}
.blog-container .blog .right ul,
.blog-container .blog .right ul li{
  height: 100%;
}
.blog-container .blog .right img{
  height: 100%;
  width: 100%;
  object-fit: cover;
  object-position: center;
}

/* 반응형웹 */
@media (max-width: 420px){
  header{
    height: var(--header-height);
  }
  header .navbar ul li .search-container{ /* 검색창을 헤더 하단에 고정 */
    position: fixed; top: var(--header-height); 
    left: 0; right: 0;
    margin: 0 auto;
    width: 85%;
  }
  .category-container{
    margin-top: 3rem; /* 검색창 아래쪽에 배치 */
  }
  .blog-container{ /* 블로그 너비 */
    width: 90vw;
  }
  .blog-container .blog{ /* 블로그 컨텐츠 순서 변경 */
    flex-wrap: wrap;
    flex-flow: column-reverse;
    box-shadow: 0 0 .2rem rgba(0, 0, 0, .3);
    margin-bottom: 2.5rem;
    border-bottom: none;
  }
  .blog-container .blog .middle{
    margin: 2rem 0rem;
  }
  .blog-container .blog .left,  /* 이미지를 화면에 가득 채움 */
  .blog-container .blog .right{
    width: 100%;
    min-width: 100%;
  }
  .blog-container .blog .middle{
    padding: 0rem 0rem;
  }
}
@media (max-width: 300px){
  header{
    justify-content: center;
  }
  header .logo{
    font-size: 1rem;
  }
  header .logo i{
    font-size: 2rem;
  }
  header .mode{
    font-size: 2rem;
  }
  header .navbar ul li a button{
    width: 4rem;
    height: 1.5rem;
    border-radius: 1rem;
    font-size: .5rem;
  }
  header .navbar ul li .search-container{
    width: 90%; top: 66px;
  }
  header .navbar ul li .search-container .keyword::placeholder{
    font-size: .7rem;
  }
}

완성된 story.css 파일은 위와 같다. 

 

페이지 모드(다크/일반) 기능 구현하기 & 브라우저 상단으로 스크롤링하기

다크모드

const scroller = new Scroller(false) // 스크롤 객체 생성 

window.addEventListener("load", (event) => {
  // 테마변경 (다크모드/ 일반모드)
  const mode = document.querySelector('.mode')
  const icons = mode.querySelectorAll('.fa-solid')
  const header = document.querySelector('header')
  const categoryContainer =  document.querySelector('.category-container') // 추가된 부분

  mode.addEventListener('click', (event) => {
    document.body.classList.toggle('dark')
    header.classList.toggle('dark')
    categoryContainer.classList.toggle('dark') // 추가된 부분 
    
    for(const icon of icons){
      icon.classList.contains('active') ? 
        icon.classList.remove('active') 
        : icon.classList.add('active')
    }
  })

  // 브라우저 상단으로 스크롤하기
  const arrowUp = document.querySelector('.footer .icons .scroll-up') // 위쪽 화살표 클릭 
  arrowUp.addEventListener('click', (event) => {
    history.pushState({}, "", `#`); // URL 주소 변경 
    scroller.setScrollPosition({top: 0, behavior: 'smooth'})
  })

  const logo = document.querySelector('header .logo') // 로고 클릭 
  logo.addEventListener('click', (event) => {
    event.preventDefault() // 부드러운 스크롤링
    history.pushState({}, "", `#`); // URL 주소 변경 
    scroller.setScrollPosition({top: 0, behavior: 'smooth'}) 
  })
})

js 폴더에 story.js 파일을 생성하고 landing.js 파일의 해당 코드를 복사붙여넣기 한다. 카테고리 섹션에 dark 클래스를 추가하여 다크테마를 적용하는 부분이 추가되었다. 

 

무한스크롤 기능 구현하기 (비주얼스튜디오 확장프로그램  HTML&LESS grammar injections  설치)

 

우리가 만든 사이트는 정적사이트이다. 즉, 자바스크립트에 의하여 HTML을 생성하지 않는다. 하지만 무한스크롤 기능을 구현하기 위하여 잠시 정적사이트를 동적사이트로 리모델링해보자. 우리가 하고 싶은 것은 스크롤바가 브라우저창 하단에 닿으면 서버에서 추가로 블로그 목록의 일부를 가져오는 것이다. 여기서는 서버가 없으므로 자바스크립트를 이용하여 동적으로 블로그 목록을 생성하고 블로그 섹션에 추가한다. 스크롤이 바닥에 닿을때마다 10개의 블로그글을 추가로 가져온다.

// 더미 데이터 생성
function getBlogList(num){
  let blogList = ''
  for (let i = 0; i < num; i++) {
    blogList += `
        <div class="blog">
        <div class="left">
          <ul>
            <li class="category-name"><a href="#">여행</a></li>
            <li class="posting-time">1시간전</li>
            <li><a href="#" class="likes">공감</a><span>9</span></li>
          </ul>
        </div>
        <div class="middle">
          <ul>
            <li><h3>제주도 오른 까페 방문하기</h3></li>
            <li><p>성산의 해안도로를 따라 달리다 보면 보이는 오른 카페는 제주도의 자연요소 중 하나인 오름을 모티브로 한 카페의 콘셉트인 만큼 자연과 함께 커피를 마시며 즐길 수 있었던 것 같아요.😋</p></li>
            <li>
            <ul>
              <li>
                <div class="account">
                  <img src="../imgs/avatar.jpg" alt="">
                  촌부 <span>by 농돌이</span>
                </div>
              </li>
              <li><button>구독하기</button></li>
            </ul>
            </li>
          </ul>
        </div>
        <div class="right">
          <ul>
            <li>
            <img src="../imgs/waterfall.jpg" alt="blog-thumbnail">
            </li>
          </ul>
        </div>
      </div>
    `
  }
  return blogList
}

우선 블로그 글을 더미(가짜)로 생성하기 위하여 getBlogList 라는 함수를 하나 만든다. 키보드 1번키 왼쪽에 백틱을 이용하여 템플릿 리터럴 형태로 블로그 글에 대한 HTML 코드블럭을 가져와서 복사하였다. num 파라미터를 이용하여 유동적으로 생성되는 블로그 목록의 갯수를 설정할 수 있게 하였다. 

const blogContainer = document.querySelector('.blog-container')
blogContainer.innerHTML += getBlogList(10) // 초기에 블로그 10개 추가하기

웹페이지 초기 로딩시 더미 블로그 목록을 생성하고 추가해둔다. 스크롤바가 생기게 하기 위함이다. 

const scroller = new Scroller(false) // 스크롤 객체 생성 

window.addEventListener("load", (event) => {
  // 테마변경 (다크모드/ 일반모드)
  const mode = document.querySelector('.mode')
  const icons = mode.querySelectorAll('.fa-solid')
  const header = document.querySelector('header')
  const categoryContainer =  document.querySelector('.category-container') // 추가된 부분

  mode.addEventListener('click', (event) => {
    document.body.classList.toggle('dark')
    header.classList.toggle('dark')
    categoryContainer.classList.toggle('dark') // 추가된 부분 
    
    for(const icon of icons){
      icon.classList.contains('active') ? 
        icon.classList.remove('active') 
        : icon.classList.add('active')
    }
  })

  // 브라우저 상단으로 스크롤하기
  const arrowUp = document.querySelector('.footer .icons .scroll-up') // 위쪽 화살표 클릭 
  arrowUp.addEventListener('click', (event) => {
    history.pushState({}, "", `#`); // URL 주소 변경 
    scroller.setScrollPosition({top: 0, behavior: 'smooth'})
  })

  const logo = document.querySelector('header .logo') // 로고 클릭 
  logo.addEventListener('click', (event) => {
    event.preventDefault() // 부드러운 스크롤링
    history.pushState({}, "", `#`); // URL 주소 변경 
    scroller.setScrollPosition({top: 0, behavior: 'smooth'}) 
  })

  const blogContainer = document.querySelector('.blog-container')
  blogContainer.innerHTML += getBlogList(10) // 초기에 블로그 10개 추가하기

  
})


// 더미 데이터 생성
function getBlogList(num){
  let blogList = ''
  for (let i = 0; i < num; i++) {
    blogList += `
        <div class="blog">
        <div class="left">
          <ul>
            <li class="category-name"><a href="#">여행</a></li>
            <li class="posting-time">1시간전</li>
            <li><a href="#" class="likes">공감</a><span>9</span></li>
          </ul>
        </div>
        <div class="middle">
          <ul>
            <li><h3>제주도 오른 까페 방문하기</h3></li>
            <li><p>성산의 해안도로를 따라 달리다 보면 보이는 오른 카페는 제주도의 자연요소 중 하나인 오름을 모티브로 한 카페의 콘셉트인 만큼 자연과 함께 커피를 마시며 즐길 수 있었던 것 같아요.😋</p></li>
            <li>
            <ul>
              <li>
                <div class="account">
                  <img src="../imgs/avatar.jpg" alt="">
                  촌부 <span>by 농돌이</span>
                </div>
              </li>
              <li><button>구독하기</button></li>
            </ul>
            </li>
          </ul>
        </div>
        <div class="right">
          <ul>
            <li>
            <img src="../imgs/waterfall.jpg" alt="blog-thumbnail">
            </li>
          </ul>
        </div>
      </div>
    `
  }
  return blogList
}

현재까지의 story.js 파일은 위와 같다. 

window.addEventListener('scroll', (event) => {
	
    // 무한스크롤 구현 
    // 브라우저창 높이 : document.documentElement.clientHeight
    // 문서 상단 높이 : window.pageYOffset
    
    const scrollHeight = Math.max(   // 전체문서 높이 (스크롤이벤트 내부에 있어야 함)
    document.body.scrollHeight, document.documentElement.scrollHeight,
    document.body.offsetHeight, document.documentElement.offsetHeight,
    document.body.clientHeight, document.documentElement.clientHeight
    );
    
    // 스크롤을 브라우저창 아래까지 다 내린경우
    if(Math.abs(scroller.getScrollPosition() + document.documentElement.clientHeight - scrollHeight) < 100){ 
      console.log('scroll is bottom of browser!')
      blogContainer.innerHTML += getBlogList(10) // 블로그글 10개씩 추가하기
    }
})

story.js 파일에 위 코드를 추가한다. 조건문을 사용하여 스크롤바가 브라우저 하단에 닿았는지 검사한다. 스크롤바를 내리면 스크롤 위치(window.pageYOffset)가 증가한다. 즉, 문서가 브라우저 창 위로 올라간다. 만약 스크롤바가 브라우저 하단에 닿으면 브라우저 창 위쪽에 보이지 않는 문서 일부(window.pageYOffset)와 브라우저 높이를 더했을때 문서의 전체높이와 동일하다. 

Math 모듈의 abs 메서드를 이용하여 HTML 문서 높이와 약 100px 정도 차이가 나면 바닥에 닿았다고 판단하고 블로그 목록을 추가한다. 모바일에서는 50px 로는 안되서 100px 로 조절하였다. 아무래도 상태바와 패널 사이즈가 추가되어서 그런것 같다. 개발자 도구에서 스크롤링하면서 Elements 탭에 블로그가 추가되는지 확인해보자!

const scroller = new Scroller(false) // 스크롤 객체 생성 

window.addEventListener("load", (event) => {
  // 테마변경 (다크모드/ 일반모드)
  const mode = document.querySelector('.mode')
  const icons = mode.querySelectorAll('.fa-solid')
  const header = document.querySelector('header')
  const categoryContainer =  document.querySelector('.category-container') // 추가된 부분

  mode.addEventListener('click', (event) => {
    document.body.classList.toggle('dark')
    header.classList.toggle('dark')
    categoryContainer.classList.toggle('dark') // 추가된 부분 
    
    for(const icon of icons){
      icon.classList.contains('active') ? 
        icon.classList.remove('active') 
        : icon.classList.add('active')
    }
  })

  // 브라우저 상단으로 스크롤하기
  const arrowUp = document.querySelector('.footer .icons .scroll-up') // 위쪽 화살표 클릭 
  arrowUp.addEventListener('click', (event) => {
    history.pushState({}, "", `#`); // URL 주소 변경 
    scroller.setScrollPosition({top: 0, behavior: 'smooth'})
  })

  const logo = document.querySelector('header .logo') // 로고 클릭 
  logo.addEventListener('click', (event) => {
    event.preventDefault() // 부드러운 스크롤링
    history.pushState({}, "", `#`); // URL 주소 변경 
    scroller.setScrollPosition({top: 0, behavior: 'smooth'}) 
  })

  const blogContainer = document.querySelector('.blog-container')
  blogContainer.innerHTML += getBlogList(10) // 초기에 블로그 10개 추가하기

  window.addEventListener('scroll', (event) => {
	
    // 무한스크롤 구현 
    // 브라우저창 높이 : document.documentElement.clientHeight
    // 문서 상단 높이 : window.pageYOffset
    
    const scrollHeight = Math.max(   // 전체문서 높이 (스크롤이벤트 내부에 있어야 함)
    document.body.scrollHeight, document.documentElement.scrollHeight,
    document.body.offsetHeight, document.documentElement.offsetHeight,
    document.body.clientHeight, document.documentElement.clientHeight
    );
    
    // 스크롤을 브라우저창 아래까지 다 내린경우
    if(Math.abs(scroller.getScrollPosition() + document.documentElement.clientHeight - scrollHeight) < 100){ 
      console.log('scroll is bottom of browser!')
      blogContainer.innerHTML += getBlogList(10) // 블로그글 10개씩 추가하기
    }
  })


})


// 더미 데이터 생성
function getBlogList(num){
  let blogList = ''
  for (let i = 0; i < num; i++) {
    blogList += `
        <div class="blog">
        <div class="left">
          <ul>
            <li class="category-name"><a href="#">여행</a></li>
            <li class="posting-time">1시간전</li>
            <li><a href="#" class="likes">공감</a><span>9</span></li>
          </ul>
        </div>
        <div class="middle">
          <ul>
            <li><h3>제주도 오른 까페 방문하기</h3></li>
            <li><p>성산의 해안도로를 따라 달리다 보면 보이는 오른 카페는 제주도의 자연요소 중 하나인 오름을 모티브로 한 카페의 콘셉트인 만큼 자연과 함께 커피를 마시며 즐길 수 있었던 것 같아요.😋</p></li>
            <li>
            <ul>
              <li>
                <div class="account">
                  <img src="../imgs/avatar.jpg" alt="">
                  촌부 <span>by 농돌이</span>
                </div>
              </li>
              <li><button>구독하기</button></li>
            </ul>
            </li>
          </ul>
        </div>
        <div class="right">
          <ul>
            <li>
            <img src="../imgs/waterfall.jpg" alt="blog-thumbnail">
            </li>
          </ul>
        </div>
      </div>
    `
  }
  return blogList
}

완성된 story.js 파일은 위와 같다. 

 

 

728x90

'프로젝트 > 블로그 사이트' 카테고리의 다른 글

5. 포스트 페이지 구현하기  (0) 2023.07.16
4. 홈페이지 구현하기  (0) 2023.07.09
2. 랜딩 페이지 구현하기  (0) 2023.07.07
1. 프로젝트 준비  (0) 2023.07.07