const skillData = {
skillList : [
{
label : "HTML/CSS" ,
detail : [ "원하는 UI를 실용적이고, 익숙하게 만들어 낼 수 있습니다." , "빌드시스템(Gulp)과 템플릿 엔진(ejs, jade)의 사용이 가능합니다." , "Css 최신 문법이 숙지되어 있고 Css 프리프로세서 Sass를 사용할 수 있습니다." ],
icon : "fab fa-html5" ,
},
{
label : "jQuery" ,
detail : [ "다양한 속성과 메소드를 능숙하게 사용이 가능합니다." , "Ajax을 활용하여 비동기 통신으로 데이터 요청을 할 수 있습니다." , "jQuery UI 라이브러리를 활용할 수 있습니다." ],
icon : "fab fa-node-js" ,
},
{
label : "Javascript/Typescript" ,
detail : [ "ES6와 이후의 자바스크립트 문법을 사용할 수 있습니다." , "Vanilla JS의 웹 제작이 가능하고 필요 이유에 대해 충분히 이해하고 있습니다." , "타입에 대한 이해를 하고 있고, 인터페이스의 사용이 가능합니다." ],
icon : "fab fa-js-square" ,
},
{
label : "React" ,
detail : [ "컴포넌트 생명주기와 속성을 사용할 수 있습니다." , "class, function 컴포넌트의 차이를 이해하고 있으며 구분하여 사용할 수 있습니다." , "리덕스를 사용하여 상태관리를 할 수 있습니다." ],
icon : "fab fa-react" ,
},
{
label : "Node.js" ,
detail : [ "Node.js가 작동하는 법에 대해 이해하고 있습니다." , "express 프레임워크를 사용하여 Restful API 서버를 만들 수 있습니다." , "모델, 라우터, 컨트롤러, 서비스를 나누어 구조화된 서버를 만들 수 있습니다." ],
icon : "fab fa-node" ,
},
{
label : "Git/Github" ,
detail : [ "git flow가 무엇인지 알고 있으며 왜 사용해야 하는지 이해하고 있습니다." , "깃과 깃헙을 사용하여 다른 개발자들과 협업을 할 수 있습니다." , "깃 리베이스를 할 수 있으며 스쿼시를 통한 커밋 관리 경험이 있습니다." ],
icon : "fab fa-git-square" ,
},
],
experienceList : [
{
period : "2018.11 - PRESENT" ,
position : "WEB DEVELOPER" ,
company : "CONER CREATIVE" ,
explain : "에이전시 회사인 코너크리에이티브에서 사이트 제작과 유지보수 운영을 담당하였습니다. 삼성, 기아, 캐논 등 대기업 솔루션 경험을 익히며 폭넓은 인터렉션 제작 능력을 키웠습니다. " ,
},
{
period : "2018.09 - 2018.11" ,
position : "PUBLISHER" ,
company : "FIVESENSE SOFT" ,
explain : "웹 에이전시 오감소프트에서 퍼블리싱 작업을 담당하였습니다. 그누보드를 기반으로 개발된 솔루션을 활용하여 웹 사이트를 제작 및 유지운영을 하였습니다. 도메인 기관으로 고도몰을 사용하였고, 기본적인 php 문법을 숙지하였습니다. " ,
},
{
period : "2017.09 - 2018.03" ,
position : "E-BOOK DEVELOPER" ,
company : "THE MOUM" ,
explain : "플래시로 제작되었던 E-BOOK을 HTML을 기반의 웹 페이지 전자교과서 제작을 하였습니다. 지학사, 천재교육, 동아 출판사의 검정교과서 5권을 제작하였습니다." ,
},
],
educationList : [
{
period : "2019.08 - 2019.09" ,
position : "리액트(React.js)프로그래밍과정" ,
company : "한국소프트웨어인재개발원" ,
explain : "React.JS의 편리한 반응형 렌더링과 상태를 관리하는 캡슐화된 컴포넌트 개발 기술을 습득하였습니다. Vitual DOM을 활용한 랜더링의 비효율성을 최소화 하기 위하여 힘썼습니다." ,
},
{
period : "2019.07 - 2019.08" ,
position : "노드JS(Node.js)프로그래밍 과정" ,
company : "한국소프트웨어인재개발원" ,
explain : "Node.js를 이용하여 비동기 통신 방식의 자바스크립트 프로그램 제작에 대해 익혔습니다. MongoDB등을 연결하여 데이터의 처리와 분석능력을 배웠고 동적 브라우저 뷰 구축 능력을 개발하였습니다." ,
},
{
period : "2016.11 - 2017.05" ,
position : "(NCS)스마트웹&앱콘텐츠제작 양성과정" ,
company : "더조은컴퓨터학원" ,
explain : "GUI 디자인 가이드를 바탕으로 UI 구현 표준을 수립하고 UI를 제작하는 법을 학습하였습니다. 동시에 구현된 UI를 검증하기 위하여 사용성 테스트 계획, 수행, 분석, 결과 보고를 수행하는 역량을 길렀습니다." ,
},
],
};
export default skillData ;
idx
import React from "react" ;
import data from "../../assets/data/skilldata" ;
import styled from "styled-components" ;
import Heading from "../atoms/Heading" ;
function SkillsWrap () {
const skillsList = data . skillList . map (( list , idx ) => (
< div className = "skill-list" key = { idx } >
< Heading level = "3" className = "skill-label" >
< i className = { list . icon } ></ i > : { list . label }
</ Heading >
< ul className = "skill-detail" >
{ list . detail . map (( detail , idx ) => (
< li key = { idx } > { detail } </ li >
)) }
</ ul >
</ div >
));
return < StyledSkillsWrap > { skillsList } </ StyledSkillsWrap > ;
}
const StyledSkillsWrap = styled . div `
display : flex;
flex-wrap : wrap ;
.skill-list {
width : calc ( 33 % - 30 px);
margin : 0 15 px 30 px;
padding : 20 px 30 px 20 px 30 px;
border : 1 px solid #252525;
border-radius : 5 px;
cursor : default ;
.skill-label {
margin-bottom : 10 px;
i {
color : ${( props ) => props . theme . mainColor };
padding-right : 10 px;
font-size : 50 px;
vertical-align : text-top ;
}
}
.skill-detail {
li {
position : relative ;
padding : 0 0 10 px 30 px;
font-size : 14 px;
line-height : 1.5 ;
color : rgb ( 255 255 255 / 80 %);
&:: before {
content : "" ;
position : absolute ;
left : 0 ;
top : 13 px;
width : 20 px;
height : 1 px;
background : ${( props ) => props . theme . mainColor };
}
}
}
}
@media ${( props ) => props . theme . laptop } {
width : 85 %;
margin : auto ;
.skill-list {
width : calc ( 50 % - 30 px);
}
}
@media ${( props ) => props . theme . mobile } {
width : 90 %;
.skill-list {
width : calc ( 100 % - 30 px);
}
}
.skill-list {
position : relative ;
&:: before ,
&:: after {
content : "" ;
box-sizing : inherit ;
position : absolute ;
width : 100 %;
height : 100 %;
border : 1 px solid transparent ;
width : 0 ;
height : 0 ;
border-radius : 5 px;
}
&:: before {
top : 0 ;
left : 0 ;
}
&:: after {
bottom : 0 ;
right : 0 ;
}
&: hover {
&:: before ,
&:: after {
width : 100 %;
height : 100 %;
}
&:: before {
border-top-color : ${( props ) => props . theme . mainColor };
border-right-color : ${( props ) => props . theme . mainColor };
transition : width 0.15 s ease-out , height 0.15 s ease-out 0.15 s;
}
&:: after {
border-bottom-color : ${( props ) => props . theme . mainColor };
border-left-color : ${( props ) => props . theme . mainColor };
transition : border-color 0 s ease-out 0.3 s, width 0.15 s ease-out 0.3 s, height 0.15 s ease-out 0.45 s;
}
}
}
` ;
export default SkillsWrap ;
idx는 .map() 함수에서 현재 반복 중인 요소의 인덱스를 나타내는 변수입니다. 여기서 idx는 각 스킬 항목을 고유하게 식별하기 위한 키(key)로 사용됩니다.
React에서 리스트(배열)의 각 항목에 고유한 key 속성을 제공하는 것은 중요합니다. key는 React가 요소를 효율적으로 관리하고 업데이트할 수 있도록 돕는 역할을 합니다. React는 key를 사용하여 각 항목이 변경되었는지 식별하고, 변경된 항목만 다시 렌더링합니다.
따라서 idx는 map() 함수에서 현재 스킬 항목을 나타내는 요소의 인덱스로 사용되고, 각 스킬 항목을 고유하게 식별하기 위한 목적으로 사용됩니다. React 컴포넌트의 key 속성은 일반적으로 요소의 고유성을 나타내는데 사용되며, 데이터가 변경될 때 React가 컴포넌트를 올바르게 업데이트할 수 있도록 도와줍니다.
import React from "react" ;
import data from "../../assets/data/skilldata" ;
import styled from "styled-components" ;
import Heading from "../atoms/Heading" ;
function SkillsWrap () {
const skillsList = data . skillList . map (( list , idx ) => (
< div className = "skill-list" key = { idx } >
< Heading level = "3" className = "skill-label" >
< i className = { list . icon } ></ i > : { list . label }
</ Heading >
< ul className = "skill-detail" >
{ list . detail . map (( detail , idx ) => (
< li key = { idx } > { detail } </ li >
)) }
</ ul >
</ div >
));
return < StyledSkillsWrap > { skillsList } </ StyledSkillsWrap > ;
}
const StyledSkillsWrap = styled . div `
display : flex;
flex-wrap : wrap ;
.skill-list {
width : calc ( 33 % - 30 px);
margin : 0 15 px 30 px;
padding : 20 px 30 px 20 px 30 px;
border : 1 px solid #252525;
border-radius : 5 px;
cursor : default ;
.skill-label {
margin-bottom : 10 px;
i {
color : ${( props ) => props . theme . mainColor };
padding-right : 10 px;
font-size : 50 px;
vertical-align : text-top ;
}
}
.skill-detail {
li {
position : relative ;
padding : 0 0 10 px 30 px;
font-size : 14 px;
line-height : 1.5 ;
color : rgb ( 255 255 255 / 80 %);
&:: before {
content : "" ;
position : absolute ;
left : 0 ;
top : 13 px;
width : 20 px;
height : 1 px;
background : ${( props ) => props . theme . mainColor };
}
}
}
}
@media ${( props ) => props . theme . laptop } {
width : 85 %;
margin : auto ;
.skill-list {
width : calc ( 50 % - 30 px);
}
}
@media ${( props ) => props . theme . mobile } {
width : 90 %;
.skill-list {
width : calc ( 100 % - 30 px);
}
}
.skill-list {
position : relative ;
&:: before ,
&:: after {
content : "" ;
box-sizing : inherit ;
position : absolute ;
width : 100 %;
height : 100 %;
border : 1 px solid transparent ;
width : 0 ;
height : 0 ;
border-radius : 5 px;
}
&:: before {
top : 0 ;
left : 0 ;
}
&:: after {
bottom : 0 ;
right : 0 ;
}
&: hover {
&:: before ,
&:: after {
width : 100 %;
height : 100 %;
}
&:: before {
border-top-color : ${( props ) => props . theme . mainColor };
border-right-color : ${( props ) => props . theme . mainColor };
transition : width 0.15 s ease-out , height 0.15 s ease-out 0.15 s;
}
&:: after {
border-bottom-color : ${( props ) => props . theme . mainColor };
border-left-color : ${( props ) => props . theme . mainColor };
transition : border-color 0 s ease-out 0.3 s, width 0.15 s ease-out 0.3 s, height 0.15 s ease-out 0.45 s;
}
}
}
` ;
export default SkillsWrap ;
import React from "react" ;
import styled from "styled-components" ;
function PortfolioMini () {
const nodes = [] . slice . call ( document . querySelectorAll ( "li" ), 0 );
const directions = { 0 : "top" , 1 : "right" , 2 : "bottom" , 3 : "left" };
const classNames = [ "in" , "out" ] . map (( p ) => Object . values ( directions ) . map (( d ) => `${ p }-${ d }` )) . reduce (( a , b ) => a . concat ( b ));
const getDirectionKey = ( ev , node ) => {
const { width , height , top , left } = node . getBoundingClientRect ();
const l = ev . pageX - ( left + window . pageXOffset );
const t = ev . pageY - ( top + window . pageYOffset );
const x = l - ( width / 2 ) * ( width > height ? height / width : 1 );
const y = t - ( height / 2 ) * ( height > width ? width / height : 1 );
return Math . round ( Math . atan2 ( y , x ) / 1.57079633 + 5 ) % 4 ;
};
class Item {
constructor ( element ) {
this . element = element ;
this . element . addEventListener ( "mouseover" , ( ev ) => this . update ( ev , "in" ));
this . element . addEventListener ( "mouseout" , ( ev ) => this . update ( ev , "out" ));
}
update ( ev , prefix ) {
this . element . classList . remove ( ... classNames );
this . element . classList . add ( `${ prefix }-${ directions [ getDirectionKey ( ev , this . element )]}` );
}
}
nodes . forEach (( node ) => new Item ( node ));
return (
< StyledList >
< h2 > ETC </ h2 >
< ul >
< li >
< i className = "far fa-file-alt" ></ i >
< p > Samsung Galaxy-Watch, Buds </ p >
</ a >
</ li >
< li >
< i className = "far fa-file-alt" ></ i >
< p > Kia Seltos Unveiling </ p >
</ a >
</ li >
< li >
< i className = "far fa-file-alt" ></ i >
< p > Samsung Notebook </ p >
</ a >
</ li >
< li >
< i className = "far fa-file-alt" ></ i >
< p > Samsung Built-in Renewal </ p >
</ a >
</ li >
< li >
< i className = "far fa-file-alt" ></ i >
< p > Samsung 건조기 그랑데 </ p >
</ a >
</ li >
< li >
< i className = "far fa-file-alt" ></ i >
< p > 놀숲 </ p >
</ a >
</ li >
< li >
< i className = "far fa-file-alt" ></ i >
< p > 대신 엔터프라이즈 </ p >
</ a >
</ li >
< li >
< i className = "far fa-file-alt" ></ i >
< p > 안국이앤씨 </ p >
</ a >
</ li >
< li >
< i className = "far fa-file-alt" ></ i >
< p > 동아 도덕 </ p >
</ li >
< li >
< i className = "far fa-file-alt" ></ i >
< p > 천재 통합사회 </ p >
</ li >
< li >
< i className = "far fa-file-alt" ></ i >
< p > 지학사 영어 중등, 고등 </ p >
</ li >
< li >
< i className = "far fa-file-alt" ></ i >
< p > 지학사 영어 공통 </ p >
</ li >
</ ul >
</ StyledList >
);
}
export default PortfolioMini ;
const StyledList = styled . div `
max-width : 1140 px;
margin : auto ;
padding-bottom : 80 px;
h2 {
padding-top : 20 px;
margin-bottom : 40 px;
font-size : 26 px;
font-weight : 600 ;
text-align : center ;
position : relative ;
&:: after {
content : "" ;
position : absolute ;
top : 0 px;
left : 0 px;
right : 0 px;
margin : auto ;
width : 500 px;
height : 1 px;
background-color : rgb ( 37 , 37 , 37 );
}
}
ul {
display : flex;
flex-wrap : wrap ;
justify-content : flex-end ;
width : 80 %;
margin : auto ;
li {
width : 30 %;
margin-bottom : 4 px;
i {
margin-right : 8 px;
vertical-align : top ;
line-height : 26 px;
color : ${( props ) => props . theme . mainColor };
}
p {
display : inline - block ;
}
}
}
@media ${( props ) => props . theme . laptop } {
ul {
li {
width : 45 %;
}
}
}
@media ${( props ) => props . theme . mobile } {
h2 {
&:: after {
width : 70 %;
}
}
ul {
width : 75 %;
li {
width : 100 %;
}
}
}
` ;
이 코드는 React 컴포넌트인 PortfolioMini를 정의하고, 이 컴포넌트가 포트폴리오 항목을 나타내는 목록을 렌더링하는 역할을 합니다. 이 코드는 React 컴포넌트와 CSS 스타일을 결합하여 포트폴리오 항목을 표시하는 데 사용됩니다. 이 코드를 간단히 해석하겠습니다. PortfolioMini 컴포넌트는 StyledList 컴포넌트를 반환합니다. StyledList 컴포넌트는 CSS 스타일이 적용된 <div> 엘리먼트를 나타냅니다. 이 엘리먼트는 포트폴리오 항목 목록을 감싸는 컨테이너로 사용됩니다. <h2> 엘리먼트는 "ETC"라는 텍스트를 포함하며, 포트폴리오 항목 목록의 제목을 나타냅니다. 이 제목은 가운데 정렬되어 있으며 하단에 1픽셀 높이의 수평 선이 있는 스타일을 가지고 있습니다. <ul> 엘리먼트는 포트폴리오 항목의 목록을 나타냅니다. 이 목록은 가로로 표시되며, 화면 너비의 80%를 차지하며 가운데 정렬됩니다. 각 포트폴리오 항목은 <li> 엘리먼트 내부에서 구성됩니다. 각 항목은 가로 너비의 30%를 차지하며 간격을 가집니다. 각 포트폴리오 항목은 아이콘과 텍스트 링크를 포함하고 있습니다. <i> 엘리먼트는 아이콘을 나타내며, <p> 엘리먼트는 포트폴리오 항목의 이름 또는 설명을 표시합니다. 미디어 쿼리 (@media)를 사용하여 화면 크기에 따라 스타일이 다르게 적용됩니다. 예를 들어, 데스크톱 화면에서는 항목의 가로 너비가 30%로 설정되고, 모바일 화면에서는 가로 너비가 100%로 설정됩니다. 이 코드는 React와 styled-components를 사용하여 포트폴리오 항목 목록을 생성하고 스타일을 적용하는 예시입니다. 이 코드를 통해 포트폴리오 목록을 만들고 다양한 화면 크기에 대응하는 반응형 디자인을 구현할 수 있습니다.
이 코드는 CSS의 미디어 쿼리를 사용하여 화면 크기가 모바일 디바이스에 적용되는 스타일 규칙을 정의합니다. 아래에서 각 부분을 자세히 해석하겠습니다.
@media ${(props) => props.theme.mobile}: 이 부분은 미디어 쿼리를 시작하는 부분입니다. 이 미디어 쿼리는 props.theme.mobile 조건이 참인 경우에만 실행됩니다. 이것은 주로 React 컴포넌트의 props 중에 theme이라는 속성을 통해 화면 크기에 대한 정보를 전달하는 방식으로 사용됩니다.
h2 { ... }: 이 부분은 미디어 쿼리 내부에서 h2 태그에 적용되는 스타일을 정의합니다. 즉, 화면 크기가 모바일 디바이스일 때 h2 태그에 스타일을 적용합니다.
&::after { ... }: h2 태그 내부 에 있는 ::after 가상 요소에 대한 스타일을 정의합니다. ::after는 요소 내의 가상으로 생성된 콘텐츠를 스타일링하는 데 사용됩니다.
width: 70%;: 모바일 화면에서 h2 태그의 가상 요소인 ::after의 너비를 70%로 설정합니다. 이로써 가상 요소의 가로길이가 h2 태그의 가로길이의 70%가 됩니다.
ul { ... }: 미디어 쿼리 내부에서 ul 태그에 적용되는 스타일을 정의합니다.
width: 75%;: 모바일 화면에서 ul 태그의 가로길이를 75%로 설정합니다. 이로써 목록의 가로길이가 화면의 75%가 됩니다.
li { ... }: 미디어 쿼리 내부에서 li 태그에 적용되는 스타일을 정의합니다.
width: 100%;: 모바일 화면에서 li 태그의 가로길이를 100%로 설정합니다. 이로써 각 항목이 목록의 전체 가로 공간을 차지하게 됩니다.
이렇게 설정된 미디어 쿼리는 화면 크기가 모바일 디바이스인 경우에만 적용되며, 해당 화면 크기에서는 h2, ul, li 태그의 스타일이 변경됩니다.