

메인페이지는 3개의 파트로 나눴다.
1) 좋아요 순으로 열개를 보여주는 best 10
2) 작성하기 버튼
3) 레시피 전체 목록
1. Best 10
Best 10 파트는 좋아요가 가장 많은 순서대로 10개의 레시피를 보여주는 파트다.
레시피 상세페이지에서 게시물에 좋아요를 할 수 있는 기능이 있는데, 그것에 대한 데이터를 읽어와서
메인페이지에 보여주었다.
(데이터를 백엔드로부터 전달받고 ejs에서 for문을 사용해서 데이터를 보여주었다)
여기서 배너기능을 손쉽게 사용하기 위해서 swiper.js 라이브러리를 사용했다.

여기서 관건은 가운데 컨텐츠를 슬라이드 했을 때 양옆의 사이트 컨텐츠들도 같이 슬라이드 된다는 것이었다.
그래서 스와이프 설정에서 양옆의 사이드 컨첸트는 allowTouchMove라는 속성을 false로 해주고 가운데 컨텐츠만 true값을 넣어주었다 .
// 중앙 스와이퍼
let centerSwiper = new Swiper('.recommend-container .recommend-centerSwiper', {
loop : true,
speed : 700,
autoplay : {
delay : 3000,
disableOnInteraction: false,
},
pagination : {
el : '.swiper-pagination',
type : 'progressbar'
},
navigation : {
nextEl : '.swiper-btnGroup .swiper-button-next',
prevEl : '.swiper-btnGroup .swiper-button-prev'
},
allowTouchMove : true,
slidePerView : 1,
})
// 사이드 스와이퍼
let sideSwiper = new Swiper('.recommend-container .recommend-rightSwiper, .recommend-container .recommend-leftSwiper', {
spaceBetween : 5,
loop : true,
slidePerView : 1,
allowTouchMove : false
})
centerSwiper.controller.control = sideSwiper; // 스와이퍼 연결
centerSwiper.controller.control = sideSwiper
이거로 스와이퍼를 연결해서 가운데 컨텐츠로 사이드 컨텐츠까지 제어할 수 있다.

타이틀의 반짝거리는 효과는 css 설정을 활용했다.
.recommend-title .title-text {
animation: backgroud-pan 3s linear infinite;
background: linear-gradient(to right, var(--darkColor2),var(--lightColor2), var(--mainColor), var(--darkColor), var(--darkColor2));
background-size: 200%;
background-position: 0% center;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-fill-color: transparent;
white-space: nowrap;
}
@keyframes backgroud-pan {
from {
background-position: 0% center;
}
to {
background-position: -200% center;
}
}
우선 linear-gradient로 백그라운드의 효과를 주고, background-clip : text를 이용해서 텍스트 영역으로 백그라운드 클리핑을 했다. 그리고 애니메이션으로 포지션을 바꿔가며 그라데이션가 움직이는 효과를 줬다.
별이 나타났다 사라지는 효과는 css 애니메이션과 js의 랜덤함수를 이용해서 구현했다.
2. 작성하기 버튼 영역

리스트만 나열하면 다소 페이지가 심심하게 보여질 것 같아서 중간에 작성하기 영역을 만들었다.
하이볼이란 주제에 걸맞게 탄산을 표현했다!
자세히 보면 버블이 보글보글 위에 올라가는 것이 보인다.
// 탄산수 효과 주는 JS
// 변수
const bubbleWrap = document.querySelector('.bubble-wrap')
let spawnNumber = 30;
let bubbles = [];
let radius, speed, leftDeviation, topLimit = 0;
for(let i = 0; i < spawnNumber; i++){
attributeRandomizer();
bubbles[i] = document.createElement('div');
bubbles[i].classList.add('bubble');
bubbleWrap.appendChild(bubbles[i]);
setBubbleRadius(bubbles[i], radius);
setBubbleDeviation(bubbles[i], leftDeviation);
bubbleFlow(bubbles[i], speed, topLimit);
}
function setBubbleRadius(bubble, size){
bubble.style.width = size + 'px';
bubble.style.height = size + 'px';
}
function setBubbleDeviation(bubble, deviation){
bubble.style.left = deviation + 'px';
}
function attributeRandomizer(){
speed = Math.floor(Math.random() * 10) + 1;
radius = Math.floor(Math.random() * 25) + 3;
leftDeviation = Math.floor(Math.random() * 1240) + 20;
topLimit = Math.floor(Math.random() * 350) + 200;
}
function bubbleFlow(bubble, time, limit){
let startingPosition = 0;
let id = setInterval(flow, time, limit)
function flow(){
if(startingPosition == limit){
startingPosition = 0;
startingPosition++;
bubble.style.bottom = 0 + startingPosition + 'px';
} else {
startingPosition++;
bubble.style.bottom = startingPosition + 'px';
}
}
}
탄산수의 효과를 주기 위해서 codepen 에 있는 코드 중 하나를 참고해서 위와 같이 구현했다.
만들어질 버블의 갯수를 30개로 정하고 전역변수로 선언했다.
그리고 그 만큼 for문을 돌려서 그 만큼 만들어지게 했다. 그리고 각 버블의 크기와 버블이 나오는 위치,
버블이 사라지는 곳, 버블이 올라가는 속도를 랜덤화해서 구현했다.


