2024. 1. 28. 17:07ㆍ[Way to PM] 프론트 엔드
0. 레퍼런스
1. 우선 순위 설정
1) HTML로 의미 구현
2) CSS로 스타일 구현
3) JS로 기능 구현
2. WHY - 해당 과제를 통해 얻고자 하는 목표
1) HTML, CSS로 부여된 랜딩 페이지 시맨틱하게 구현을 통한 학습 및 역량강화
2) JS로 모달창, 탑버튼, 이미지 데이터 불러와서 이벤트(클릭, 스크롤)와 연동하는 기능 구현 을 통한 학습 및 역량강화
3) 모범 사례와의 비교를 통한 심화 학습
3. HOW - 코드 리뷰
1)내가 구현한 코드
See the Pen Untitled by 송재훈 (@ovzdbclr-the-sasster) on CodePen.
2) 모범 사례
See the Pen Untitled by 송재훈 (@ovzdbclr-the-sasster) on CodePen.
리뷰-HTML
1. mainText들의 위치 정렬을 위해 div를 과사용했다. 이미지의 배치가 애매해서 section으로 첫 구획을 나누지 않았다.
->figure와 figcaption을 이용해 이미지와 그에 대한 설명이 이루어진 독립적거나 자체 완결성을 가진 콘텐츠를 구분한다.
<section class="intro-main">
<figure>
<img src="images/img_hodu1.jpeg" alt="체크무늬 이불위에 옆으로 엎드린채 고개를 들어 어딘가를 바라보는 호두">
<figcaption>
<h2>Lorem Ipsum is simply
dummy text of the printing and dummy text</h2>
<p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. </p>
</figcaption>
</figure>
</section>
*figure: 이미지, 다이어그램, 코드스니펫, 오디오, 비디오 등이올 수 있다.
**figcaption: figure 콘텐츠를 설명하는 콘텐츠를 담는다.
2. 요소의 기능과 사용성을 고려하지 않고 section으로만 구획을 나누려 했다.
-> 요소의 기능과 사용성을 고려해 article사용도 고려한다. 가령 위젯같은 경우가 그렇다.
<article class="widget-subscribe">
<div class="box-widget-txt">
<h3>Subscribe to our Blog post</h3>
<p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text </p>
</div>
<form class="form-subscribe">
<label for="inpEmail" class="a11y">구독 메일 입력창</label>
<input id="inpEmail" type="email" required placeholder="Enter your e-mail address">
<button type="submit" class="btn-common">Subscribe</button>
</form>
</article>
3. 접근성을 고려해 a11y와 같은 ImageReplacement기법을 사용한다. 가령 텍스트가 클라이언트에 보이지 않아야하는 텍스트들이 그렇다.
4. 모달창 구현시 dialog태그를 사용해 JS와의 연동성과 클린코드 구현을 고려한다.
->form태그 안에도 figure/figcaption 태그가 들어갈 수 있다. 코드를 유연하게 짜보면서 접근성, SEO, 시맨틱 코드를 모두 고려하도록 한다.
*strong태그는 p태그 안에 있지 않고 단독으로 사용해도 된다.
<dialog class="modal-hodu">
<form method="dialog">
<figure>
<img src="images/img_hodu_head.png" alt="볼빨간 호두">
<figcaption>
<strong>Thank you!</strong>
<p>Lorem Ipsum is simply dummy text of the printing industry.</p>
</figcaption>
</figure>
<button class="btn-common">OK! I Love HODU</button>
</form>
</dialog>
5. 구획을 나누는 선을 구현하기 위해 div태그를 사용했다.
-> 구획을 나누는 부분과 가장 가까운 요소에 boder-bottom을 적용하고 마진, 패딩으로 처리 가능하다.
리뷰-CSS
1. 반응성을 고려하지 못하고 전체 배경에 요소를 배치하려고하니 모든 요소에 margin이 적용되었다.
-> 반응성을 고려해 웹페이지의 배경색이 들어갈 부분과 사용자가 이용할 요소가 들어갈 부분을 구분해 css를 적용한다.
container가 있다면 flex-basis, 아니라면 max-width나 width: 100%를 적용해 좌우 반응성에 대응한다.
*background 속성 중 cover를 사용하면 이미지 자체의 크기와 상관없이 부모 요소의 넓이가 넓어짐에 따라 같이 넓어진다.
2. 디자인 영역에서의 이미지 같은 경우(큰 의미를 가지지 않는)도 img 태그로 html문서에 구현해서 css를 적용해 배치했다.
-> before 가상선택자를 이용해 이미지를 적용한다. 이를 위해선 content 속성을 통해 공간을 만들어주어야 한다.
.widget-subscribe .form-subscribe::before{
position:absolute;
left:28px;
top:50%;
transform: translateY(-50%);
content:'';
width: 24px;
height: 24px;
background-image: url("images/icon_mail.png");
}
리뷰-JS
1. 모달창 구현 -
1) 모달 오픈, 클로스 버튼 외에 윈도우 창을 눌러 모달창을 끌 수 있도록 구현했다.
2) 과정에서 event bubbling현상으로 모달 오픈 버튼을 눌러도 모달이 열리지 않았다.
3) 이를 위해 이벤트가 전파되지 않도록 stopPropagation을 적용하거나, window에 적용되는 함수를 적용하지 않음으로써 해결했다.
const openModal = document.getElementById("emailBtn")
const modal = document.getElementById("modal")
const closeModal = document.getElementById("modalBtn")
openModal.addEventListener('click', () => {
modal.style.display = "flex";
event.stopPropagation();
})
closeModal.addEventListener('click', () => {
modal.style.display = "none";
})
window.addEventListener('click', (event) => {
if (event.target !== modal) {
modal.style.display = 'none';
}
});
-> dialog태그를 사용하면 showModal 문법사용이 가능해진다. 이를 통해 코드를 많이 줄이면서 쉽고 빠르게 모달을 구현할 수 있다.
const btnSubscribe = document.querySelector('.form-subscribe .btn-common');
const modal = document.querySelector('.modal-hodu');
btnSubscribe.addEventListener('click', ()=>{
modal.showModal();
});
2. 무한 스크롤에 따른 이미지 데이터 랜더링 구현 -
1) fetchImages함수를 초기 페이지 랜더링시 실행시켜 자동으로 이미지를 로드시킴
2) 사용자가 이미지를 보고싶다면 Showmore버튼을 눌러 수동으로 이미지 추가로드
3) 이후 윈도우에 스크롤 이벤트가 발생할때마다 이미지 추가 로드되어 innerHTML형태로 랜더링
4) 쓰로틀링을 따로 구현하지 않음
*쓰로틀링: 반복된 작업으로 사용자 경험이 저해되지 않도록 작업간의 delay를 설정하는 것
const imageList = document.querySelector(".imgLoading");
let pageToFetch = 1;
async function fetchImages(pageNum){
try {
const response = await fetch('https://picsum.photos/v2/list?page='+pageNum+'&limit=6');
if (!response.ok) {
throw new Error('네트워크 응답에 문제가 있습니다.');
}
const datas = await response.json();
console.log(datas);
makeImageList(datas);
} catch (error) {
console.error('데이터를 가져오는데 문제가 발생했습니다 :', error);
}
}
function makeImageList(datas) {
datas.forEach((item) =>{
imageList.innerHTML = imageList.innerHTML + "<li><img src="+ item.download_url+" alt=''></li>"
;})
}
window.addEventListener('scroll', () =>{
//스크롤이 상단으로부터 얼마나 이동했는지 (뷰포트의 높이 + 스크롤된 길이)
//화면에 로딩된 페이지의 전체 높이
//화면에 로딩된 페이지의 전체 높이 == 뷰포트의 높이 + 스크롤된 길이+5~10px
if (window.innerHeight + document.documentElement.scrollTop + 10 >=
document.documentElement.offsetHeight) {
fetchImages(pageToFetch += 1);
}
});
const showMore = document.getElementById("showMore")
showMore.addEventListener('click', function () {
fetchImages(pageToFetch += 1);
});
fetchImages();
-> 무한 스크롤에 따른 이미지 데이터 랜더링과 쓰로틀링 구현
1) insertAdjacentHTML을 이용해 랜더링된 이미지를 li요소로 추가.
*innerHTML은 요소가 추가될때마다 전체 html을 다시 랜더링하므로 효율성이 떨어진다. 하지만 inserAdjacentHTML은 이미지가 포함된 요소만을 추가함으로 대량의 데이터를 다루고 더 나은 사용자 경험을 제공하기에 효율적이다.
2) scroll이벤트 발생시 throttleTracker함수 실행해 작업간 반복을 피하도록 함. throttling함수에서 timer를 계속 null로 다시 설정해 쓰로틀링이 계속 발생할 수 있게 한다.
3) 또한 이미지가 스크롤되는 영역을 따로 지정해 윈도우 전체의 스크롤과 이미지 랜더링을 위한 구역의 스크롤을 나눌 수 있게 했다. 더 나은 사용자 경험을 제공할 수 있다.
/*
* 무한 스크롤 코드
* */
const photosMain = document.querySelector('.photos-main');
const listPhotos = photosMain.querySelector(".list-photos");
const btnShow = photosMain.querySelector('.btn-more');
const btnEnd = photosMain.querySelector('.btn-end');
let pageToFetch = 1;
async function fetchImages(pageNum){
try {
const response = await fetch(`https://picsum.photos/v2/list?page=${pageNum}&limit=6`);
if(!response.ok){
throw new Error('네트워크 응답에 문제가 있습니다.');
}
const datas = await response.json();
makeImageList(datas);
}catch (e) {
console.error('데이터를 가져오는데 문제가 생겼습니다.', e);
}
}
function makeImageList(datas){
datas.forEach((data)=>{
listPhotos.insertAdjacentHTML('beforeend', `<li><button type="button"><img src=${data.download_url} alt=""></button></li>`);
});
}
function throttling(callback, delay){
let timer = null;
return ()=>{
if(timer === null){
// listPhotos.clientHeight ==> 요소의 높이
// listPhotos.scrollTop ==> 요소에서 얼마나 스크롤이 됐는지
// listPhotos.scrollHeight ==> 요소가 스크롤된 내용의 전체 높이
if(listPhotos.clientHeight + listPhotos.scrollTop + 5 >= listPhotos.scrollHeight){
timer = setTimeout(()=>{
callback(pageToFetch++);
timer = null;
}, delay);
}
}
}
}
const throttleTracker = throttling(fetchImages, 500);
btnShow.addEventListener('click', ()=>{
photosMain.classList.toggle('stop');
listPhotos.style.height = '1400px';
listPhotos.style.overflowY = 'scroll';
document.body.style.overflow = 'hidden';
listPhotos.addEventListener('scroll', throttleTracker);
fetchImages(pageToFetch++);
window.scrollTo({
top: listPhotos.offsetTop + 1400 ,
behavior: "smooth"
});
});
btnEnd.addEventListener('click', ()=>{
photosMain.classList.toggle('stop');
document.body.style.overflow = "auto";
listPhotos.removeEventListener('scroll', throttleTracker);
});
3. 탑버튼 구현 -
1) JS로 클릭이벤트 발생할때마다 최상단으로 이동하도록 구현
const topBtn = document.getElementById("topBtn")
window.addEventListener('scroll', function () {
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
topBtn.style.display = 'block';
} else {
topBtn.style.display = 'none';
}
});
const topBtnImg = document.getElementById("topBtnImg")
topBtn.addEventListener('mouseover', function () {
topBtnImg.src = "scroll-top-btn2.png"
});
topBtn.addEventListener('mouseout', function () {
topBtnImg.src = "scroll-top-btn.png"
});
// 클릭 이벤트를 사용하여 상단으로 스크롤합니다.
topBtn.addEventListener('click', function () {
document.body.scrollTop = 0;
document.documentElement.scrollTop = 0;
});
-> button이 아니라 a태그로 구현하고 css hover 속성을 이용해 구현. 또한 a태그의 href에 최상단 요소의 id와 연동시켜 JS를 이용할 필요없이 구현할 수 있다.
.link-top:hover{
background-image:url("images/icon_arrow_up_hover.svg");
}
4. WHAT - 구현 결과
5. 배운점 및 자기평가
1) 상급자의 코드 구성이나 적극적인 코드 리뷰를 통해 질 좋은 코드 구현의 중요성을 배웠다.
2) 다시 배웠다고 할 수 있는 시점은 언제든 내가 직접 코드를 칠 수 있는 시점이다.
3) 나만의 css컨벤션이나 규칙을 정해놓으면 효율적이고 시맨틱한 코드를 구축할 수 있다.
자기평가
1. 배운것들로만 코드를 구현하려 하지말고 최대한 간결하고 시맨틱하게 구현할 수 있는 모든 것을 습득해 구현하는 습관을 가진다.
2. 공부했던 것, 과제했던 것 등 무조건 글로 다시 써봐야 한다. 쓰면서도 생각나는 점과 배운점이 또 다르다.
'[Way to PM] 프론트 엔드' 카테고리의 다른 글
[Way to PM] 프론트 엔드 - Figma Plugin을 통해 화면 설계 쉽게 접근하기 (1) | 2024.03.22 |
---|---|
[Way to PM] 프론트 엔드 - HTML, CSS JS로 랜딩 페이지 구현(footer) (0) | 2024.01.29 |
[Way to PM] 프론트 엔드 - HTML, CSS JS로 랜딩 페이지 구현(Header) (0) | 2024.01.24 |
[Way to PM] 프론트엔드 - 회원가입 구현 (0) | 2024.01.20 |
[Way to PM] 프론트엔드 - 반응형 Input 요소 만들기 (0) | 2024.01.19 |