저희 조 오성그룹이 개발한 ‘이화 마켓’은 중고거래 플랫폼의 웹어플리케이션입니다. 이화마켓의 핵심 기능 중 하나는 바로 ‘리뷰 전체 조회’ 페이지인데요, 이 페이지에서 사용자들은 많은 후기들을 쉽고 편하게 볼 수 있습니다. 이번 포스팅에서는 이 많은 정보들을 직관적이고 깔끔하게 정리하여 탐색 경험을 최적화한 프론트엔드 구현 과정을 설명하고자 합니다.
리뷰 페이지, 어떻게 구현할까?
리뷰 조회 페이지에서는 리뷰 제목, 이미지, 별점, 작성자 아이디 등 다양한 정보가 한 눈에 들어오는 것이 중요합니다. 또 모든 정보가 똑같은 중요도를 가지는 것은 아닙니다. 사용자들이 주로 많이 신경 쓸 제목과 별점, 이미지를 강조할 수 있어야 했습니다. 따라서 단순히 리스트로 나열하기보다는 카드에 정보를 담아 보여주는 것이 중요한 정보를 더 확실하게 보여주고 정보들의 위치를 배열하는 데에 더 적합하다고 판단했습니다. 또한 리뷰 수가 어느 정도 늘어나면, 스크롤만으로 모든 리뷰를 보기엔 불편함이 있습니다. 따라서 페이지네이션을 추가하여 편리함을 더했습니다. 이외에도 별점 로직, Hover effect 등을 통해 더 쾌적하고 사용하기 쉽게 구축하고자 했습니다.
사용한 기술들
화면 구성: CSS Grid Layout
template engine: Flask (Jinja2)
interaction: CSS Hover Effects & Transactions
상세 구현 방법
1. CSS Grid를 활용한 3열 카드 레이아웃
리뷰 카드를 정갈하게 배치하기 위해 CSS Grid 시스템을 사용했습니다.
.review-grid {
display: grid;
grid-template-columns: repeat(3, 1fr); /* 1:1:1 비율의 3열 구조 */
gap: 40px; /* 카드 사이 간격 */
justify-items: center;
max-width: 1200px;
margin: 0 auto 60px;
}
repeat(3, 1fr) 속성을 사용해 화면 크기에 맞춰 유동적으로 변하는 3개의 열을 만들고, gap으로 카드 사이의 여백을 부여해 카드끼리 구분이 잘 될 수 있도록 했습니다.
2. Jinja2 템플릿을 이용한 별점 로직
별점 이미지를 가져오는 대신 Flask의 템플릿 엔진인 Jinja2의 문자열 연산 기능을 활용하였습니다.
<div class="review-stars">
{% set r = (p.rating or 0)|int %}
{{ '★' * r }}{{ '☆' * (5 - r) }}
</div>

rating 점수만큼 꽉 찬 별을 반복해서 찍고, 나머지 점수만큼 빈 별을 찍습니다. 서버에서 HTML이 이미 완성되기 때문에 페이지 로딩 즉시 별이 렌더링 되고, 유지보수 또한 쉬워진다는 장점이 있습니다.
3. Hover effect
사용자가 현재 마우스로 어떤 카드를 가르키고 있는지 알려주고, 카드를 클릭할 수 있다는 사실을 알 수 있도록 CSS transform을 통한 Hoover 효과를 적용했습니다.
.review-card {
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.review-card:hover {
transform: translateY(-4px); /* 위로 살짝 떠오르는 효과 */
box-shadow: 0 5px 12px rgba(0,0,0,0.1); /* 그림자 강화 */
}

첫번째 리뷰 카드가 살짝 위로 올라가고, 그림자가 진해진 것을 확인할 수 있습니다.
4. 이미지 비율 유지와 카드 통일성 맞추기
리뷰 작성자가 업로드한 사진들은 모두 그 비율과 크기가 제각각 입낟. 이게 그대로 카드에 반영 된다면 카드들은 통일성을 잃고 조화롭지 못하게 보일 것 입니다. 이 문제를 css의 object-fit:cover 속성을 사용하여 해결했습니다.
HTML [review.html]
<div class="review-image">
<img src="{{ url_for('static', filename='images/' ~ p.images[0]) }}">
</div>
CSS [review_style.css]
.review-card .review-image {
width: 100%;
height: 250px;
overflow: hidden; /* 영역 밖으로 나가는 이미지 부분 숨김 */
margin-bottom: 15px;
}
.review-card .review-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
overflow: hidden을 통해 영역 밖으로 나가는 이미지를 숨기고, object-fit:cover;을 통해 비율을 유지하고 정해진 영역을 꽉 체울 수 있도록 했습니다.
5. 페이지네이션
다수의 리뷰를 보기 쉽게 한 페이지당 일정 개수만큼 나오도록 페이지네이션을 사용했습니다. 현재 페이지에 나오는 리뷰들과 아닌것들을 구분하기 위해 상태에 따라 구분되는 CSS를 추가했습니다.
HTML [review.html]
{% if p == page %}
<span class="page-num active">{{ p }}</span>
{% else %}
<a href="..." class="page-num">{{ p }}</a>
{% endif %}
CSS [review_style.css]
.page-num.active {
color: #fff;
background: #2e6b56;
}

그 결과 이런식으로 이동을 할 수 있는 페이지네이션이 완성되었습니다.
마무리
다양한 항목들을 보여줘야하는 상황이 왔을 때, 전달하고자 하는 데이터의 종류와 성격에 따라 어떤 스타일이 적합할 것인가에 대해 깊게 고민해볼 수 있었습니다. 또한 복잡한 프레임워크 없이도 CSS 기본 기술만으로 반응형 디자인과 카드 통일성을 확보할 수 있다는 점을 배울 수 있었습니다. 또한 템플릿 엔진 내에서 별점 로직을 사용한 것은 초기 로딩 속도를 개선하고, 프론트엔드 로직의 일부를 백엔드 템플릿 엔진으로 적절히 이전하여 사용자 경험과 개발 효율성을 동시에 향상시킬 수 있었던 좋은 선택이었다고 생각합니다.