그리고 이 버블과 작성하기 버튼을 감싸는 영역의 배경은 역시 linear-gradient를 활용했는데,
사실 스크롤 위치마다 미묘하게 배경색이 바뀐다.. 그런데 너무 미묘해서 다른 사람들은 알아채지 못했다!! ㅠ ㅠ
음.. 다음에 할 떄는 좀 더 사람들이 알아채기 쉽도록 해야할 것 같다!
그리고 토끼 아이콘에 애니메이션을 줘서 움직이게 만들었다. 나름 술 취한 토끼를 표현한 것이다..ㅋㅋ
3. 레시피 전체 목록
이번 프로젝트에서 전체 목록에 대해 따로 페이지를 만들지 않고, 메인페이지에서 보여지도록 했다.
(데이터 양이 많지 않아서 이렇게 구현했다. 만약 데이터 양이 많거나, 검색 기능등을 넣는다면 따로 페이지를 만들고 메인페이지에서는 최신 10개만 보이도록 해야할 것 같다.)

레시피의 전체 목록을 보여주되, 주재료 버튼을 누르면 그에 해당하는 레시피만 나오도록 구현했다.
레시피 버튼을 마우스 오버했을때 심심하지 않게 나름 애니메이션을 주었다..(하지만 아무도 몰랐다........ㅠㅠ)
// 카테고리 버튼용 배열
let ingredientArray = ['all', 'whiskey', 'vodka', 'soju', 'sake', 'etc']
// 카테고리 버튼 만드는 함수
function makeCategoryBtn(){
let hcode =``;
let ingredientKr
let categoryBtnColor;
ingredientArray.forEach(ele=>{
switch(ele){
case 'all' : ingredientKr = '전체', categoryBtnColor = '#EA4949';
break;
case 'whiskey' : ingredientKr = '위스키', categoryBtnColor = '#F29B30' ;
break;
case 'vodka' : ingredientKr = '보드카', categoryBtnColor = '#25B7D3' ;
break;
case 'soju' : ingredientKr = '소주', categoryBtnColor = '#22A858';
break;
case 'sake' : ingredientKr = '사케', categoryBtnColor = '#32BEA6';
break;
case 'etc' : ingredientKr = '기타', categoryBtnColor = '#EA4949';
break;
}
hcode +=
`<li>
<div class="btn-bx" title=${ele}>
<figure style='background-color : ${categoryBtnColor}'>
<img src='/public/img/${ele}.png' alt='${ingredientKr}'/>
</figure>
<p class="category-title">${ingredientKr}</p>
</div>
</li>`
})
recipeCategory.innerHTML = hcode
}
버튼에 대한 태그는 html 하드코딩을 피하고자 자바스크립트를 통해 만들었다.
나름대로 주재료에 대한 배열을 만들었는데.. 저게 좋은 방법인지는 모르겠다. 백엔드에서 가져올 방법은 없었을까.?
다른 프로젝트를 할때는 저 부분에 대해서 좀 더 고민을 해봐야 할 것 같다.
버튼 색도 각 주재료에 맞게 표현하고, 주재료를 한글로 표현하기 위해 switch case를 썼다.
처음에 페이지가 렌더링되었을 때도 목록이 보여야 하므로 ejs 에서 for문을 사용해 백엔드에서 넘겨준 전체 레시피에 대한 데이터를 보여주었다.


