가장 먼저 시도해볼 프로젝트의 이름은 바로 'NFT preview card component'입니다. 이 프로젝트는 개인적으로 Frontend Mentor에서 가장 쉽다고 생각되는 프로젝트 중 하나였기 때문에 선택했지만, 마지막에 약간 막혔던 부분들이 있었기 때문에 마냥 쉬웠다고 보기에도 힘들었던 프로젝트였습니다 (아직 실력이 많이 부족하다는 증거이기도 하네요 ㅠㅠ.).
사용한 기술스택은 HTML, CSS입니다.
(저는 이미 제출을 완료한 상태이기 때문에 체크 표시가 되어 있습니다.)
난이도는 '1단계'로 상당히 쉬운 편에 속했습니다. 'Frontend Mentor'는 크게 5단계로 나뉘어진 프로젝트를 가지고 있습니다.
- NEWBIE - 정말 초보적이고 간단한 프로젝트
- JUNIOR - 평이한 난이도를 가진 프로젝트
- INTERMEDIATE - 약간 까다로운 난이도를 가진 프로젝트
- ADVANCED - 꽤 어렵고 까다로운 프로젝트
- GURU - 초고난도. 신급 난이도를 가지고 있으며, 전문가 수준의 실력을 갖추고 있어야 함
어쨌거나 지금의 제 실력으로 보아선 2~3단계가 가장 적절하지 않을까 하고 생각했지만, 어디까지나 처음이었기 때문에 이 챌린지를 선택했습니다. 혹시 제가 직접 작성한 코드에서 문제점이나 별로 달갑지 않은 부분들이 있으시다면 적극적으로 말씀해주세요 🙃
제가 완성한 웹사이트는 이 링크에 들어가면 볼 수 있습니다. 완성된 웹사이트를 한 번 살펴보시고 완성된 프로젝트가 어떤 모습인지 확인해 보세요.
겉으로 보기에는 딱히 주목할 만한 부분이 없으시겠지만, 한 가지 특징이 있는데 그것은 바로 이미지를 hover하면 다음과 같이 옅은 하늘색 필터와 '눈 모양'의 아이콘이 나타나야 한다는 점입니다.
이처럼 아이콘을 hover하면 투명도가 있는 하늘색 필터와 눈 모양의 아이콘이 들어있는 것을 확인하실 수 있습니다. 그렇다면 본격적으로 해당 컴포넌트를 제작하기 위해 어떤 컴포넌트를 작성했는 지에 대해 설명해 드리도록 하겠습니다.
저는 이번 CSS를 작성하는 과정에서 'BEM 기법'으로 설계를 진행했습니다. 'BEM 기법'에 대해 간략하게 설명을 하자면, 모든 css의 엘리먼트의 클래스를 'Block, Element, Modifier'로 작성하는 방식을 말합니다. Block 내부에는 여러 개의 Element가 들어갈 수 있으며, Block이나 Element를 기반으로 변형을 가하고 싶은 경우 Modifier를 작성합니다. 이들의 클래스를 작성할 때 Block__Element--Modifier의 형식으로 작성합니다. 너무 깊게 들어갔다가는 딴 길로 샐 수도 있으니 간략한 설명은 여기서 마치도록 하겠습니다.
상당히 다양한 클래스 네이밍을 사용했지만 아주 간략하게 설명하기 위해 모든 클래스를 설명하는 것이 아닌 '주요 클래스'만을 설명해 드리도록 하겠습니다. 저는 설계를 하는 과정 속에서 다음과 같이 대략적인 그림을 그렸습니다. 사실 코딩을 하는 과정 속에서 변형된 부분들이 상당히 많았는데, 이 그림을 변형된 것까지 모두 포함한 그림입니다.
이제 대략적인 클래스에 대한 설명을 모두 마쳤으니 지금부터는 본격적으로 코드 설명을 드리도록 하겠습니다.
가장 먼저 전역적으로 쓰이는 css에 대해서 먼저 설명을 진행하겠습니다. 저 같은 경우에는 개인적으로 가장 먼저 css 코드를 작성할 때 '*'를 이용해서 작성하고는 합니다. '*'는 html의 엘리먼트 각각에 기본적으로 들어가는 속성이기 때문에 공통적으로 모두 쓰이게 되지만 반면에 우선순위의 측면에서는 굉장히 낮은 특징을 보이고 있습니다. 따라서 padding과 margin을 모두 0으로 설정해 기본적으로 들어가게 되면 이를 없애기로 하고, box-sizing을 border-box로 설정해서 길이나 높이 값을 설정할 때 border 속성까지 전부 포함시킬 수 있도록 합니다.
그 다음으로 주로 작성하는 선택자는 'body'입니다. 메인 화면에서 보여지는 대다수의 코드들이 이 엘리먼트 내부에서 작성되기 때문에 범위 또한 굉장히 넓은 영역을 커버합니다. 그러나 '*'와는 달리 하나의 엘리먼트(body)에서만 적용된다는 특징을 가지고 있습니다. 가장 먼저 body의 height을 100vh로 설정합니다. 어차피 현재 작업중인 컴포넌트는 스크롤이 필요없기 때문에 전체 화면의 100퍼센트를 기준으로 높이를 설정합니다. font-family, font-size를 설정해 나중에 'rem'을 기준으로 설정할 때 1rem이 바로 해당 font-size가 되기 때문에 이를 생각해서 코드를 작성했습니다. 그리고 line-height은 1로 설정했습니다.
header1은 나중에 'description__title'에서 잠깐 쓰이게될 요소에 불과하지만, 혹시나 다른 곳에서도 쓰일 가능성을 생각해서 전역 css 영역에서 작성했습니다. 'flexbox-column'의 경우에는 실제로 다양한 컴포넌트에서 세로형 flexbox를 사용하기 때문에 재사용이 상당히 많은 선택자 클래스였습니다.
이제 head 엘리먼트 내부에 들어있는 코드를 설명하겠습니다.
head 엘리먼트 내부에는 meta와 link가 들어있는데, 이 부분은 탬플릿을 다운로드 받았을 때 기본적으로 작성이 되어 있었던 코드입니다. 13~18번째 줄의 코드는 구글 폰트를 적용하고자 하기 위해 붙여넣기했고, 19번째 줄은 css를 html에 연결하기 위해, 20번째 줄은 크롬 브라우저 위의 '타이틀 바'에 title 엘리먼트 내부에 작성한 텍스트가 표시됩니다.
그 다음에는 'attribute 클래스'에 대한 코딩 과정입니다. .main__attribution 부분은 제가 코딩을 했고, 나머지 부분은 탬플릿에 들어있었던 코드입니다. position을 absolute로 설정해서 화면의 크기가 변해도 일정 비율을 유지해서 적절한 위치에 올 수 있게 했고, transform: translateX(-50%) 을 적용해 원하는 엘리먼트가 정확히 정중앙에 올 수 있도록 합니다.
그 다음에는 본격적으로 html/css를 설명하기로 하겠습니다.
main: 가장 기반이 되는 클래스입니다. 이 클래스는 화면 전체를 관할합니다. 이 클래스를 가지고 있는 엘리먼트 내부에는 'card'와 'attribution'이 존재합니다.
card: 카드 컴포넌트가 들어가는 영역입니다. 대부분의 작업을 card 내부에서 진행합니다.
가장 먼저 'card 컴포넌트'의 구성은 다음과 같이 이루어져 있습니다. 지금까지 잠깐이나마 설명했던 부분들을 제외하고, 'card__img--filter'에 대해서 설명을 드리자면, 이 클래스는 'Modifier'라고 볼 수 있습니다. 이 클래스가 하는 역할은 바로 hover 이벤트를 발생시켰을 때 약간 투명한 하늘색 필터를 기존의 이미지에 덮어 쓰는 역할을 합니다. 그리고 이 엘리먼트 내부에 들어있는 svg는 '눈 모양'과 관련된 아이콘입니다.
이제 본격적으로 main과 card의 css를 살펴보면 다음과 같습니다. main 클래스는 flexbox에서 flex-container의 역할을 담당하고, card 클래스는 flexbox에서 flex-items의 역할을 담당합니다. 따라서 display, align-items, justify-content는 모두 'main'에서 구현합니다. background-color는 모두 탬플릿에서 기본적으로 제공한 색상의 기준을 그대로 따랐습니다.
이 때 overflow를 hidden으로 설정했는데, 그 이유는 main 엘리먼트의 자식 요소들의 margin 속성을 main에서도 그대로 적용받았기 때문입니다. 이를 방지하기 위해 overflow를 사용했는데, 사실 overflow가 성능적인 측면에서 좋지 못하다는 소리를 들어서 사용을 하는데 많이 주저했습니다 ㅠㅠ 혹시 더 나은 방법이 있으시다면 말씀해주세요!
card__img: 카드 내부에 들어갈 사진을 관리하는 영역입니다. 'Card' Block 내부에 들어있는 'img' Element라고 생각하면 됩니다. 이 부분이 제일 까다로웠는데, 그 이유에 대해서는 나중에 설명을 드리도록 하겠습니다.
card__icon: card__img를 hover하면 'card__icon', 즉 '눈 모양의 아이콘'이 나타나도록 해야 합니다.
'.card__img' 선택자를 보면, margin이 설정되어 있다는 사실을 알 수 있습니다. 이 선택자를 가진 요소의 부모 엘리먼트인 'card'와의 간격을 1rem으로 벌리기 위해서 설정했는데, 결과적으로 부모 엘리먼트까지 영향을 받게 되어 위쪽의 흰 여백이 설정되었습니다. 그래서 부모 요소에 작성한 것이 바로 'overflow: hidden'이었습니다. 그리고 또 주목할 점은 바로 'height'과 'padding-bottom'입니다. 이 부분은 width 속성과 height 속성의 길이를 의도적으로 일치시키기 위한 트릭이었습니다.
다음의 코드를 살펴보면 'card__img' 선택자는 'div 요소' 내부에 들어 있습니다. 게다가 이 안에는 그 어떤 img 요소도 들어있지 않습니다. 'div 요소'는 기본적으로 'block' 이고, 따라서 width, height, margin, padding으로 레이아웃이나 영역을 구성해야 합니다. 이 안에 들어있는 내용물 또한 해당 요소의 길이에 영향을 주지 못하고 있습니다. 그리고 한 가지 중요한 점은 바로 'padding 사이즈'를 퍼센테이지로 설정하게 된다면 부모 요소의 width를 기준으로 padding의 길이가 결정된다는 것입니다. 놀라운 점은 믿거나 말거나지만, 오직 '1퍼센트'의 개발자만이 이 사실을 알고 있다는 점입니다. 출처는 여기에 있었습니다 :) 따라서 width나 padding-bottom이나 퍼센테이지를 적용하는 기준은 동일한 대상이 될 수 있게 되었고, 결과적으로 정사각형 모양의 길이를 완성시킬 수 있었습니다.
다시 코드로 돌아와서 이번에는 background-image에 대해서 살펴보기로 하겠습니다. 'card__img'의 경우에는 'background-image'가 url()로 설정되어 있고, 'card__img--filter'의 경우에는 'background-image'가 linear-gradient로 설정되어 있습니다. 저는 반투명한 색상 필터를 적용할 때 주로 'linear-gradient'를 사용하고는 합니다.
이 코드에서 주목해야 할 점은 바로 'opacity'입니다. 저는 'card__img--filter'의 opacity를 0으로 설정했는데, 그 이유는 hover 이벤트를 발생시켰을 경우 '1'로 설정을 하기 위해서였습니다. 즉, 저는 '애니메이션'을 적용해서 마우스를 위로 올리면 점점 불투명해지도록 다음과 같이 코딩했습니다.
마지막으로 'card__img'의 position을 relative로 설정한 이유에 대해서는 나중에 설명을 드리도록 하겠습니다.
그 다음에는 hover를 했을 경우 일단 약간 투명한 필터의 opacity를 1로 설정했고, 그 다음에는 card__icon'의 opacity 속성 또한 1로 설정했습니다. 그 결과 아이콘과 필터가 동시에 서서히 보이게 되는 결과를 만들 수 있습니다. 그 다음에는 클릭을 할 수 있는 경우의 아이콘을 설정하기 위해 'cursor: pointer'를 설정했습니다.
card__icon의 경우에는 'position'을 absolute로 설정해 부모 요소의 정중앙에 아이콘이 올 수 있도록 합니다. 여기에서 'card__img'의 position을 'relatve'로 설정한 이유가 드러납니다. 이제 'top, left'를 '50%'로 설정하고, 'transform: translate()'속성을 이용해 기준축을 요소의 가운데로 설정합니다.
그 다음에는 transition인데, opacity 속성을 '0.5초'간격으로 서서히 바뀔 수 있도록 합니다. 이 역시 hover를 했을 때 드러나야 하기 때문에 opacity 속성을 0으로 설정합니다.
description: 이미지 아래에 들어있는 부분들을 총괄합니다. 완성된 웹페이지를 보면 밑에 선이 그어져 있는데, 해당 선의 윗부분(여기서 card__img는 제외합니다.)을 관리하는 영역입니다.
coin-info: description 내부에 들어있는 블록입니다. 0.041ETH와 3 days left가 들어가는 엘리먼트이며, 이 엘리먼트 내부에 스타일링을 할 요소가 많다고 판단되었기에 따로 블록을 만들었습니다.
코드의 모양은 다음과 같습니다. Block 내부에 또 다른 Block이 있다는 사실이 의아할 수도 있겠지만, 이런 방식으로 하는 것도 BEM에서는 얼마든지 가능합니다. 이 코드에서는 하나의 coin-info 내부에 두 개의 'coin-info__detail'이 들어있다는 사실을 알 수 있을 겁니다. 그 이유는 바로 왼쪽의 이더리움 부분과 오른쪽의 시간 부분이 서로 비슷한 디자인을 가지고 있기 때문입니다. 그런데 왼쪽의 '이더리움'이 형광색에 굵은 글씨를 가지고 있어 좀 더 강조되는 모양새를 가지고 있습니다. 따라서 이 스타일을 구현하기 위해 'coin-info__detail--highlight' 클래스를 추가합니다. 이 클래스는 '모디파이어'에 해당하며, BEM 메서드에서 이름을 작성하는 경우에는 '의미'를 강조하기 때문에 다음과 같이 시멘틱적인 구현을 하는 것이 좋습니다.
코드를 계속 읽어보시면, 'coin-info__detail'에 대해서 살펴볼 수 있습니다. 이 부분을 통해 아이콘을 먼저 배치하고 텍스트를 나중에 배치하고 있다는 사실을 알 수 있습니다.
그 다음으로는 본격적으로 'description' 클래스에 대한 css 설명입니다. 물론 이전에도 'align-items: center' 설정을 했지만, 혹시 몰라서 flexbox 내부에 들어있는 각각의 아이템에서 설정하는 'align-self' 속성을 'center로 설정'했습니다. width는 'img'에서 설정했던 것처럼 85%를 사용합니다.
그 다음에는 'description__title'에 대한 내용입니다. 이 부분은 flexbox 내부에 있기 때문에 'align-self'를 이용해서 'flex-start'를 진행할 수 있습니다. 결과적으로 '왼쪽'에 해당 엘리먼트가 배치됩니다.
'description__detail'의 경우에는 font-size, margin-bottom, line-height와 같은 기본적인 설정을 진행했습니다.
그 다음에는 'coin-info'와 'creation'에 대한 내용이었는데, coin-info와 creation 간의 간격을 넓히기 위해 'margin-bottom'을 1rem으로 설정했고, 'card__creation'의 경우에는 border-top을 설정해서 coin-info와 creation 사이의 희미한 선을 그어야 했습니다. 이를 위해 투명도를 0.2로 설정했습니다.
coin-info는 display 설정에 대한 값을 'flex'로 설정하고, align-items나 justify-content와 관련된 부분들을 모두 정중앙으로 배치했습니다. 그 다음에 'coin-info__detail'의 경우에는 display 속성의 값을 'flex'로 하고 마찬가지로 text-align을 center로 배치했는데, 아이콘과 텍스트 간의 간격을 넓히기 위해 'gap' 설정을 했습니다. 모디파이어에 해당하는 'coin-info__detail--highlight'는 강조를 하기 위해 형광색상에 글자의 굵기를 굵게 설정했습니다.
creation: 이 카드를 만든 사람의 이미지와 이름이 들어가는 구역을 관할합니다. 밑에 그어진 얇은 선의 밑부분에 해당한다고 생각하시면 됩니다.
그 다음에는 카드 영역의 가장 밑부분에 해당하는 'creation'입니다. 이 영역에서 들어가야 하는 순서는 왼쪽에서 오른쪽으로 '이미지 -> 텍스트(일반 텍스트->강조 텍스트)'의 순서대로 들어가야 합니다.
creation 영역 내부에 들어있는 각각의 엘리먼트들도 마찬가지로 가운데로 정렬을 해야 하기 때문에 'align-items'를 'center'로 설정해서 가운데로 놓았습니다. 그런데 한 가지 알아야 할 사항은 바로 creation 또한 card라는 하나의 flexbox 내부에 들어있는 아이템이라는 점입니다. 중요한 점은 바로 'creation'이라는 이름을 가진 클래스가 'align-self'를 통해 'center'로 두어야 border와 같이 희미한 선을 긋는 경우에도 가운데로 올 수 있도록 설정할 수 있다는 점입니다. 그러나 이미지와 텍스트의 경우에는 해당 엘리먼트의 시작점에 위치해 있어야 하기 때문에 'justify-content'를 start로 설정했습니다.
creation-img의 경우에는 creation 영역에서 가장 먼저 오게 되는 이미지에 대한 설정으로, 동그란 원의 모양을 만들고 여기에 'border'를 덧붙여 굵은 선을 만들었습니다.
attribution: 'footer 엘리먼트'로 감쌌으며, 챌린지를 제공해주신 사이트의 이름과 개발자의 이름(제 이름)을 적는 곳입니다.
이제 마지막으로 'footer 클래스'를 이용해서 margin을 설정합니다. margin을 '2rem auto'로 설정하게 되면 상하에 2rem의 간격을 줄 수 있는 것과 동시에 좌우로 정렬할 수 있도록 설정할 수 있습니다.
여기까지 코드 설명에 대한 부분을 전부 마치도록 하겠습니다.