사실 HTML에서는 그렇게 특별하게 설명할 거 없다. CSS의 경우 드롭다운 메뉴에서 평소에 안 보이는... 그러니까 위키피디아로 치자면 언어 변경을 눌렀을 때 나오는 수많은 언어들은 평소에 안 보였다가 우리가 버튼을 누르면 그 때 나와야 하잖아요? 그것때문에 하위 메뉴에 display=block을 준다. 자바스크립트 없이 드롭다운 메뉴를 만들때도 마찬가지.
내 10년 전쯤 드롭다운 메뉴 갖고 끙끙거릴때는 드롭다운 메뉴 만들라면 position 앱솔루트 릴레이티브 막 주고 플로트 주고(왼쪽으로) 디스플레이 블록이여 인라인이여 몇픽셀이여… 이러면서 거의 사투를 벌였는데 이거는 지금도 크게 다르지는 않다. 대신 지금은 float: left; 주는 대신 display: flex; 주고 방향이랑 정렬만 정하면 돼서 편할…거라고 생각했는데 아니더라…
이게 그 부분 관련된 CSS이다. 이거는 자바스크립트로 제어할거라 드롭다운 메뉴에 들어갈거랑 메뉴 링크만 넣었는데, CSS만으로 할 때는 .dropdown-contents:hover로 display: block 줘야 한다. 이건 무슨 소리냐면 마우스를 올렸을 때 보여줘라 이런 얘기인데, 회사나 단체의 홈페이지 상단에 쓰이는 드롭다운 메뉴는 대부분 이런 식이다.
일단 원문하고 다른 점을 몇가지 서술하자면, 원문에는 메뉴 옆에 삼각형이 있어서 클릭할때마다 바뀐다. 근데 내가 만든 드롭다운 메뉴에는 그 삼각형이 없기때문에 그거 관련된 코드가 없다. 그리고 원문에는 var로 가져오는데, 본인은 DOM 가져올 때 따로 생성할 거 외에 내용이 딱히 바뀌는 게 없겠다 싶으면 다 const로 가져온다. 그래도 되냐고요? 저거 한번에 됐는데요?
만약 본인이 이미지도 같이 올릴거다 그러면 이런 식으로 배경이미지 지정해서 써도 된다. 이건 체크 전/체크 후 둘 다 가능.
그럼 오늘 배운 걸 응용해서 뭔가 만들어보자. 오늘 만들 건 파스타 재료 픽업용 페이지인데… 아니 이거 집에서 만드는거니까 오해는 하지 말고.
본인 블로그나 인별을 한번이라도 봤다면 알겠지만 본인 집에서 해먹는 파스타는 면이 착한 사람만 보이는 건더기가 급나 완전 핵쌉많은 파스타다. 그리고 건더기 종류는 보통 금요일에 결정해서 장 봐서 토요일에 해먹는 편... 물론 엄마 의견도 반영된 게 함정. 아무튼, 파스타에는 면 1종류(양심에디션/밀가루에디션)와 소스 1종류(그때그때 다름), 건더기 최대 5종까지를 넣는다. 그래서 뭐 만들길래 파스타 얘기냐고? 이거요.
위에서도 설명했듯 면 하나, 소스 하나에 건더기 급나 퍼붓는 구조이기 때문에 면과 소스는 하나씩만 고를 수 있다. 그리고 건더기는 뭐 본인 위장이 허락하는 한 뭐… 알아서 넣으시고…
이거는 실시간 시계를 만들기 위해 필요하다. setInterval()을 통해 현재 시각을 가져오는 함수와 그걸 십이지시로 바꾸는 함수를 1초 간격으로 실행하는거다. 아래꺼는 원래 setInterval() 안했었는데 10시에서 11시 넘어가는데 십이지시가 자동으로 안바뀌더라고.
일단 미리 말씀드리고 갈 부분이 있다. 자바스크립트 코드가 내가 원했던 로직이 아니기때문에 테마가 추가되거나 할 때 if문이 아주 덕지덕지 붙어서 뵈기 싫은데 이걸 대체 어떻게 깔끔하게 해야 할 지 모르겠다. 이건 나중에 방법 찾으면 보완할 예정.
HTML이나 CSS는 걍 깃헙 가서 보는 게 편한데, 기존에 체크박스나 라디오버튼과 달리 이번에는 라벨태그를 추가했다. 라벨태그는 <label>아무개</label> 이런 식으로 쓰는 태그인데, label for=""에 라디오버튼이나 체크박스의 아이디를 넣으면 꼭 그 라디오버튼이나 체크박스를 정확하게 클릭하지 않더라도(글자를 눌러도) 선택이 되게끔 해 주는 매우 편한 태그다. 특히 화면이 작은 모바일에서 말이지.
그리고 각 테마별로 배경색과 글자색을 추가했다. 순서대로 반다이크 브라운, 프러시안 블루, 비리디언, 울트라바이올렛. 이름에서 짐작하셨겠지만 순서대로 갈색/파란색/초록색/보라색 계통이다. 그리고 아이보리 블랙은 골탄이라고도 부르는데 반다이크 브라운과 프러시안 블루를 1:1로 섞으면 나오는 검정색. #000000과는 다르다. 실기 할 때는 섞어서 썼었는데 걍 물감 사서 쓰십쇼… 비율 안 맞으면 똥망이니까…
위에 네 줄은 가져오는거니까 패스. ForEach가 보면 if문 안쪽에 하나씩 있는 게 있고 그 바깥쪽에 있는 ForEach문이 또 있을 것이다. 위에 selectTheme가 테마 옵션을 선택할 라디오 버튼, 그러니까 위 사진에 있는 동글뱅이들인데… 그렇죠. 동글뱅이'들'이니까 단체로 가져왔을거고, 당연하지만 이거 배열이기 때문에 반복문을 돌려야 쓸 수 있다. 밖에 있는 ForEach문은 그래서 있는거다.
안쪽에 있는 ForEach문은 컨텐츠라는 클래스를 갖는 P태그가 여러개이기 때문에 거기에 전부 클래스를 붙여야 해서 존재하는 거다. 요소가 여러개라 배열로 가져오기 때문에 반복문 없으면 클래스가 가장 첫 번째 요소에만 붙거나 떨어지기 때문.
여기 이 모든 if문이 default를 선택했을 때에 대한 if문이다. default(기본 테마)를 선택했고 클래스가 프러시안을 포함할 때(프러시안 블루 테마가 선택되어 있었을 때) 프러시안 클래스를제거한다. default를 선택했고 클래스가 반다이크를 포함할 때 반다이크 클래스를 제거한다. default를 선택했고 클래스가 비리디언을 포함할 때 비리디언 클래스를 제거한다. default를 선택했고 클래스가 울트라바이올렛을 포함할 때 울트라바이올렛을 제거한다.
이제 뭐가 문제인 지 보이는가? 이거 사실 default를 선택했고 뭐가 됐든 클래스가 '붙어 있으면' 그걸 떼기만 하면 장땡이다. 저렇게 if 줄줄이 달 필요가 없다 이거임. 그리고 다른 걸 선택했을 때도 마찬가지다. 반다이크 브라운에서 울트라 바이올렛으로 넘어가면 반다이크 클래스를 떼고 울트라 바이올렛 클래스를 붙여야 하는데 저것도 골치아파서 다른 선택지에는 붙어있으면 떼십쇼 이거를 추가를 못 했어요 지금...
그리고 내가 언젠가 개선하겠다고 한 부분이 이것과 관련 있는 것이다. 예를 들어서 내가 비리디언 테마를 선택했다, 그러면 default에서 바로 선택한거면 그냥 viridian 클래스를 붙이면 된다. 하지만 다른 테마에서 비리디언으로 변경하는 거면 어떤 테마에서 선택하건간에 해당하는 클래스를 '떼버리고' 비리디언 클래스를 붙이는 걸 하고 싶은거다.
테마는 순서대로 기본(아이보리 블랙)-반다이크 브라운-프러시안 블루-비리디언-울트라바이올렛. 왜 소스 코드에 프러시안 블루가 먼저 있었냐면 이게 잘 됐나 봐야 하는데 아이보리 블랙이랑 반다이크 브라운이랑 구별이 잘 안 돼서...
자바스크립트의 시작은 항상 조작할 DOM을 가져오는 것... 아래 세 줄은 1번에서는 신경쓰지 않아도... 아니 네번째줄은 필요하고 세번째줄이랑 다섯번째줄이 나중에 봐도 되는거임. 아무튼 기본 로직에서 필요한 건 버튼이랑 길이값(아이디 길이), 그리고 아이디 만들어서 붙일 공간이다.
button.addEventListener('click',(e) => {
let idLength = idLengthval.value;
let idText = "";
let word_list = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!@#$%^&*";
let idValue = document.createElement("p");
//자바스크립트는 아스키코드 못다루나?
for (let i = 0;i < idLength;i++) {
idText += word_list.charAt(Math.floor(Math.random() * word_list.length));
idValue.innerText = idText;
idValarea.appendChild(idValue);
}
})
아니 궁금해서 그러는데... 파이썬은 아스키코드로 리스트 만들 수 있는데 JS는 그거 안됨? 아니 되지 않아요? 안될리가 엄서... 아무튼. 기본 로직은 저 리스트 중에서 랜덤으로 하나를 뽑아서 내가 원하는 길이(위에 입력한 그 길이)만큼 만드는거다. 반복문은 장식이 아니고 아이디 길이때문에 들어간거다.
버튼이 querySelectorAll인데는 이유가 있는 법이니라... 근데 아이디를 div에 p태그로 갖다 붙이는 형태라 아무리 생각해도 이걸 한꺼번에 지울 수가 없는거다. 그래서 걍 페이지 새로고침함... ㅋㅋㅋㅋㅋㅋ 아니 새로고침해도 지워져요 지워지기는 ㅋㅋㅋㅋㅋㅋ
아이디를 원하는 길이만큼 원하는 개수로 만들기
function makeID() {
let idLength = idLengthval.value;
let idText = "";
let word_list = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!@#$%^&*";
let idValue = document.createElement("p");
//자바스크립트는 아스키코드 못다루나?
for (let i = 0;i < idLength;i++) {
idText += word_list.charAt(Math.floor(Math.random() * word_list.length));
idValue.innerText = idText;
idValarea.appendChild(idValue);
};
}
이걸 하려면 일단 위에 저 로직을 함수로 빼버리는 게 좋다. 안 빼도 할 수는 있는데 하다가 님들 멘탈 나가고 정신없으니까 빼시는게 정신건강에 이롭습니다. 왜냐하면 아이디를 원하는 개수만큼 만들려면 가장 원시적이면서 간단한 방법이 반복문 뺑뺑이거든…
button[0].addEventListener('click',(e) => {
let idCountvalue = idCount.value;
for (let i = 0;i < idCountvalue;i++){
makeID();
};
});
그래서 사실 여기까지는 쉽다. 이 다음이 문제지…
아이디 만들 때 쓸 문자 범위 정하기
이게 뭔 소리냐… 생각해봅시다. 나는 아이디 만들 때 특수문자를 안 넣을거다. 근데 위에 로직에 있는 리스트에는 특문이 들어가있거든요? 그러면 좋든 싫든 특수문자를 넣어야되잖아요. 안 들어갈 수도 있지만 랜덤이라는 건 그런겁니다. 그니까 아예 랜덤으로 만들되 범위를 정하자 이 얘깁니다.
저기 Generate 버튼 밑에 있는 건 아이디 만들면 커지니까 걱정 마시고… 위에 있는 체크박스가 아이디 만들 때 쓸 문자 옵션이다. 그러니까 특문 안 넣을거면 특문 빼고 다 고르면 된다.
function makelist() {
let word_list = "";
for (let i = 0;i < wordOption.length;i++) {
if (wordOption[0].checked) {
word_list += "0123456789"
}
else if (wordOption[1].checked) {
word_list += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
}
else if (wordOption[2].checked) {
word_list += "abcdefghijklmnopqrstuvwxyz"
}
else if (wordOption[3].checked) {
word_list += "!@#$%^&*"
}
}
return word_list
}
근데 이렇게 하니까 여러개를 골랐는데도 하나만 들어가는거다. 그니까 영소 영대를 골랐는데 영어 소문자로만 아이디를 만들어주잖아욧. 그래서 구글링을 했는데 대부분 HTML단에서 Value 정해두고 그거 가져오는 것만 있더라고... 원래 하려던건 체크박스 값에는 말 그대로 옵션명(숫자/영소/영대/특문)만 넣어두고 자바스크립트단에서 목록 만드는거였는데... 혹시 방법 아시는 분 제보좀 주세요.
아무튼 value에서 갖다 쓰는 방법만 있고 시간도 새벽이라 나도 걍 value에 때려박고 갖다 쓰기로 했음. 그래서 체크박스에는 각 옵션에 해당하는 문자가 들어갑니다. 특수문자는 !@#$%^&*까지만 있음.
function makelist() {
let word_list = "";
wordOption.forEach((item) => {
if (item.checked) {
word_list += item.value
}
})
return word_list
}
그리고 회심의 ForEach 들어갑시다. 저건 또 왜 함수로 뺐냐면 위에서 아이디 반복문으로 돌리려고 뺀거에 리스트가 있었기 때문이다. 그때는 고정값이었지만 이제 옵션에 따라 리스트를 만들거란말이죠. 근데 리스트 만드는거를 아이디 만들때마다 일일이 만들거임? 옵션도 같은데?? 난 그러고 싶지 않아...
const button = document.querySelectorAll('button');
const idLengthval = document.querySelector('.idlength');
const idCount = document.querySelector('.idhowmany');
const idValarea = document.querySelector(".ID");
const wordOption = document.querySelectorAll(".listopt");
function makelist() {
let word_list = "";
wordOption.forEach((item) => {
if (item.checked) {
word_list += item.value
}
})
return word_list
}
function makeID() {
let idLength = idLengthval.value;
let idText = "";
let word_list = makelist();
let idValue = document.createElement("p");
//자바스크립트는 아스키코드 못다루나?
for (let i = 0;i < idLength;i++) {
idText += word_list.charAt(Math.floor(Math.random() * word_list.length));
idValue.innerText = idText;
idValarea.appendChild(idValue);
};
}
button[0].addEventListener('click',(e) => {
let idCountvalue = idCount.value;
for (let i = 0;i < idCountvalue;i++){
makeID();
};
});
button[1].addEventListener('click',(e)=>{
location.reload(true);
});
let operatorOn = '';
let prevNum = '';
let nextNum = '';
//순서대로 연산자, 앞의 숫자, 뒤의 숫자
let calculate = (n1, operator, n2) => {
let result = 0;
switch (operator) {
case '+':
result = prevNum + nextNum;
break;
case '-':
result = prevNum - nextNum;
break;
case 'X':
result = prevNum * nextNum;
break;
case '/':
result = prevNum / nextNum;
break;
default:
break;
}
return String(result);
}
//입력받은 값을 연산자에 따라 계산한다
생각해봅시다. 계산이라는 건 연산자를 사이에 두고 앞에 숫자가 있고 뒤에 숫자가 있잖아요? 예를 들어서 2+2면 2 더하기 2 이런 식으로. 이거는 숫자 두 개(버튼 누르면 된다)랑 연산자가 정해지면 계산해서 결과 내놔라 이런 얘기다. 참고로 밑에서 한번 더 서술하겠지만 따로 0으로 나누는것에 대해 대비를 하지 않아도 된다.
내 여기서 개고생한것이다… ㅡㅡ 여기가 바로 계산기 버튼을 누르는 것에 반응하는 부분이다. 그러니까 봐봐요? 2+2면 2 누르고, + 누르고, 2를 또 누를 거 아닙니까. 바로 그거다. 앞이랑 뒤를 어떻게 나누는지 컴퓨터한테 가르쳐주는거다.
buttons.forEach((item) => {
item.addEventListener('click', (e) => {
let action = e.target.classList[0];
let click = e.target.innerHTML;
ForEach가 왜 있냐면 맨 위에서 가져올 때 숫자 표시할 창을 뺀 다른 요소들을 전부 document.querySelectorAll로 가져왔다. 그러면 이거 배열이라 반복문이 있어야 안에 있는 걸 꺼내올 수 있다. 오케이? 그니까 쉽게 말하자면 이 두 줄을 통해 당신이 누른 버튼의 클래스와 내용물을 가져오는거다.
정리하니까 탭이 가출했는데 아무튼… action에서 클래스를 가져왔는데 이게 operator, 그러니까 연산자면 앞에 숫자가 끝난거니까 끊고 뒤에꺼 가져와라 이 얘기. 여기 있는 isFirstDigit이라는 변수는 숫자가 0으로 시작할 때, 그러니까 0020, 003같은 애들을 20이랑 3으로 인식하게 해 준다. 뒤에 숫자를 입력할때도 저게 그대로 적용되기때문에 0이 아닌 다른 숫자로 시작할때까지 걍 입력중이라 0으로 나누는 것에 대해 굳이 처리할 필요가 없는 것.
위에 설명했던 첫 자리수가 0이면 무시하는 기능, 그러니까 0001을 1로 인식하는 기능이 위에 네 줄이다. 아래는 쉽게 설명하자면 분기점에 따라 앞의 수인지 뒤에 수인지를 나누는거라고 보시면 된다. display는 버튼 입력하면 숫자가 보이는 부분을 의미한다. 일단 operatorOn이 공백이 아니라는 건 일단 연산자를 입력했다는 얘기이기 때문에 operatorOn이 공백인 상태에서는 앞의 숫자가 되고, operatorOn이 공백이 아닐 때는 뒤의 숫자가 된다. 왼쪽 조건은 말 그대로 이미 입력된 숫자가 있으면 거기에 갖다 붙이라는 얘기. 2를 누르고 5를 누르면 25로 입력된다.
그러고보니 함수가 둘다 김람다씨네… 아무튼. 우리 여기서 한번 생각해봅시다. operator는 따로 가져왔고, 숫자도 따로 가져왔는데 결과 보는거랑 AC버튼은 왜 안 가져왔는지 궁금했던 사람 있을거다. 일단 내가 그랬음. 어차피 위에서 버튼 전체를 가져왔기때문에 굳이 변수로 따로 가져오지는 않았다. 그리고 버튼들에서 결과 보기를 누르면 위에 만들었던 앞에꺼 연산자 뒤에꺼 계산하는 기능과 AC를 누르면 초기화하는 기능이다.
const buttons = document.querySelectorAll("button");
const operators = document.querySelectorAll(".operator");
const display = document.querySelector(".display");
const numBtn = document.querySelectorAll(".numBtn");
// 순서대로 1. 모든 버튼'들'. 2. 더하기 빼기 곱하기 나누기 =, 3. 결과창, 4. 숫자 버튼
let operatorOn = '';
let prevNum = '';
let nextNum = '';
//순서대로 연산자, 앞의 숫자, 뒤의 숫자
let calculate = (n1, operator, n2) => {
let result = 0;
switch (operator) {
case '+':
result = prevNum + nextNum;
break;
case '-':
result = prevNum - nextNum;
break;
case 'X':
result = prevNum * nextNum;
break;
case '/':
result = prevNum / nextNum;
break;
default:
break;
}
return String(result);
}
//입력받은 값을 연산자에 따라 계산한다
let calculator = () => {
let isFirstDigit = true;
buttons.forEach((item) => {
item.addEventListener('click', (e) => {
let action = e.target.classList[0];
let click = e.target.innerHTML;
if (action === 'operator') {
operatorOn = click;
prevNum = display.textContent;
display.textContent = '';
isFirstDigit = true;
}
if (action === 'numBtn') {
if (isFirstDigit && click === '0') {
return;
}
if (display.textContent === '' && operatorOn === '') {
display.textContent = click;
prevNum = display.textContent;
} else if (display.textContent !== '' && operatorOn === '') {
display.textContent = display.textContent + click;
prevNum = display.textContent;
} else if (display.textContent === '' && operatorOn !== '') {
display.textContent = click;
nextNum = display.textContent;
} else if (display.textContent !== '' && operatorOn !== '') {
display.textContent = display.textContent + click;
nextNum = display.textContent;
}
isFirstDigit = false;
}
if (action === 'result') {
display.textContent = calculate(prevNum, operatorOn, nextNum);
isFirstDigit = true;
}
if (action === 'ac') {
display.textContent = '';
prevNum = '';
operatorOn = '';
nextNum = '';
isFirstDigit = true;
}
});
});
};
calculator();
사실 전체 코드에는 맨 밑에 한줄이 더 있는데 설명할 때 굳이 언급할 필요는 없어보여서 걍 언급 안했다. 그죠 함수 짰으면 불러야 일하죠.
UI/UX 그리고 기능 하나 만들때는 정말 여러 경우의 수를 고려해야 한다. 내 팩토리얼 짠거 보면 양수 팩토리얼 나오지 0! 나오지 소수점(정확히는 정수가 아닌 유리수) 감마펑션 때렸지 음수는 응 못해~ 했잖음? 세상은 넓고 사람들은 다양하므로 급나 나노 반도체 단위로 고려해야 할 게 많다. 잔걱정 많은 성격이 이럴 때는 도움이 된다니까...
아무튼. 미래 날짜라는 게 무슨 소리냐... 오늘이 2024년 1월 16일인데 생일이 2024년 1월 30일이면 아직 안 태어난거다 그죠? 무슨 백투더퓨처도 아니고. 막말로 내가 91년생이면 90년에는 엄마 반 아빠 반이거나 엄마 뱃속에 있거나 둘 중 하나였음.
calButton.addEventListener('click',function(event){
if (todayMonth > new Date(birthday.value).getMonth() + 1) { //현재 날짜의 월이 생일보다 크다면
//만나이 겟또
yourAge.innerText = `만 ${todayYear - new Date(birthday.value).getFullYear()}세입니다. `;
yourAge_div.appendChild(yourAge);
}
else if (todayMonth == new Date(birthday.value).getMonth() + 1 && todayDate >= new Date(birthday.value).getDate()) { //현재 날짜의 월이 생일과 같'고' 현재 날짜의 일이 생일보다 같거나 크다면
//만나이 겟또
yourAge.innerText = `만 ${todayYear - new Date(birthday.value).getFullYear()}세입니다. `;
yourAge_div.appendChild(yourAge);
}
else if (todayYear - new Date(birthday.value).getFullYear() -1 < 0) { //생년월일로 미래의 날짜를 입력한다면?
alert('미래 날짜로 만 나이를 계산할 수 없습니다!') //응 계산 못해요
}
else { //아녀?
//응 멀었어
yourAge.innerText = `만 ${todayYear - new Date(birthday.value).getFullYear() - 1}세입니다. `;
yourAge_div.appendChild(yourAge);
}
})
탭 정리하기 귀찮아서 일괄로 넣었는데, 저기 세번째 else if를 보면 다른 if들과 달리 innerText가 아니고 alert가 들어가 있다. 위에도 썼지만 생일이 현재 날짜보다 미래라는 건 아직 태어나지 않았다는... 그니까 엄마 반 아빠 반이거나 아직 뱃속에 있을 시기니까 만 나이라는 개념이 있을 수가 없다.
사실 저거 if를 또 몇개나 들이 부어야 하나 했는데 미래 날짜로 하면 만나이 걍 음수되더라… 그래서 만나이 계산했는데 음수뜨면 알림창 ㄱㄱ하게 했다. 그거 말고 뭐 크게 수정된 건 없음.
이게 왜 전후편이 나뉘었냐면 변수때문에 시간을 너무 잡아먹었고 나는 출근을 해야 하는 몸입니다... 그래서 자잘한 것들... 뭐 CSS 마진 패딩 이딴거라던가 날짜에 시작점 끝점 정하는 뭐 그런거 있죠? 그런게 아직 안됐음... 그래서 깃헙에 올린 버전은 '일단 계산만 해 주는' 버전입니다. 아무튼 그럼.
사실 기본적인 로직은 파이썬하고 다를 게 없다. 그리고 자바스크립트에서도 날짜를 다룰 수 있지.
위에서부터 순서대로 입력창(날짜), 버튼, 만나이를 표시하기 위한 p태그와 갖다붙일 div이다. 여기까지는 순조로울 거 없죠? 죄다 const로 가져와서 재선언 재할당 안된다. ...근데 어떻게 내용물은 바꾸는겨?
// 현재 날짜
const today = new Date;
let todayYear = today.getFullYear();
let todayMonth = today.getMonth() + 1;
let todayDate = today.getDate();
이것도 어려울 거 없다. 만나이를 현재 나이 기준으로 계산할거니까 당연히 오늘 날짜를 가져와야 하는 게 맞다. 그래서 연월일 가져왔다. 시요? 에이 그런건 필요엄서. 뭐 내가 오후 8시 50분에 태어났다고 생일날 20시 50분 될때까지 만나이 안오르는거 아니다. 만나이는 0시 되면 칼같이 오른다.
calButton.addEventListener('click',function(event){
if (todayMonth > new Date(birthday.value).getMonth() + 1) { //현재 날짜의 월이 생일보다 크다면
//만나이 겟또
yourAge.innerText = `만 ${todayYear - new Date(birthday.value).getFullYear()}세입니다. `;
yourAge_div.appendChild(yourAge);
}
else if (todayMonth == new Date(birthday.value).getMonth() + 1 && todayDate >= new Date(birthday.value).getDate()) { //현재 날짜의 월이 생일과 같'고' 현재 날짜의 일이 생일보다 같거나 크다면
//만나이 겟또
yourAge.innerText = `만 ${todayYear - new Date(birthday.value).getFullYear()}세입니다. `;
yourAge_div.appendChild(yourAge);
}
else { //아녀?
//응 멀었어
yourAge.innerText = `만 ${todayYear - new Date(birthday.value).getFullYear() - 1}세입니다. `;
yourAge_div.appendChild(yourAge);
}
})
긍꼐 문제가 어디다? 저기다.
일차적인 문제로, 저 인풋.value로 값을 가져오면 잘 되는데 인풋.value를 변수에 할당하면 object가 되고 Invalid Date가 뜨고 아주 난리 부르스를 추는데 해결방법이 없어 죄다 IE에서 에러터졌을 때 얘긴데 얘는 또 console.log로 확인해보면 yyyy-mm-dd로 월 일 다 두자리다. 근데 위에도 썼지만 내일 출근해야돼서 오래 잡지를 못하는거라. new Date(인풋.value)는 되니까 그냥 저기다 때려박았기 때문에 파이썬과 달리 전체 코드에서 생년월일 관련 변수만 없다.
그리고 거기서 오는 2차적인 문제가 바로 한눈에 알아보기 개같이 어려운 IF문이다. 파이썬이었으면 가로 스크롤따원 필요없었는데 쟤는 변수에 생년월일 갖고 온 게 할당이 안 돼서 IF문 조건이 저 꼬라지가 된 것. 저것때문에 로직짜면서 계산 이상하게 됐는데 당최 뭐가 뭔지 찾기가 힘들어서 골머리 앓았는데 알고보니 월 계산할 때 +1 안 붙여서 저렇게 된거였음... 여러분은 조심하십셔.
생일이 지난 경우(인데 1월이면 뭐...)
월은 같은데 아직 생일이 안 지난 경우
그냥 안 지났다.
후편에서 할 것은 크게
1. 현재 날짜 표시(현재 날짜는 0000년 00월 00일입니다!). 이거에 따라 출력하는 텍스트 역시 바뀔 예정.
2. 날짜 입력 제한 걸기(1900-1-1~2900-12-31)
3. 생년이 현재 날짜보다 뒤일때에 대한 처리. 아직 태어나지 않은 사람은 만 나이를 계산할 수 없기 떄문에 여기에 대한 처리도 해야 한다. 즉, 저 꼬라지 난 IF문에 일단 else if 하나가 더 추가될 예정. 아마 지금은 계산하면 음수가 나올 것이다.
여기서 위의 두 줄은 캔버스(HTML에 캔버스가 또 있다... 뭔지는 모름)를 가져오는거고 아래 세 줄은 내릴 눈의 크기, 속도, 방향을 결정하는 함수다.
const Snow = {
data: [],
canvasWidth: $canvas.clientWidth,
canvasHeight: $canvas.clientHeight,
init() {
Snow.make();
Snow.loop();
},
loop() {
Snow.move();
Snow.draw();
window.requestAnimationFrame(Snow.loop);
//얘를 이용해서 재귀를 돌린다
},
make() {
//눈 데이터를 만드는 함수
const data = [];
for (let i = 0; i < 250; i++) {
const x = Math.random() * Snow.canvasWidth;
const y = Math.random() * Snow.canvasHeight;
const size = getRandomRadius();
const speed = getRandomspeed();
const dir = getRandomDir();
data.push({ x, y, size, speed, dir });
}
Snow.data = data;
},
move() {
//방향에 맞게 이동
Snow.data = Snow.data.map((item) => {
item.x += item.dir * item.speed;
item.y += item.speed;
//캔버스를 벗어났나요?
const isMinOverPositionX = -item.size > item.x;
const isMaxOverPositionX = item.x > Snow.canvasWidth;
const isOverPositionY = item.y > Snow.canvasHeight;
//벗어났다면 반대방향, 맨 위로
if (isMinOverPositionX || isMaxOverPositionX) {
item.dir *= -1;
}
if (isOverPositionY) {
item.y = -item.size;
}
return item;
});
},
draw() {
//CSS에 배경색 설정이 따로 안됐던게 이것때문이었고만?
ctx.clearRect(0, 0, Snow.canvasWidth, Snow.canvasHeight);
ctx.fillStyle = '#010101';
ctx.fillRect(0, 0, Snow.canvasWidth, Snow.canvasHeight);
Snow.data.forEach((item) => {
ctx.beginPath();
ctx.fillStyle = 'rgba(255,255,255,.6)';
ctx.arc(item.x, item.y, item.size, 0, Math.PI * 2); //눈을 원형으로 만들어준다. 없으면 네모눈이 내리나?
ctx.fill();
ctx.closePath();
});
},
};
이쪽이 메인디쉬인 Snow 함수. 여기에 눈을 뿌리기 위한 모든 요소가 들어간다. 일단 다섯개의 함수로 구성되어 있는데, 위쪽부터 순서대로 1) 눈을 만드는(init) 함수(얘는 뭐 하는건 없고 init() 호출하면 make랑 loop가 세트로 소환된다), 2) 재귀 돌리는 함수, 3) 눈 데이터를 만드는 함수(저기 for문 안에 250을 바꾸면 눈 갯수가 바뀐다), 4) 눈이 제대로 이동하게 해 주는 함수, 5) 배경 채우고 눈을 원형으로 만들어주는 함수 이렇게 다섯개가 있다.
보면 위에 두개는 단촐한데 밑에 세개는 어우... 뭐가 많다 그죠? 일단 make는 말 그대로 눈 데이터를 만드는 함수이다. 저기 for문에 있는 250을 다른 숫자로 바꾸면 눈 개수가 변화하는데 한 1000정도 넣어두면 저기 어디 시베리아 평원에서 흩날리는 눈폭풍을 볼 수 있다. 눈의 크기, 방향, 속도는 위에 짜 둔 함수가 랜덤으로 정해주는데 이 함수의 수치를 조절하면 눈의 크기, 속도, 방향도 제어가 가능하다.
move는 눈이 제대로 이동하게 해 주는 함수인데, 이게 뭔 소리냐면 눈이 내리다보면 화면 밖으로 나가게 된다. 그러면 그대로 빠이짜이찌엔 안녕히계세요 여러분 하는 게 아니라 방향을 잡아주는 역할을 하는 함수이다. 나도 처음에 보고 뭔소린가 했는데 코딩하면서 설명 보고 납득함.
draw에서 볼 것은 fillstyle와 arc이다. fillstyle은 배경색을 채워주는 것이고, 그래서 어두운 배경에 눈이 흩날리고 있음에도 CSS에는 아무것도 없이 오버플로우 숨기라는 것만 되어 있었다. arc은... 이게 있어야 눈이 둥글어진다.
그 왜 가끔 사이트 보면 기깔나는 아이콘인데 텍스트처럼 선택이 되는 뭐 그런 아이콘이 있을 것이다. 그 있어... 뭐 햄버거 버튼 별 이런거 있는데 별의별게 다 있다. 그리고 개발자 도구를 봤더니... 폰트 패밀리에 FontAwesome? 이게 뭐시여?
폰트어썸은 아이콘 라이브러리이다. 당연한 소리지만, 내가 처음 네이버 마이홈을 쓸 때는 CSS라는 개념이 없어서 속성도 죄 인라인에 줬고, 아이콘? 아이콘은 이미지 파일을 서버에 직접 업로드해야 했다. 그러면 색깔 잘못 올리면 어떻게 되냐고요? 걍 주옥되는겁니다. 새로 작업해서 새로 올려야 하거든... 이 얼마나 힘듭니까... 그때 컴퓨터가 램 16기가면 쥰내 비쌌어요...
하지만 얘는 CSS로 제어가 가능하기때문에 그냥 CSS로 색깔을 바꾸면 된다. 즉, 이미지를 쌩으로 다시 작업하는 게 아니라 코드를 수정하면 된다. 이미지 작업보다 한결 간단하기도 하고, 굳이 컴퓨터에 뭘 깔 필요도 없으니 상당히 편하다.
일단 나는 계정을 미리 만들었고 키트도 무료 키트도 하나 받아놨다. 그래서 이전 설명글에 가끔 아이콘이 보였던 것...
kit에서는 내가 만든 키트들을 볼 수 있다. 저거 내 키트인데 이름이 kit인거다. 들어가면 세 가지 탭이 있는데 Set up을 보면
요로코롬 나 복사하슈~ 하는 코드가 있다. 코드는 혹시 몰라서 가렸음. 그걸 복사해서 HTML파일의 head태그 안에 붙여넣기 하면 끝이다. 그럼 아이콘은 어떻게 입력해요?
kits에서 아이콘 탭을 누르면 이 키트로 쓸 수 있는 아이콘 종류가 나온다. 나는 무료 키트라 저렇게 쓰는데 솔리드/레귤러로도 뽕 뽑고 남으니까 패스.
여기는 icons에서 solid를 눌러서 들어온 페이지이다. 여기서 본인이 원하는 아이콘을 찾을건데
free를 선택하면 무료 아이콘을 찾을 수 있다. 여기서 뭐 찾지... 메테오를 찾아보자.
여기서 맨 왼쪽 위에 있는거 쓸거다. 그러면 얘를 클릭하면 뭐가 뜨느냐...
요렇게 아이템 가져가기 전에 지지고 볶을 수 있습니다. 회전도 되고 사이즈 조절도 되고 색깔도 바꿀 수 있는데... 그거 말고 맡에 탭을 보면 HTML, React, Vue.js, SVG 이렇게 네 개가 있을 것이다. 저 아이콘을 당신이 써먹을 데에 맞게 가져가면 되는데 하아니 그럼 콘트롤씨 눌러야돼요? 아니 저거 클릭하면 복사 됩니다.
<i class="fa-solid fa-meteor"></i>
위 메테오를 HTML에 넣을거면 이걸 복사하면 된다. React랑 Vue.js는 뭐 또 분기가 있는데
React는 이렇고
Vue.js는 이렇고... 아니 이거 나 바이올렛 IGN인디... (IGN: 인 게임 네임)
위가 리스트 뷰, 아래가 그리드 뷰이다. 포켓몬고의 인벤토리는 원래 위쪽만 있었다가, 아래쪽도 일부 계정에 시범적용 후 현재는 전 계정에서 해당 설정을 할 수 있게 되었다.
일단 두 방식은 각각 장/단점이 있는데
리스트 뷰는 위의 사진처럼 아이템의 설명을 확인할 수 있고, 버리기 아이콘도 줄의 끝에 있기 떄문에 아이템이랑 거리가 있어서 아이템의 조작이 상대적으로 편하다. 하지만 도구가 많아질수록 표시하는 데 필요한 페이지 길이가 길어지기때문에 스크롤하기가 매우 귀찮고, 카테고리에 따라서는 가지고 있는 아이템의 종류와 수량을 한번에 파악하기가 어렵다는 단점이 있다. 위쪽은 도구이고 평소에는 일부 도구를 정리해서 저 정도지만, 저기에 상처약 두 종류만 추가돼도 리스트 뷰에서는 한번에 못 본다. 루어 모듈도 마찬가지.
그리드 뷰는 장단점이 리스트 뷰와 반대이다. 일단 한 눈에 아이템의 개수와 종류가 확 들어오고, 상대적으로 리스트 뷰에 비해 필요한 페이지 길이도 짧아서 스크롤 노가다를 덜 해도 된다. 하지만 아이템과 삭제버튼 간 간격이 좁기때문에 아이템을 버리려다가 다른 아이템을 선택하게 되는 사태가 자주 일어난다. 그리고 나야 본가 게임을 꽤 해봤지만 아예 포고로 처음 포켓몬을 접하거나, 아이템이 처음 보는 거라 설명이 필요할 때가 있는데, 이 때 그리드 뷰는 설명이 안보이기때문에 불편할 수도 있다.
만들어보자
일단 네이버 글자 제한때문에 HTML 코드는 여기 다 못 올린다. 그러니 같이 보고 싶으면 깃헙으로 갑시다. 위에 주소 써뒀음.
<div class="button"> <!-- 전환 버튼이 위치할 곳 -->
<label class="switch">
<input type="checkbox">
<span class="slider"></span>
</label>
</div>
위에 이 부분은 슬라이드 버튼이다. w3school에서 긁어온건데 이번에는 네모네모 버튼으로 긁어봤음. 그리고 아래쪽이 본체다.
버튼과 아이템을 wrap이 싸고 있고, 버튼 밑에 아이템이 본체인데
대충 이런 구조... 길어서 아이템족은 또 접었는데 item-wrap 안에는 아이콘과 컨텐츠가 들어있다.
그래서 이런 식으로 리스트 뷰가 적용되는거다. CSS는 후가공 몇번 했음... ㅡㅡ 옆에 있는거 독이니까 당황하지 마십쇼. 지금 이 글 쓰는 OS 리눅스임.
여기서 그리드 뷰로 바꾸는 방법은 간단하다. 첫번째, 그리드 뷰로 바꿀 때 바뀔 요소에 대해 CSS를 적용한다(grid 클래스를 주고). 두번째, JS로 토글되게 한다. 그러면 JS보다 골치 덜 아픈(안아프다고 안했음) CSS부터 들어가보자... 예전에 다크모드 포스팅 한 걸 보셨던 분들이라면 아시겠지만, 그거 비슷하게 돌아간다.
일단 여기서 그리드 뷰로 바꾸게 되면 바뀌는 거 세 가지다. 아이템(아이템 래퍼를 담고 있는 거)은 display가 그리드가 되고, 3등분 그리드로 안의 컨텐츠를 담는다. contents는 대충 설명같은 느낌인데(위에 보면 아이콘 옆에 있는 글이 contents), 그리드 뷰에서는 contents를 안 보이게 할거니까 display 속성을 none으로 했다. 그리고 아이콘의 경우 크기를 더 키웠다. 아니 쟤가 제일 잘 나가니까 당연한거 아님?
//가져올건 이 쪽
let toggleButton = document.querySelector('.slider');
let viewItem = document.querySelector('.item');
let viewCont = document.querySelectorAll('.contents');
let viewIcon = document.querySelectorAll('.icon');
//실행 코드
toggleButton.addEventListener("click", function () {
if (viewItem.classList.contains('grid')) { //.item은 하나밖에 없어서 하나만 가져왔습니다
viewItem.classList.remove('grid');
} else {
viewItem.classList.add('grid');
}
viewCont.forEach(function (item) {
if (item.classList.contains('grid')) { //컨텐츠와 아이콘은 HTML파일에 추가된 숫자만큼 여러개 있어서 forEach로 해결
item.classList.remove('grid');
} else {
item.classList.add('grid');
}
})
viewIcon.forEach(function(item){
if(item.classList.contains('grid')) {
item.classList.remove('grid');
} else {
item.classList.add('grid');
}
})
}, false);
그러면 자바스크립트에서 이거 바꿀 수 있게 해야 한다고 했는데... 그러면 동작 지시할 버튼과 바꿀 요소를 가져와야 한다. 위에 있는 건 버튼이고, 아래 세 줄이 바꿀 요소들. forEach가 들어간것과 안 들어간 것의 차이? HTML에 뭉텅이로 있으면 forEach로 순회하면서 작업해야 다 바뀜....
나도 포고해야되니까 최대한 간결하면서도 이해하기 쉽게 설명해봅시다. item 클래스를 가지고 있는 건 HTML 파일에 하나밖에 없고, grid 클래스가 붙으면 3열짜리 그리드로 바꿀 거다. 반면 아이콘이나 콘텐츠도 별도의 클래스를 부여하고 그리드 븅 맞게 바꾸는 건 맞지만 얘네는 HTML 안에 여러개가 있다. 다행히도(?) 동적 생성은 아니고 수제로 복붙함...
아무튼 그런 과정을 거쳐서 이런 식으로 토글할 수 있다. 저거 근데 다크모드때처럼 버튼 통으로 가져왔다니 이벤트 두번 적용되더라... ㅡㅡ
라디오 버튼은 '같은 그룹 내에서 하나만 선택할 수 있는' 버튼이다. 체크박스처럼 중복 선택이 안되는데 그걸 위해서 필요한 게 name이다. 위에 있는거 HTML코드 그대로 가져온거다. 잘 보면 이상해씨, 파이리, 꼬부기가 같은 name으로 묶여있는 것을 볼 수 있을 것이다. 저거 없으면? 우웅 중복선택? 얘네 같은 그룹임? 이 돼서 막 갖다 고를 수 있는 대참사가 터진다.
이렇게 선택지 그룹에 따라 name을 다르게 하면 여러 개의 선택지를 만들 수도 있다. 그럼 어떻게 만드는 지는 알았으니 여기에 CSS 적용을 어떻게 하는 지 알아보자. 여기서 CSS 적용이라는 건 span 말고 그 옆에 있는 라디오 버튼 말하는거다. 일단 새로 쓰기는 귀찮으니 2번 소스를 복사해서 써먹어봅시다.
소스에 주석이 붙은 부분도 있지만... 일단 라디오 버튼에 CSS를 주기 위해서는 그냥 줘서는 안된다. 안보이그덩. 그래서 appearance: none;을 줘서 기존 스타일을 숨긴 다음 적용했다. 아무튼 이런 식으로 라디오버튼의 그 동그란거에 대한 CSS를 적용할 수 있다. transition은 PPT의 애니메이션 효과 같은 건데, 이게 있으면 좀 자연스럽게 전환할 수 있다.
근데 만약 위 예제에서 스테이크 재료가 다 떨어져서 스테이크를 못 팔면 어떻게 하나요? 아, 그거 간단합니다.
갑자기 뭔가 개 복잡한 게 나왔다?? 가끔 스칼렛 바이올렛 중 뭘 사야 할 지 고민하는 사람들이 있어서 만들어 본 선택지다. 정작 오래 걸린 부분은 유효성 검사였던 게 함정. 아무튼... 이 코드가 어떻게 돌아가냐면 1) 질문에 따라 선택을 하면 2) 그 선택지의 value를 배열에 넣고 3) 스칼렛과 바이올렛 중 많은 걸 종합해서 추천해준다. 근데 이게 라디오버튼이 비면 안되잖아요? 그것때문에 처리하느라 오래 걸린거임...ㅠㅠ
아무튼... 예제를 보면 선택지가 두개씩인데 스칼렛/바이올렛 버전에 따라 분기되는 걸 나타낸거다. 라디오버튼에 따라 색이 다른 건 CSS 적용해서 그럼. 아무튼 그럼. 그러면 HTML, CSS는 각각 뼈와 살이고 자바스크립트로 작동하는건데... 일단 선택지 요소를 가져온 다음, 반복문과 if문의 조합을 통해 선택된 선택지의 value를 가져온다.
이렇게 라디오버튼에 지정해 둔 name으로 묶어서 가져오면, 이 중에서 선택된 거 하나의 결과값을 가져오게 된다. 값은 스칼렛 or 바이올렛이고 전부 선택하면 5개니까 스칼렛쪽이 더 많으면 스칼렛을, 바이올렛 버전이 더 많으면 바이올렛 버전을 추천해주는 거다. 근데 이거 어렵죠? 그래서 위에 있는 메뉴 예제를 다시 가져와봤습니다.
let dish = document.getElementsByName("main");
let drink = document.getElementsByName("desert");
let selection = document.querySelector("#selection");
let selectDish = "";
let selectDrink = "";
function order() {
//메인메뉴
dish.forEach((node) => {
if (node.checked && node.value == "meat") {
selectDish = "T본스테이크";
} else if (node.checked && node.value == "bibim") {
selectDish = "돌솥비빔밥";
} else if (node.checked && node.value == "sushi") {
selectDish = "모둠초밥+미니우동";
} else if (node.checked && node.value == "ddeokggalbi") {
selectDish = "떡갈비 정식";
} else if (node.checked && node.value == "burger") {
selectDish = "수제버거&감자튀김";
}
});
//음료
drink.forEach((node) => {
if (node.checked && node.value == "plum") {
selectDrink = "매실차";
} else if (node.checked && node.value == "coffee") {
selectDrink = "아포가토";
} else if (node.checked && node.value == "greentea") {
selectDrink = "녹차";
} else if (node.checked && node.value == "earlgray") {
selectDrink = "홍차(얼 그레이)";
} else if (node.checked && node.value == "assam") {
selectDrink = "홍차(아삼)";
}
});
//주문을 포함한 출력
let orderDish = document.createElement("p");
orderDish.innerHTML =
"주문하신 메뉴는 " + selectDish + "와 " + selectDrink + "입니다. ";
selection.appendChild(orderDish);
}
메뉴를 하나씩 고르고 버튼을 누르면 식사와 음료가 출력되는 개 심플한 코드다. 유효성 검사... 귀찮아서 안함... 아무튼 위에 두 줄은 getElementsByName인데 이게 라디오버튼의 선택지를 가져오는 코드이다. 그리고 order 함수 에 보면 주석으로 음식과 음료로 나뉘어진 부분이 있는데, 이걸 왜 이렇게 했냐... HTML 태그를 보면 아시겠지만 value가 영어다. 스테이크는 meat, 수제버거+감자튀김은 burger 이런 식인데 이거 사용자한테 그대로 보여줄거임? 아니잖아요. 그니까 노드 밸류에 따라 라디오 버튼 옆에 있는 메뉴로 바꿔줘야 한다.
출력은 p태그를 만들어서 div에 갖다 붙이는 식으로 진행된다. 라디오버튼의 경우 동일 그룹 내에서는 하나만 선택할 수 있어서 가져오는 건 쉬운 편이다. 체크박스 과연...
브라우저에서 F12를 누르면 개발자 도구가 나온다. 아, 오페라는 컨트롤 시프트 I 눌러야 나옴. 아무튼... 가끔 글쓰다가 열리면 어이 이거 뭐야 하게 되지만 사실 이거, 장식이 아니다. 자바스크립트에서 console.log()로 출력하게 되면 결과물 확인할 때 콘솔창을 들러야 한다...만 그거 말고 다른 쓸모가 또 있다. 예전에 고대에서 일할 때 도움 됐던 팁임.
오늘은 파폭으로 열었다. 파폭도 개발자 도구 오른쪽에 고정할 수 있는데... 보니까 뭔가 복잡하다 그죠? 검사기에 있는 것들은 지금 내가 보고 있는 이 사이트(네이버 메인)의 DOM이다. 뭐 네이버 검색창이라던가, 내 정보창(로그인 상태니까), 뉴스, 웹툰 이런거 말하는거다. 여기서 DOM을 선택해서 볼 수도 있고, 직접 보고자 하는 요소에 우클릭-검사(대충 비슷한거 있음)를 통해 볼 수도 있다. 그럼 시험삼아 검색창의 속성을 한번 확인해보자.
검색을 하기 위해 뭘 입력하는 곳이 포커싱되었다. 내가 지금 속성을 확인중인 것은 검색창 안에 있는 입력창이다. 그리고 저기 포커싱 된 건 마진이나 패딩에 따라 색깔이 달라지는데 아마 보라색이 마진인가 그랬음.
여기 보이는 건 선택한 요소의 CSS 선택자다.
그리고 여기에 있는 건 CSS 관련된건데, 왼쪽에 있는 게 CSS 속성이다. 근데 아니 이거 알려주려고 이 글 쓰신건가요? 놉.
생각해봅시다. HTML/CSS/JS까지 다 해서 올렸는데 이게 처음에는 바로바로 반영이 되겠지만 좀 쌓이다보면 바로바로 반영이 안된다. 캐시 지우고 해야 반영될까 말까인데 이걸 솔직히 일일이 서버 올리고 하는 것도 노가다고, 일일이 캐시 삭제하는 것도 너무 번거롭잖아요? 그러니까 올리고 수정하고 또 올리고 이런 노가다를 할 바에는 개발자 도구에서 미리 수정해보고 적용해라, 이게 내가 들었던 꿀팁이다. 엥? 그게 돼요?
여기서 해당 객체의 속성을 수정할 수도 있고
좀 간단한거면 여기서 추가해도 된다. DOM 추가는 좀 빡세지만 이미 추가된 DOM의 CSS 속성을 다양하게+빠릿빠릿하게 적용해볼 수 있다. 그리고 개발자 도구로 CSS 속성을 바꾼다고 해도 서버에 올리는 게 아니기때문에 새로고침 하면 원상복구된다. 다르게 말하자면 내가 개발자 도구로 CSS 속성을 바꾼다고 해도 다른 네이버를 열어보는 사람들이 보는 페이지에는 아~무 영향도 가지 않는다.
자 생각해봅시다. 파이썬은 print문으로 출력하잖음? 근데 자바스크립트에는 그런게 없어요. 여기서 오해하시면 안되는 게, 출력이 안되는 게 아니라 파이썬이나 C언어처럼 print 어쩌고로 적는 출력문이 없다는 얘기다. 그럼 어떻게 출력하나요? 그걸 이제 알아볼거다.
자바스크립트는 프론트엔드 삼신기(HTML, CSS, JS) 중 유일한 프로그래밍 언어이다. 거기서 더 나가면 리액트 뷰 앵귤러 삼대장이 기다리고 있지만 아무튼... 삼대장의 삼대장 걔네는 뭐 확장팩같은거고 걍 자바스크립트도 있긴 있잖아요? 그런데 자바스크립트가 왜 프론트엔드 삼신기냐... 프론트엔드 개발자는 HTML로 뼈대를 만들어서 CSS로 살을 붙이고 자바스크립트로 동작하게 하기 때문이다.
하아니 그런데 출력문이 없다고요? 그럼 어떻게 출력함??? print 이딴게 없는거지 출력할 방법 많습니다. 심지어 네 개나 있다.
저거는 걍 간단하게 버튼에 연결했지만 반드시 입력해야 하는 필드에 뭐가 빠졌거나 하면 알림창을 띄울 수도 있다. 보통 쓸 때는 windows.alert()가 아니라 걍 alert()라고 쓰면 된다.
DOM 조작하기
처음 하시는 분들은 좀 생소할 수 있는데, 간단하게 말하자면 이 페이지에 있는 HTML 요소들을 조작한다는 얘기다. 예전에도 몇 번 코딩한 거 올리면서 얘기했지만 자바스크립트는 뻐대(HTML)는 물론 뼈대에 붙은 살(CSS)을 조작할 수 있다. 뼈대의 내용물을 바꾸거나, CSS 클래스를 새로 추가하거나, CSS 속성을 바꾼다던가 뭐 그런.
여기서는 위에서 썼던 버튼을 그대로 우려먹어서(...) 버튼을 누르면 아래에 글자가 추가되게 해놨다. 아래에 div태그로 영역 하나를 만들고, 버튼을 누르면 영역을 가져옴과 동시에 p태그 하나를 만들어서 내용 정하고 갖다 붙이는거다.
console.log
자, 생각해봅시다. 직접 DOM을 조작하거나 알림창으로 띄우게 되면 어쨌든 보일 거 아니예요. 근데 나는 사용자가 이런걸 한다는 걸 몰랐으면 좋겠어! 브라우저에 직접 안 떴으면 좋겠어! 그럼 조용히 콘솔.로그를 써보자. 일단 각 브라우저에는 개발자 도구라는 게 있고, F12키를 누르면 열린다.
구글 크롬의 개발자 도구파이어폭스의 개발자 도구(얘는 밑에 가로로 뜨더라...)엣지의 개발자 도구오페라의 개발자 도구(얘는 별도로 설정하지 않으면 단축키가 ctrl+shift+I다)
개발자도구 여셨음? 그러면 console(콘솔)로 들어가자.
console.log는 웹 브라우저 개발자 도구에 있는 콘솔에 결과가 출력된다. 보통 뭐 자잘하게 테스트하거나 버그 잡을 때 유용하다. 개발자 도구의 다른 용도는 또 나중에 설명드리겠음.
여기서 정규식이 대충 와일드카드 같은거고(글은 분량상 두개로 나눴음) 파이썬에서는 re모듈을 쓴다고 했는데, 사실 파이썬 뿐 아니라 자바스크립트에서도 정규식을 쓸 수 있다. 정규표현식, 그러니까 정규식은 파이썬만의 전유물이 아니기때문에 다른 언어에서도 사용하는 방법이 다를 뿐이지 일단 쓸 수는 있다.
정규식에 대해 설명하면서 대충 '와일드카드 같은 것'이라고 했는데, 이런식빵 저런식빵을 모두 식빵 한방에 찾게 해 주는 매우 놀랍고 유용한 것이다.
아니 이런거 말고 다른거 없어요? 이 정규식을 유효성 검사에 활용할 수 있다면 어떰? 유효성 검사는 되게 간단한건데 여러분들도 많이 봤던 그거다. 회원가입 할 때 이메일 입력하다 삐끗했더니 올바른 이메일이 아닙니다! 올바른 아이디가 아닙니다! 이러는거 있잖음? 그거 말고 입력 다 해주세요 이런거. 그것도 유효성 검사다. 입력을 '제대로' 했는지 확인하는 것.
정규식을 이용해 이메일 주소라는 걸 알 수 있는 건, 메일주소에 패턴이 있기 때문이다. 대부분의 메일 주소는 아이디@도메인이고 도메인은 뭐시기닷컴 뭐시기닷넷 이런 식으로 온점이 하나씩 들어간다. 그러니까 yourid@naver.com 이런 형식을 가지고 있다 이거지. 그래서 정규식 패턴이
여기서는 전화번호가 나온다. 전화번호는 패턴이 어떤가요? 010-1234-5678 이런 식으로 숫자 세개 하이픈 숫자 네개 하이픈 숫자 네개다. 그럼 정규식으로 전화번호를 어떻게 짜나요?
const phone_pattern = /\d{3}-\d{3,4}-\d{4}/
이렇게 짠다. 저기에 왜 3,4가 들어가냐면 옛날에는 국번 세자리도 있었다.
const bread_array = ['이런식빵','밤식빵','붕어빵','조런식빵','팥빵','크림빵','슈크림빵']
const regex = new RegExp('식빵');
for (let i of bread_array) {
console.log(regex.test(i))
}
regex(위에 RegExp 쟤).test는 true, false로 출력해주고
const bread_array = ['이런식빵','밤식빵','붕어빵','조런식빵','팥빵','크림빵','슈크림빵']
const regex = new RegExp('식빵');
for (let i of bread_array) {
console.log(regex.exec(i))
}
얘는 Null or 배열로 뽑아준다.
const bread_array = ['이런식빵','밤식빵','붕어빵','조런식빵','팥빵','크림빵','슈크림빵']
const regex = new RegExp('식빵');
const cream = /크림/
for (let i of bread_array) {
console.log(regex.exec(i),cream.exec(i))
}
이렇게 하면 식빵이 있거나 크림이 있으면 배열이 나오고, 둘 다 없으면 널만 두 개 나온다.
const bread_array = ['이런식빵','밤식빵','붕어빵','조런식빵','팥빵','크림빵','슈크림빵']
const regex = new RegExp('식빵');
const cream = /크림/
for (let i of bread_array) {
console.log(i.match(regex),i.match(cream))
}
match도 배열로 돌려주는 건 맞는데 위에 두 놈이랑 순서가 다르다. test나 exec는 패턴.test(문자열)이고 match는 문자열.match(패턴)이라 주의해야 한다. 순서 뻑나면 당연하게도 에 ㅋ 러 ㅋ
const bread_array = ['이런식빵','밤식빵','붕어빵','조런식빵','팥빵','크림빵','슈크림빵','달빵','땅콩크림빵','딸기크림빵']
const regex = new RegExp('[^식]빵');
for (let i of bread_array) {
if (regex.test(i)) {
console.log(i)
}
}
매우 당연하지만 이런 식으로 식빵 빼는 것도 된다. (대괄호 안에 꺾쇠는 저거 빼고 찾으라는 얘기)
const bread_array = ['풀빵','이런식빵','밤식빵','붕어빵','조런식빵','팥빵','크림빵','슈크림빵','달빵','땅콩크림빵','딸기크림빵']
const regex = new RegExp('[^식|크림]빵');
for (let i of bread_array) {
if (regex.test(i)) {
console.log(i)
}
}
OR과 조합하면 식빵 크림빵을 다 뺄 수 있다. 이런 식으로 정규식의 메타문자와 사용법을 익혀두면 패턴이 존재하는 문자열에 한해서 정규식으로 패턴을 짜서 그것만 필터링할 수 있다. 왜 네이버 블로그에서 뭐 찾을 때