주재료에 대한 버튼을 눌렀을 때 해당하는 레시피가 나오도록 하는 것은 아래와 같이 구현했다.
일단은 버튼에 있는 title 값을 읽어서, 백엔드로 보내주고 백엔드에서 그 값을 읽어서(req.params로 읽었을 것이다) 주재료에 대한 값을 프론트에게 넘겨준다. 그러면 프론트에서 그 값을 받아서 주재료에 대한 레시피가 나오도록 했다.
(말이 내가 쓰면서도 너무 어렵네;;)
레시피 목록에 대한 css 구성은 display: grid 를 활용했다 사실 flex 를 써도 되지만 좀 더 화면 구성을 반응형에 쉽고 걸맞게 만들고 싶었기 때문이다. (캡쳐도 잠깐 보여지지만 화면 사이즈가 특정 크기 이하면 3개, 2개씩 보여지도록 반응형을 구성했다.)
메인페이지에 처음부터 모든 레시피에 나올 경우에 데이터가 많아지면 너무 길어지므로 더보기 버튼을 만들었다.
처음엔 8개까지 보여주다가 데이터가 8개보다 많으면 더보기 버튼을 눌러서 나머지 데이터를 8개씩 보여지도록 구현했다.
그를 위해서 전역변수로 8을 할당해주고, start라는 변수를 선언해서 버튼을 누를때마다 이 start 변수에 8이 더해지도록 구현했다. 그리고 slice() 메서드를 사용해서 레시피 배열에 대한 배열을 8개씩 더해서 나올 수 있도록 구현했다.
// 더보기 기능을 위한 변수 설정
const MAXCOUNT = 8;
let start = 0;
let cnt = 1;
// 주재료 버튼 눌렀을 때
recipeBtn.forEach(ele=>{
ele.onclick = async (e) => {
e.preventDefault();
const currentScroll = document.querySelector('.recipe-list__container').offsetTop
start = 0;
const btnTitle = ele.querySelector('.btn-bx').getAttribute('title');
// 초기화
recipeLists.innerHTML = '';
recipeMoreBtnBx.innerHTML = `<button class="morebtn2 morebtn">+ 더보기</bottion>`
window.scrollTo({top : currentScroll, behavior: 'smooth'})
await getRecipeList(btnTitle)
}
})
// 레시피 목록 조회 함수
async function getRecipeList(ingredient) {
try{
const getRecipeAxios = await axios({
method : 'get',
url : `/${ingredient}`,
})
const recipeData = getRecipeAxios.data;
renderRecipeLists(recipeData.slice(start, start + MAXCOUNT));
let recipeMoreBtn = recipeMoreBtnBx.querySelector('.morebtn2')
recipeMoreBtn.addEventListener('click', ()=>{
if(recipeData.length > MAXCOUNT) {
start += MAXCOUNT;
renderRecipeLists(recipeData.slice(start, start + MAXCOUNT));
// 더보기 버튼 처리
if(start + MAXCOUNT >= recipeData.length) {
recipeMoreBtn.style.display = 'none'
} else {
recipeMoreBtn.style.display = 'block'
}
}
})
// 더보기 버튼 처리
if(start + MAXCOUNT >= recipeData.length) {
recipeMoreBtn.style.display = 'none'
} else {
recipeMoreBtn.style.display = 'block'
}
}catch(err){
console.error(err);
}
}
// 리스트 렌더링 함수
function renderRecipeLists(recipes){
let hcode = ``;
const image_path = '/uploads/recipe/';
recipes.forEach(recipe=>{
if(recipe['Recipe_Imgs.image_url']){
hcode +=
`<li>
<a href="/recipe/read?recipe_num=${recipe.recipe_num}">
<figure>
<img src="${image_path}${recipe['Recipe_Imgs.image_url']}" alt="레시피이미지" class="recipe-list__img" />
</figure>
<p class="recipe__writer">${recipe['User.user_name']}</p>
<p class="recipe__title">${recipe.title}</p>
</a>
</li>`
} else {
hcode +=
`<li>
<a href="/recipe/read?recipe_num=${recipe.recipe_num}">
<figure>
<img src="/public/img/default_img.jpg" alt="레시피이미지" class="recipe-list__img" />
</figure>
<p class="recipe__writer">${recipe['User.user_name']}</p>
<p class="recipe__title">${recipe.title}</p>
</a>
</li>`
}
})
recipeLists.innerHTML += hcode;
}
프로젝트 끝물에 생각난 건데 프로필 사진을 설정해 놓고 쓰는 곳이 없어서 차라리 메인페이지에서 작성자 이름 옆에 프로필 사진이 뜨도록.. 설정해둘까.. 생각만하고 시간관계상 하지 못했다..
그냥 시간을 더 들여서라도 할 걸 그랬나 싶다.. 안그래도 그 부분에 대해서 프로필 사진을 쓰는 곳이 없다고 피드백을 받았었다. 역시 생각난 것은 바로바로 구현해야 하는 교훈을 얻었다.
그래도 뭔가 메인페이지에서 비동기식으로 구현한 것이 많아서 배운 점이 많았다.
리액트를 쓰지 않고 ejs로 버튼 클릭했을 때 레시피 목록이 바뀌는 것이나 더보기 기능을 할 수 있을까?? 했는데 역시 하면 된다!
'Project > SeSAC 1차 팀 프로젝트' 카테고리의 다른 글
| [새싹x코딩온] 웹 개발자 부트캠프 과정 1차 팀프로젝트 회고 #5 | 프로젝트를 마치며 (0) | 2024.07.28 |
|---|---|
| [새싹x코딩온] 웹 개발자 부트캠프 과정 1차 팀프로젝트 회고 #4 | 서버 배포 (0) | 2024.07.28 |
| [새싹x코딩온] 웹 개발자 부트캠프 과정 1차 팀프로젝트 회고 #2 | 회원가입과 로그인 (0) | 2024.07.27 |
| [새싹x코딩온] 웹 개발자 부트캠프 과정 1차 팀프로젝트 회고 #1 | 공통 UI(Header, Footer, Cookie) (0) | 2024.07.25 |
| [새싹x코딩온] 웹 개발자 부트캠프 과정 1차 팀프로젝트 회고 #0 (0) | 2024.07.25 |