(61)

특정 조건을 만족하면 DOM이 나타나게 해 보자

요즘은 핸드폰으로 본인인증 많이 하는데, 그 인증창 보면 딸랑 이름 입력하는 란만 보인다. 그리고 이름을 입력하면 주민번호 입력란이, 주민번호 입력란을 다 채우면, 핸드폰 번호 입력란, 그리고 보안문자 입력란(이건 대충 사람입니다 체크하는거라고 보시면 될듯) 다음에 인증번호 입력란이 순차적으로 뜬다. 그러니까 이게 처음부터 떠있는 게 아니라 어떤 조건을 만족하면 뜬다 이거다. 뭐함? 이거 해볼거니까 빨리 에디터 켜요.입력창 세 개와 버튼 하나가 보인다. 그리고 여기서 어떻게 할 거냐면 1. 이름을 입력하면 생년월일 입력창이 보이고2. 생년월일 입력창을 다 채우면 연락처 입력하는 창이 보이고3. 연락처를 입력하면 확인버튼이 보이는 걸 해 볼거다. #no2 { display: none;}#no3 { di..

프로그레스 바 만들어보기

여기서 만들 건 나우 로딩하면 나오는 프로그레스 바가 아니라, 스크롤 프로그레스 바다. 티스토리 블로그 중에 가끔 글을 읽을 때 화면 상단에 무슨 막대기같은 게 자라는 게 있는데, 그걸 해 볼거다.Referencehttps://doooodle932.tistory.com/177 [JS] 스크롤 프로그레스 바 만들기완성본 HTML CSS body { margin: 0; padding: 0; height: 1000px; /* 스크롤을 위해 임시로 추가 */ } .progressWrap { position: fixed; top: 0; left: 0; width: 100%; height: 3px; background-color: #fff; } .bar { width: 0%; height: inherit; posi..

드디어 추가되는 파일 불러오기 기능

https://koreanraichu.tistory.com/718 텍스트 에디터에 뭔가 추가해보자https://koreanraichu.tistory.com/711 아주 간단한 텍스트 에디터를 만들어보자카테고리를 보시면 아시겠지만, 자바스크립트로 할 거다. 근데 일단 구현할 기능이 쓴 걸 저장하는 것 말고 없음… ㅋㅋㅋkoreanraichu.tistory.com여기서 이어진다. 텍스트 에디터를 만들면서 처음에 얘기했던 '텍스트 파일을 불러오면 제목에 파일명, 이름에 내용을 띄우는'걸 해 보자. 이건 일단 input이랑 FileReader API가 필요하다고 한다.저기 셀렉트박스 오른쪽에 보면 버튼 하나가 있다. 저건 사실 인풋 파일+라벨 붙여놓고 라벨에 CSS 준 거임. 아무튼 그럼. const fileO..

텍스트 에디터에 뭔가 추가해보자

https://koreanraichu.tistory.com/711 아주 간단한 텍스트 에디터를 만들어보자카테고리를 보시면 아시겠지만, 자바스크립트로 할 거다. 근데 일단 구현할 기능이 쓴 걸 저장하는 것 말고 없음… ㅋㅋㅋㅋㅋㅋ 여기서 더 들어가면 지피티 불러야됩니다…기본적인 기능(저장koreanraichu.tistory.com여기서 이어진다. 원래 추가하려던 건 파일 불러서 파일 이름을 제목으로 하는거였는데… 씁 그건 지피티 있어야되고… 오늘 추가해볼 건 그거다. 에디터 글꼴 변경하는 거. 페이지 전체 글꼴이 아니라, 글 작성하는 부분 글꼴(인풋이랑 텍스트에리어) 말이다.우선 이걸 하려면 먼저 할 일이 있다. 물론 HTML에 추가하는 것도 할 일인 건 맞는데, CSS단에서 바꾸려면 폰트 URL을 imp..

아주 간단한 텍스트 에디터를 만들어보자

카테고리를 보시면 아시겠지만, 자바스크립트로 할 거다. 근데 일단 구현할 기능이 쓴 걸 저장하는 것 말고 없음… ㅋㅋㅋㅋㅋㅋ 여기서 더 들어가면 지피티 불러야됩니다…기본적인 기능(저장하기)그니까 뭘 할거냐면, 여기서 글씨를 적고 하단의 저 버튼을 누르면 저 텍스트에리어 안의 글이 저장된다. 오케이? const textArea = document.querySelector('#textarea');const textSave = Document.querySelector('#save');일단 자바스크립트로 뭘 할라면 가져와야된다. textSave.addEventListener('click',()=>{ console.log(textArea.value);})이 블로그를 많이 봐오신 분들은 다 알겠지만, 여기까지..

카카오톡 모아보내기를 대충 구현해보자

여러분들 카톡할때 사진 모아서 보내기 하시죠? 그걸 구현해볼건데, 이게 사진 장 수에 따라서 구성이 어떻게 되냐면 3장: 3*14장: 2*25장: 3*1+2*210장: 3*2+2*2이렇게 된다. 10은 3*3+1로 해도 되지 않아요? 그렇게 해도 되는데 카톡 모아서 보내기는 사진 한장만 보낼 때 빼고는 밑에 한장만 띡 안 띄우고 3배수+2배수로 한다. 아, 근데 사진을 매번 만들기도 힘들고 해서 일단은 div를 동적으로 만드는걸로 나 자신이랑 합의 보기로 했음. 오늘은 감도 안 잡혀서 걍 GPT한테 해달라고 했다. 고수 코더 여러분들은 따라하지 마세여...const photoContainer = document.querySelector('.photocontainer');const photoCount = ..

우리도 그 유리 효과인지 뭔지 해봅시다 거

이번에 애플이 iOS 26을 발표하면서 리퀴드 글래스인가 뭐시기인가를 도입한다고 했는데… 우리 쿡이는 19 20 21 22 23 24 25를 못 세나요? 왜 갑자기 26으로 건너뛰었음? 우리 쿡쪽이는 수학공부를 안 했나? 각설하고 우리도 그 리퀴드 글래스인지 뭔지 하는 그 효과 좀 내 보자. 근데 애플처럼 깔쌈하게 나올거라는 장담은 못 드림. 잊지 마십시오. 이 블로그 주인장은 뉴비입니다. 애플처럼 진짜 깔쌈한 유리 효과를 내고 싶으시면 여기 말고 다른 블로그를 가십시오.일단 유리 하면 투명하다. 아무것도 안 묻은 새삥 유리는 투명한데… 문제가 하나 있다. 이걸 그대로 CSS에 도입하게 되면, 배경에 뭐가 있을 경우 콘텐츠가 안 보일 위험이 있다. 정확히는 사용자가 콘텐츠에 집중을 못 할 위험이 크고,..

자바스크립트로 음악 재생기를 만들어보자

오늘 그래서 뭘 해볼거냐... 두가지를 해볼건데 1. 당신 컴퓨터에 있는 음악 파일(예: *.mp3)을 열어서 2. 재생할거다. 끝이다. 나한테 많은 걸 기대하지 말라.파일 첨부를 할 수 있게끔 만들자컴퓨터에 있는 파일을 불러오려면 input type="file"을 써야 한다. 그리고 이 인풋을 통해 사용자가 음악 파일만 입력하도록 해야 하는데내가 아는 음악파일이 이거밖에 없으니 걍 이것만 함. 그리고 저 파일 첨부하는 거 그대로 올라와있는 거 뵈기 싫어서 라벨 연결하고 숨겼다. 본격적으로 자바스크립트에 들어가자올 것이 왔다. 아주 크나큰 난관이 예상되지만 아무튼... const musicUpload = document.querySelector('#uploader');var audio = new Audi..

체크박스를 버튼처럼 만들어보자

왜 가끔 그런거 있다. 여러개(혹은 하나)를 선택하는 선택지인데 버튼같이 생긴 거. 그럼 이건 체크박스거나 라디오 버튼이라는 얘기인데, 이걸 체크박스로 만들 수 있나? 를 해볼거다. 이건 뭐 일단 체크박스를 만들고 input[type="checkbox"] { width: 100px; height: 33px; appearance: none; background-color: #ffffff; color: #000000; border: 1px solid #000000;}input[type="checkbox"]:checked { background-color: #000000; color: #ffffff;}appearance: none;을 줘서 기존의 네모 상자를 없애면 이런 식으로 커스터마이..

토스트 창을 만들어보자

토스트 창이 뭐냐… 가끔 뭐 하다보면 뿅 하고 나오는 그거 맞다. 예를 들자면 메일을 보냈을 때, 화면 한 켠에서 보냈습니다! 하고 뿅 나왔다가 사라지는 그거 말이다.일단 들어가기 전에 생각해보자. 토스트창을 왜 쓰나요? 모달창도 있고 alert()로 알림을 띄울 수도 있는데 굳이 토스트 창을 만드는 이유가 뭘까? 모달창과 alert()로 띄우는 알림은 사용자가 상호작용을 해야 없어진다. 뭐 예를 들어서 오류가 났다, 그거는 내가 확인하고 상호작용 해야 하는거니까 모달이나 alert()를 쓰는 게 맞다. 오류가 났으면 오류메시지를 통해서 뭔 오류가 어떻게 터졌는지 보고 확인하고 문의를 하든 대처를 해야 하니까. 근데 그게 상대적으로 사소한거라 굳이 모달이나 alert()로 안 해도 되는 알림이라면? 예를..

아이디 생성기에 복사버튼을 달아보자

https://koreanraichu.tistory.com/634 클립보드 버튼을 만들어보자오늘 해볼 건 그거다. 일반적으로 복사할때는 컨트롤씨 컨트롤브이를 눌러야 하는데, 가끔 다른 앱이나 사이트같은 거 이용하다 보면 클릭하면 복사가 되거나 옆에 있는 버튼을 누르면 복사되koreanraichu.tistory.com여기서 이어진다.기존의 아이디 생성기는 생성된 아이디를 직접 드래그해서 컨트롤씨를 눌러야 복사가 됐다. 솔직히 붙여넣는건 그렇다치고 복사까지 그렇게 한다고요? 아 귀차낭. 그래서 복사버튼을 추가해봤음. 그래서 전체적으로 어떻게 바꼈느냐… 1. 전체적인 디자인 변경2. 체크박스에 라벨 추가(이거 해두면 글자 눌러도 체크박스 선택됨)3. 입력창 포커스 상태 CSS 추가4. 생성되는 아이디에 복사버..

클립보드 버튼을 만들어보자

오늘 해볼 건 그거다. 일반적으로 복사할때는 컨트롤씨 컨트롤브이를 눌러야 하는데, 가끔 다른 앱이나 사이트같은 거 이용하다 보면 클릭하면 복사가 되거나 옆에 있는 버튼을 누르면 복사되는 뭐 그런 게 있다. 그걸 해 볼거다.\그냥 이렇게 만들었음. 여기서 뭘 해볼거냐면 위에다가 아무거나 텍스트를 쓰고 저 클립보드 아이콘을 누르면 텍스트 상자에 입력한 게 복사가 되게 할 거다. const Text = document.querySelector('#text');const copyButton = document.querySelector('#button');어디갑니까 가져와야지. 우리야 컨트롤씨 컨트롤브이 하면 다 되니까 복붙은 쉽다고 생각할 수 있지만, 사실 복붙을 하려면 컴퓨터 입장에서는 클립보드에 적어뒀다가..

::selection 색상을 부분부분 다르게 할 수 있을까?

일단 ::selection이 뭐냐면 이거다. 이게 뭐냐고? 마우스로 긁었을 때의 색깔을 ::selection을 통해 줄 수 있다. 기본은 파란 배경에 흰색 글씨인데, 가끔 코딩 좀 하시는 분들 블로그 가 보면 텍스트를 마우스로 긁었을 때 배경색이 다른 것을 볼 수 있다. 이것도 보면 위랑 색깔이 다르죠? 괴담수사대는 일부러 빨간색+어두운색 기반으로 색깔을 정해서 ::selection 배경이 빨간색이다. 이런 효과를 줄 때 배경색은 걍 CSS 백그라운드 주면 되고 글자색은 color 주면 된다. ::selection { background-color: #cc0000; color: #ffffff;}이렇게 말이지. CSS는 저렇게 ::붙어있는 게 있는데 다른건 나중에 또 알아봅시다. 참고로 괴담수..

글자 수 카운터를 만들어보자

우리가 해 볼 게 위 그림 두 장에 나와있다. 머스크가 인수해서 똥을 싸제끼고 있는 트위터나 마스토돈, 블루스카이에는 한 번에 올릴 수 있는 내용에 글자수 제한이 있다. 트위터는 140자, 마스토돈은 500자(하이퍼스페이스 인스턴스), 블루스카이는 300자. 오늘은 입력창의 글자수를 실시간으로 세 보는 걸 할 건데, 해볼 게 총 두가지이다. 첫번째는 그냥 입력창에 몇 글자가 입력됐는지를 세 주는거고, 두번째가 위 두 장의 사진에 나와있는 n자 제한에서 n자가 남았는지 표시해보는 것이다. 아, 저 원은 아쉽게도 구현을 못 한다. 내 능력 밖임.입력창의 글자 수를 나타내보자그러니까 일단 남은 글자수는 모르겠고 몇 글자 썼는지 실시간으로 띄우는 것 먼저 해 보자. 얘를 해야 밑에께 좀 더 쉬워진다. 뭐 해요,..

모바일 뷰포트 확인하는 법(feat. 개발자 도구)

https://koreanraichu.tistory.com/572 미디어쿼리에 대해 알아보자미디어쿼리? 그게 뭐죠? 에 대해 설명하기 전에 한번 잘 생각해보자. 진짜 완전 찐으로 구식인 웹사이트... 그니까 막 쌍팔년도에 만들어져서 방치된 스멜이 폴폴 풍기면서 들어koreanraichu.tistory.com이 글에서 미디어쿼리로 모바일에서 UI 배치를 바꿔봤는데… 읽으면서 에이 뭐 뷰포트 뭐 창 줄여서 보지 이런 분들도 계셨겠지만 아니 저거 어케봄??? 이런 분들도 계셨을 거라 생각한다. 그거 어케 하는지 알려드림.  얘까지 한 글에 쓰면 내용이 번잡시러워서 포스트 분리한겁니다.다들 알다시피 브라우저에서 F12키를 누르면 개발자도구가 열린다.F12 눌렀는데 안열려요? 오페라 쓰신다고요? 오페라는 단축키가..

특정 조건을 만족하면 DOM이 나타나게 해 보자

Coding/JavaScript 2025. 9. 17. 23:22

요즘은 핸드폰으로 본인인증 많이 하는데, 그 인증창 보면 딸랑 이름 입력하는 란만 보인다. 그리고 이름을 입력하면 주민번호 입력란이, 주민번호 입력란을 다 채우면, 핸드폰 번호 입력란, 그리고 보안문자 입력란(이건 대충 사람입니다 체크하는거라고 보시면 될듯) 다음에 인증번호 입력란이 순차적으로 뜬다. 그러니까 이게 처음부터 떠있는 게 아니라 어떤 조건을 만족하면 뜬다 이거다.

 

뭐함? 이거 해볼거니까 빨리 에디터 켜요.


입력창 세 개와 버튼 하나가 보인다. 그리고 여기서 어떻게 할 거냐면

 

1. 이름을 입력하면 생년월일 입력창이 보이고

2. 생년월일 입력창을 다 채우면 연락처 입력하는 창이 보이고

3. 연락처를 입력하면 확인버튼이 보이는

 

걸 해 볼거다.

 

#no2 {
  display: none;
}

#no3 {
  display: none;
}

#no4 {
  display: none;
}

그래서 이름 입력란을 제외한 나머지는 다 display를 none으로 해 줬다.

 

const name = document.querySelector("#no1");
const nameInput = document.querySelector("#name");
const birthday = document.querySelector("#no2");
const birthInput = document.querySelector("#birthday");
const phone = document.querySelector("#no3");
const phoneInput = document.querySelector("#cellphone");
const button = document.querySelector("#no4");

어디가요 제어할라면 가져와야지.

 

근데 여기서 중요한 게 있다. 우리는 저기에 뭘 '채워 넣었을 때' 다음 창이 보이게 해야 하잖아요? 그러니까 이벤트 리스트너를 쓸 거면 쟤를 클릭할때 되게 하면 안 된다. 그리고 위에 뭐가 길어서 봤더니 입력란 달린 애들은 다 두줄씩 달고 있죠? 보여야 하는 건 div인데 값 갖고와야 하는 건 입력란이라 그렇다. 첫번째줄은 div, 두번째줄은 입력란이라고 생각하면 된다.

 

근데 애초에 저기에 이벤트 리스트너를 줄 수가 없음… 내가 해봤는데 안됨. 그럼 어떻게 해요? 값 가져와서 if문 때려박아봐야지.

 

nameInput.addEventListener("input",()=>{
  if (nameInput.value.trim() !== "") {
  birthday.style.display = "block";
  } else {
  birthday.style.display = "none";
  }
});

아니 쟤 인풋도 있어… 없는게 뭐야 대체… 그래서 이 코드가 뭐임? 말 그대로 입력하면 생년월일이 나오게 하는 코드다. 근데 문제가 하나 있다면 쟤는 입력을 하자마자 밑의 항목이 나오기때문에

 

성만 써도 이름이 나오는 것을 볼 수 있다. 아니 그럼 이름을 다 썼을 때 나오게 하려면 어떻게 해요?

 

nameInput.addEventListener("blur",()=>{
  if (nameInput.value.trim() !== "") {
  birthday.style.display = "block";
  } else {
  birthday.style.display = "none";
  }
});

이벤트 리스트너 안에 있는 input을 blur로 바꿔주면 이름을 다 쓰고 커서를 뗐을 때 생년월일 칸이 나온다.

 

birthInput.addEventListener('blur',()=>{
  if (birthInput.value.trim() !== "") {
    phone.style.display = "block";
  } else {
    phone.style.display = "none";
  }
});

같은 input이라 그런가 저 코드가 먹힌다. 그럼 폰 번호도 저렇게 하면 되나요? 일단 저렇게 해도 되긴 되는데, 핸드폰 번호의 경우 절차가 하나 더 필요하다. 뭔 절차요? 저거 유효성검사 하셔야죠.

 

const phoneRegex = /^01[016789]-?\d{3,4}-?\d{4}$/;

그래서 규식정씨를 불러야 한다. (정규식이라 규식 정)

 

phoneInput.addEventListener('blur',()=>{
  if (phoneRegex.test(phoneInput.value.trim())) {
    button.style.display = "block";    
  } else {
    phoneInput.style.borderColor = "#cc0000";
    button.style.display = "none";
  }
});

근데 저기 else에 들어간 건 뭐예요? 형식 안 맞으면 테두리 색깔 바꾸라는 얘기다.

 

전화번호는 3자리-4자리-4자리 혹은

 

11자리 숫자로 걍 쓰면 확인버튼이 보이는데 저 버튼은 눌러도 별 기능 없다.

 

그리고 형식이 안 맞으면 이렇게 빨간 테두리가 나오는 것.

 

근데 아까부터 저 if문에 들어가는 trim()은 뭔가요? 저건 문자 양 옆에 있는 공백을 다 떼버리라는 얘기다. 저걸 해주면 이름란에 공백만 입력한 경우에도 다음으로 안 넘어가게 된다. 물론 이름 앞뒤에 공백 들어가면 알아서 떼 주는 역할은 덤.

 

아까 그 빨간 테두리를 전체 영역으로 확장했다.


이벤트 리스트너의 blur는 이름을 입력하고 나서 커서를 입력란 말고 다른데다 둬야 이벤트를 실행하는데, 이조차 번거롭다! 하면 다른 방법도 있다.

 

nameInput.addEventListener("keydown",(e)=>{
  if (e.key === "Enter" && nameInput.value.trim() !== "") {
  nameInput.style.borderColor = "#000000";
  birthday.style.display = "block";
  } else {
  nameInput.style.borderColor = "#cc0000";
  birthday.style.display = "none";
  }
});

이벤트 리스트너의 blur를 키다운으로 바꾸고 if문에 1) 이름이 입력된 상태에서 2) 엔터키를 눌렀을 때 다음 입력란을 볼 수 있게 하면 된다. 근데 쟤는 위에 썼던 코드랑 달리 트리거 옆에 e가 있네요? 님 원래 저거 생략하지 않았음? 저걸 생략한 게 익명함수 김람다씨인데 김람다씨가 아무때나 다 쓸 수 있는 게 아니다. 마치 우리가 인터넷에서는 닉네임으로 활동하지만 송금할때는 본명이 필요하듯이 말이다. 김람다인 이유는 그냥 김씨가 흔해서다 그럼 저 e가 없으면 어떻게 되는데요? 컴퓨터가 타겟을 못 찾아서 뭔 엔터를 뭘 어쩌라는거냐면서 밥상을 뒤집습니다. 물론 진짜 밥상을 뒤집지는 않겠지만 아마 의도한대로 작동은 안 할거다. 

 

아니 근데 하는 김에 하나만 더 합시다. 이거 커서 다음 입력란으로 알아서 못 넘겨요?

nameInput.addEventListener("keydown",(e)=>{
  if (e.key === "Enter" && nameInput.value.trim() !== "") {
  nameInput.style.borderColor = "#000000";
  birthday.style.display = "block";
  birthInput.focus();
  } else {
  nameInput.style.borderColor = "#cc0000";
  birthday.style.display = "none";
  }
});

커서를 넘길 DOM.focus() 해 주면 알아서 다음으로 넘어간다.

 

See the Pen 조건부 DOM by koreanraichu (@koreanraichu) on CodePen.

버튼은 장식이니까 굳이 눌러보지는 말자. 진짜로 장식이다. 

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

프로그레스 바 만들어보기

Coding/JavaScript 2025. 9. 16. 02:35

여기서 만들 건 나우 로딩하면 나오는 프로그레스 바가 아니라, 스크롤 프로그레스 바다. 티스토리 블로그 중에 가끔 글을 읽을 때 화면 상단에 무슨 막대기같은 게 자라는 게 있는데, 그걸 해 볼거다.


Reference

https://doooodle932.tistory.com/177

 

[JS] 스크롤 프로그레스 바 만들기

완성본 HTML CSS body { margin: 0; padding: 0; height: 1000px; /* 스크롤을 위해 임시로 추가 */ } .progressWrap { position: fixed; top: 0; left: 0; width: 100%; height: 3px; background-color: #fff; } .bar { width: 0%; height: inherit; position:

doooodle932.tistory.com

 

https://sunshineyellow.tistory.com/88#clientHeight

 

[코딩애플] 요소의 높이와 위치값 구하기 (clientHeight, offsetHeight, scrollHeight, getBoundingClientRect())

clientHeight console.log(element.clientHeight); clientHeight는 패딩을 포함한 요소의 높이를 가져온다. offsetHeight console.log(element.offsetHeight); clientHeight는 패딩, 테두리, 가로스크롤바(있는 경우)을 포함한 요소

sunshineyellow.tistory.com


일단 이건 스크롤을 해야 하는 거기 때문에 분량을 급나 길게 늘려야 한다. 그래서 폰트 크기에 줄 높이 주고 로렘입숨을 겁나 때려박아서 기이이이이이이이이이일게 만들었다.

 

const progressBar = document.querySelector('#progress');

만들었으면 가져오십쇼.

 

#progress {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 5px;
  background-color: #f7cac9;
}

깜빡하고 CSS 생략할 뻔 했는데, 보통 프로그레스 바는 화면 상단에 있다. 그러니까 위치를 화면 상단으로 고정해야 한다.

 

이 스크롤바 역시 이 블로그에 단골로 등장하는 이벤트 리스트너를 이용할건데... 그럼 여기서 짬 좀 있으신 분들은 감이 좀 오실 것이다. 아니 이거 스크롤 이벤트도 커버가 돼요? 아니 글쎄 그것까지 커버가 되더라고요? 얘 대체 안되는게 뭐임? 

 

document.addEventListener('scroll',()=>{
  let scrollNum = window.scrollY;
  console.log(scrollNum);
})

이런 식으로 스크롤 높이를 가져온 다음 거기에 맞춰서 작대기 크기를 늘였다 줄였다 하면 된다고 보시면 된다. window.scrollY 말고 document.documentElement.scrollTop도 쓴다. 

 

document.addEventListener('scroll',()=>{
  let scrollHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
  let progressPercent = window.scrollY / scrollHeight
  console.log(progressPercent)
})

뭐임? 왜 갑자기 많아짐? 을 이제 하나씩 풀어보자...

 

document.documentElement.scrollHeight은 본문에 로렘 입숨을 급나 때려박아서 만든 높이이다. 그러니까 캡쳐된 부분 말고 그 밑에 더 있는 본문까지 해서 나오는 높이를 말한다. 그리고 document.documentElement.clientHeight은 나도 뭔지 모르겠음… 패딩을 포함한 요소의 높이라는데 이건 좀 더 알아봐야겠음.

 

그래서 이게 뭐 하는건데요? 저 작대기 길이 정할라면 계산을 해야 할 거 아닙니까. 프로그레스 바 길이 정할라고 계산하는거다.

 

document.addEventListener('scroll',()=>{
  let scrollHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight; //뒤에꺼 뭔지 모르겠음...
  let progressPercent = (window.scrollY / scrollHeight) * 100;
  progressBar.style.width = progressPercent + "%";
})

그리고 길이 계산해서 프로그레스 바 길이 바꾸게 하면 땡이다.

 

티스토리 블로그에 적용중인 프로그레스 바. 이거 괴담수사대에도 적용중인데, 모바일 버전에서는 껐다. (미디어쿼리로 끌 수 있음)

 

코드펜에 적용한 프로그레스 바. 단색 뿐 아니라 그라데이션도 가능하다. 티스토리 블로그에 적용한 것도 그라데이션이다.

 

See the Pen progress bar(JS) by koreanraichu (@koreanraichu) on CodePen.

가서 직접 휠을 굴려보자. 

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

드디어 추가되는 파일 불러오기 기능

Coding/JavaScript 2025. 9. 11. 00:44

https://koreanraichu.tistory.com/718

 

텍스트 에디터에 뭔가 추가해보자

https://koreanraichu.tistory.com/711 아주 간단한 텍스트 에디터를 만들어보자카테고리를 보시면 아시겠지만, 자바스크립트로 할 거다. 근데 일단 구현할 기능이 쓴 걸 저장하는 것 말고 없음… ㅋㅋㅋ

koreanraichu.tistory.com

여기서 이어진다. 텍스트 에디터를 만들면서 처음에 얘기했던 '텍스트 파일을 불러오면 제목에 파일명, 이름에 내용을 띄우는'걸 해 보자. 이건 일단 input이랑 FileReader API가 필요하다고 한다.


저기 셀렉트박스 오른쪽에 보면 버튼 하나가 있다. 저건 사실 인풋 파일+라벨 붙여놓고 라벨에 CSS 준 거임. 아무튼 그럼.

 

const fileOpen = document.querySelector('#textuploader');

늘 그렇듯이 주무르려면 일단 갖고와야 한다.

 

fileOpen.addEventListener('click',(e)=>{
    const file = e.target.files;
    console.log(file[0])
})

쟤 file만 해서 콘솔 띄워보니까 FileList라고 뜨더라… 이런건 바로바로 불러오려면 인덱싱을 해야 한다. 사실 GPT가 [0] 썼을때부터 아 저놈도 인덱싱 해야 하는구나 싶긴 했음.

 

fileOpen.addEventListener('click',(e)=>{
    const file = e.target.files[0]; //너도 배열이냐?
    if (!file) return; 

    textTitle.value = file.name.replace(/\.txt$/, '');

    const reader = new FileReader();
    reader.onload = function(event) {
        textArea.value = event.target.result;
        count.innerText = textArea.value.length;
    };
    
    reader.readAsText(file, 'utf-8');
})

이게 로직은 문제가 없음. 불러와서 파일명을 제목에 띄우고, 내용을 내용란에 띄워라. 이게 다임. 근데 안되는거임. 그래서 왜 안됨? 했더니 정말 뜻밖의 문제점을 지적해줬다.

 

fileOpen.addEventListener('change',(e)=>{
    const file = e.target.files[0]; //너도 배열이냐?
    if (!file) return; 

    textTitle.value = file.name.replace(/\.txt$/, '');

    const reader = new FileReader();
    reader.onload = function(event) {
        textArea.value = event.target.result;
        count.innerText = textArea.value.length;
    };
    
    reader.readAsText(file, 'utf-8');
})

무슨 차이인지 모르겠다고? 안 되는 코드는 이벤트 리스트너에 click이 들어가 있고, 되는 코드는 이벤트 리스트너에 change가 들어가 있다. 아니 지피티야 이거 왜이럼?

 

클릭은 쟤를 클릭하면 이벤트가 시작된다. 그러니까 파일을 첨부하기 '전에' 이벤트가 발동되는데, 컴퓨터 입장에서는 뭐여 왜 없는 파일 제목을 띄우래? 가 되는거다. 마치 내 남친은 질량이 0인데 남자친구를 데려오라는 것과 같다. 그리고 체인지는 파일이 올라왔을 때 발동되는거라 아 올라왔으니 띄워줌이 되는 것이다. 이해가 어렵다면 change는 '(파일의 업로드) 상태가 바꼈을 때'라고 이해하면 된다.

 

불러온 후

 

참고로 원래 기능(텍스트 내용 복사, 텍스트 저장)역시 파일을 불러온 후로도 제대로 작동한다.

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

텍스트 에디터에 뭔가 추가해보자

Coding/JavaScript 2025. 8. 18. 23:39

https://koreanraichu.tistory.com/711

 

아주 간단한 텍스트 에디터를 만들어보자

카테고리를 보시면 아시겠지만, 자바스크립트로 할 거다. 근데 일단 구현할 기능이 쓴 걸 저장하는 것 말고 없음… ㅋㅋㅋㅋㅋㅋ 여기서 더 들어가면 지피티 불러야됩니다…기본적인 기능(저장

koreanraichu.tistory.com

여기서 이어진다.

 

원래 추가하려던 건 파일 불러서 파일 이름을 제목으로 하는거였는데… 씁 그건 지피티 있어야되고… 오늘 추가해볼 건 그거다. 에디터 글꼴 변경하는 거. 페이지 전체 글꼴이 아니라, 글 작성하는 부분 글꼴(인풋이랑 텍스트에리어) 말이다.


우선 이걸 하려면 먼저 할 일이 있다. 물론 HTML에 추가하는 것도 할 일인 건 맞는데, CSS단에서 바꾸려면 폰트 URL을 import해야 한다. 뭐 거창한 건 아니고

이거 몇 개 복사해서 CSS파일에 붙여넣기 하자.

 

<select id="fonts">
  <option value="CHOGOONCHICKENSCRATCHV7">조군 개발새발 7</option>
  <option value="nanumgothic">나눔고딕</option>
  <option value="Pretendard">프리텐다드</option>
  <option value="ridibatang">리디바탕</option>
  <option value="dosiyagi">도스이야기</option>
  <option value="mabinogiclassic">마비옛체</option>
</select>

그리고 HTML단에서는 select태그를 줬다. 저 폰트 전부 CSS에 import 된 상태여야 적용이 된다. 참고로 기본 글씨체는 온글잎 콘콘이다.

 

const fontSelection = document.querySelector('#font');

어디가요 가져와야지.

 

이거 반복문 되나…? 아니 그 전에 이거 리스트가 아닌 거 아냐?

 

아, 얘도 .value 써서 가져오는 모양이다.

 

selectedIndex도 있는데 걍 이걸로 하자. 폰트명 너무 길어… ㅡㅡ

 

아니 내가 이걸 생각한 건 맞는데… 이게 왜 됨????? 진짜 왜 되는거임?

 

이게 기본폰트(온글잎 콘콘)다.

 

그리고 마비옛체로 바꾼 결과가 이거. 아니 근데 뭘 생각하셨길래요?

 

fontSelection.addEventListener('click',()=>{
    if (fontSelection.selectedIndex == 0) {
        fontSelection.style.fontFamily = 'Ownglyph_corncorn-Rg';
        textTitle.style.fontFamily = 'Ownglyph_corncorn-Rg';
        textArea.style.fontFamily = 'Ownglyph_corncorn-Rg';
    } else if (fontSelection.selectedIndex == 1) {
        fontSelection.style.fontFamily = 'CHOGOONCHICKENSCRATCHV7';
        textTitle.style.fontFamily = 'CHOGOONCHICKENSCRATCHV7';
        textArea.style.fontFamily = 'CHOGOONCHICKENSCRATCHV7';
    } else if (fontSelection.selectedIndex == 2) {
        fontSelection.style.fontFamily = 'nanumgothic';
        textTitle.style.fontFamily = 'nanumgothic';
        textArea.style.fontFamily = 'nanumgothic';
    } else if (fontSelection.selectedIndex == 3) {
        fontSelection.style.fontFamily = 'Pretendard-Regular';
        textTitle.style.fontFamily = 'Pretendard-Regular';
        textArea.style.fontFamily = 'Pretendard-Regular';
    } else if (fontSelection.selectedIndex == 4) {
        fontSelection.style.fontFamily = 'RIDIBatang';
        textTitle.style.fontFamily = 'RIDIBatang';
        textArea.style.fontFamily = 'RIDIBatang';
    } else if (fontSelection.selectedIndex == 5) {
        fontSelection.style.fontFamily = 'DOSIyagiMedium';
        textTitle.style.fontFamily = 'DOSIyagiMedium';
        textArea.style.fontFamily = 'DOSIyagiMedium';
    } else if (fontSelection.selectedIndex == 6) {
        fontSelection.style.fontFamily = 'MabinogiClassicR';
        textTitle.style.fontFamily = 'MabinogiClassicR';
        textArea.style.fontFamily = 'MabinogiClassicR';
    }
});

이거요. forEach 돌릴까 하다가 걍 if 돌려버렸음.


온글잎 콘콘

 

조군 개발새발 V7

 

나눔고딕

 

프리텐다드.. 솔직히 고딕체는 딱딱해서 별로 안 좋아하지만 가장 깔끔한 것도 고딕체이긴 하다. 깔끔한건 인정. 근데 내가 고딕체를 별로 안 좋아하는 이유도 특유의 딱딱함때문임...

 

리디바탕. 바탕체중에서 개인적으로 제일 좋아하는 바탕체이다.

 

도스이야기. 왜 초딩때 했던 한컴타자가 생각나는거냐...

 

마비옛체

 

참고로 이 글꼴들을 고를 때 본인 기준으로 한자 지원 안 하는 폰트는 다 빠진다. 이게 단순히 지원만 안 하는거면 구글 웹폰트에서 일본 폰트 긁어와서 쓰면 되는데 한자 지원을 안하면서 한자를 공백으로 표기하면 골치아파짐… 특히나 괴담수사대에는 제목이나 본문에 한자가 들어가는 경우도 있어서 그런 폰트는 기피된다.

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

아주 간단한 텍스트 에디터를 만들어보자

Coding/JavaScript 2025. 8. 8. 00:26

카테고리를 보시면 아시겠지만, 자바스크립트로 할 거다. 근데 일단 구현할 기능이 쓴 걸 저장하는 것 말고 없음… ㅋㅋㅋㅋㅋㅋ 여기서 더 들어가면 지피티 불러야됩니다…


기본적인 기능(저장하기)

그니까 뭘 할거냐면, 여기서 글씨를 적고 하단의 저 버튼을 누르면 저 텍스트에리어 안의 글이 저장된다. 오케이?

 

const textArea = document.querySelector('#textarea');
const textSave = Document.querySelector('#save');

일단 자바스크립트로 뭘 할라면 가져와야된다.

 

textSave.addEventListener('click',()=>{
    console.log(textArea.value);
})

이 블로그를 많이 봐오신 분들은 다 알겠지만, 여기까지는 쉽다. 여기까지는.

 

const textArea = document.querySelector('#textarea');
const textSave = document.querySelector('#save');

textSave.addEventListener('click',()=>{
    const blob = new Blob([textArea.value], {type:'text/plain'});
    const url = window.URL.createObjectURL(blob);
    //블롭블롭

    const link = document.createElement('a');
    link.style.display = 'none';
    link.href = url;
    link.download = 'file';
    document.body.appendChild(link);

    link.click();

    setTimeout(() => {
        document.body.removeChild(a);
        window.URL.revokeObjectURL(url);
    }, 100);
})

롸? 이 코드가 아님? 

 

const textArea = document.querySelector('#textarea');
const textSave = document.querySelector('#save');

textSave.addEventListener('click',()=>{
    const blob = new Blob([textArea.value], {type:'text/plain'});
    const url = window.URL.createObjectURL(blob);
    //블롭블롭

    const link = document.createElement('a');
    link.style.display = 'none';
    link.href = url;
    link.download = 'file';
    document.body.appendChild(link);

    link.click();

    setTimeout(() => {
        document.body.removeChild(link);
        window.URL.revokeObjectURL(url);
    }, 100);
})

바꾼건 setTimeout함수 안에 있는 a를 link로 바꾼 것 뿐인데 갑자기 잘 된다. 많이 당황스럽다.

 

그럼 이제 여기서 뭘 할거냐… 지금은 텍스트파일을 저장하면 file로 저장되는데 이걸 이름을 입력받을거고, 이거 글쓰기 할 때 쓸거라서 글자수 세는 기능도 넣을거다.


제목을 파일명으로 저장하기

일단 파일명을 어떻게 할 거냐고? 이 스크린샷을 보자.

 

저 위에 인풋창을 새로 만들었는데, 쟤가 제목 겸 파일명이다. 그럼 자바스크립트에서 뭘 해야 하냐, 크게 1) input의 값이 있는지 확인하고 있으면 그걸 파일명으로 쓰되 2) 없을때에 대한 처리도 해야 한다.

 

const textTitle = document.querySelector('#title');

어디가요 갖고와야 뭘 하지.

 

link.download = 'file';

그리고 이 부분을 

 

    if (textTitle.value.trim().length == 0) {
        link.download = 'Untitled';
    }
    else {
        link.download = textTitle.value;
    }

이렇게 수정할거다.

 

?? 왜 됨?


글자수 세 주는 기능을 넣어보자

이건 전에 했던거 있어서 그거 걍 갖다 붙이면 된다.

 

https://koreanraichu.tistory.com/618

 

글자 수 카운터를 만들어보자

우리가 해 볼 게 위 그림 두 장에 나와있다. 머스크가 인수해서 똥을 싸제끼고 있는 트위터나 마스토돈, 블루스카이에는 한 번에 올릴 수 있는 내용에 글자수 제한이 있다. 트

koreanraichu.tistory.com

여기를 참고합시다.


Coming up...

1. 파일 불러오기(파일명을 제목으로 불러오고 내용을 내용으로 불러옴)

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

카카오톡 모아보내기를 대충 구현해보자

Coding/JavaScript 2025. 6. 27. 00:59

여러분들 카톡할때 사진 모아서 보내기 하시죠? 그걸 구현해볼건데, 이게 사진 장 수에 따라서 구성이 어떻게 되냐면 


3장: 3*1

4장: 2*2
5장: 3*1+2*2
10장: 3*2+2*2

이렇게 된다. 10은 3*3+1로 해도 되지 않아요? 그렇게 해도 되는데 카톡 모아서 보내기는 사진 한장만 보낼 때 빼고는 밑에 한장만 띡 안 띄우고 3배수+2배수로 한다.

 

아, 근데 사진을 매번 만들기도 힘들고 해서 일단은 div를 동적으로 만드는걸로 나 자신이랑 합의 보기로 했음. 오늘은 감도 안 잡혀서 걍 GPT한테 해달라고 했다. 고수 코더 여러분들은 따라하지 마세여...


const photoContainer = document.querySelector('.photocontainer');
const photoCount = document.querySelector('#photocount');
const photoButton = document.querySelector('#photobutton');

photoButton.addEventListener('click',(e)=>{
    let countNum = photoCount.value;
    console.log(countNum)
});

일단 동적으로 생성할 div의 수를 입력받아보자. HTML? CSS? 그게 지금 중요한 게 아녀… 이 코드의 코어이자 빡셈을 담당하는 자바스크립트를 봐야 한다 이거요.

 

사진이 한 장일때

일단 사진이 한 장일때를 빼면 무조건 3n+2n으로 들어가는 구조인데, 여기에 맞춰서 div를 우리가 동적으로 생성해야 한다. 이거 어디서 비슷한 문제 봤다고요? 예에에에에에에에전에 그리디 알고리즘 풀 때 봤던 것 같죠? 거스름돈 640원을 가장 적은 수의 동전으로 지불하려면 어떻게 해야 할 지. 뭐 그리디 알고리즘이 전체적으로 최적일거라는 보장은 없지만, 사진 올린거 배치하는데 전체적으로 최적이고 자시고가 어디 있음.

 

if (photoRemain == 1) {
    const photoRow = document.createElement('div');
    photoRow.className = 'grid-row';
    photoContainer.appendChild(photoRow);

}; //사진이 한 장만 있을 때

일단 사진이 한 장일때를 따로 처리해야 한다. 사진이 하나밖에 없으면 3n+2n으로 못 하거든… 3n+2n에 0을 때려박지 않는 이상 1이 더 작아요. 아무튼 그래서 예외처리 한다 보시면 됩니다.

 

이건 나중에 디스플레이 플렉스 줘서 잡으면 되니까 안심하십시오.

 

사진이 두 장 이상일때

왜 두장부터냐고요? 3n+2n에서 3n이 0이면 두장 할 수 있잖아.

 

while (photoRemain > 0) {
    let photosInrow;

    if (photoRemain >= 3) {
        photosInrow = 3;
    } else if (photoRemain == 2) {
        photosInrow = 2;
    } else {
        console.log('어? 뭔가 이상한데?');
        break;
    }

    const photoInbox = document.createElement('div');
    photoInbox.className = 'grid-row';

    for (let i = 0;i < photosInrow;i++) {
        const photosIndiv = document.createElement('div');
        photosIndiv.className = 'photo-row';
        photosIndiv.textContent = photoIndex++;
        photoInbox.appendChild(photosIndiv);
    }

    photoContainer.appendChild(photoInbox);
    photoRemain -= photosInrow;
}

자바스크립트에도 while문이 있냐고? 있다. 잘 안 쓸 뿐이지. 아무튼 사진이 2장 이상이면 3x+2y꼴로 나타내라는 얘기인데... 이게 문제가 있어요.

 

일단 이게 첫번째 문제다. 최대 3장씩 가로로 나열돼야 맞는데 저기 보시면 세로로 줄줄이 있죠? 이거는 JS가 아니라 CSS를 건드려서 해결 봐야 하는 문제라서 디스플레이를 flex로 주면 된다. 근데 저게 첫 번째 문제면, 다른 문제가 또 있다는건가요? 그렇다.

 

사진은 13장인데 한장이 짤렸다. 내가 지피티한테 3n+2n으로 나와야 한다고 했는데… 얘 더워먹었나…

 

if (photoRemain == 1) {
    const photoInbox = document.createElement('div');
    photoInbox.className = 'grid-row';

    const photosIndiv = document.createElement('div');
    photosIndiv.className = 'photo-row';
    photosIndiv.textContent = photoIndex++;

    photoInbox.appendChild(photosIndiv);
    photoContainer.appendChild(photoInbox);

    return;

}; //사진이 한 장만 있을 때

지피티는 가끔 말을 해줘도 씹어먹을 때가 있어서 매번 리마인드를 해 줘야 한다는 단점이 있다. 얘 그림 그릴때도 A 수정하면 B 망치고 B 수정하면 다시 A 망치고 반복함… 실화예요. 이것때문에 이미지 리사이징 포기하고 구글 whisk에게 그려달라고 했다니까.

 

그래서 뭘 어떻게 바꾼건데요? 일단 사진 한장짜리는 크게 바뀐 거 없다. 인박스 안에 사진이 들어가는 구조긴 한데 그게 다거든. 그래서 크게 바꿀 건 2장 이상 말고는 없다.

 

while (photoRemain > 0) {
    let photosInrow;

    if (photoRemain % 3 === 1 && photoRemain >= 4) {
        photosInRow = 2; // 1장 남을 미래 방지
    } else if (photoRemain >= 3) {
        photosInRow = 3;
    } else if (photoRemain == 2) {
        photosInRow = 2;
    } else {
        console.log('어? 뭔가 이상한데?');
        break;
    }

    const photoInbox = document.createElement('div');
    photoInbox.className = 'grid-row';

    for (let i = 0; i < photosInRow; i++) {
        const photosIndiv = document.createElement('div');
        photosIndiv.className = 'photo-row';
        photosIndiv.textContent = photoIndex++;
        photoInbox.appendChild(photosIndiv);
    }

    photoContainer.appendChild(photoInbox);
    photoRemain -= photosInRow;
}

얘는 대체 뭔 미래를 방지한다는건지 모르겠음... 시키는거나 똑띠 했으면 좋겠구만.

 

일단 while문의 if문 맨 위를 보자. 저 문제가 터진게 4, 7, 10인데 1을 제외하면 3n+1이라는 공통점이 있다. 근데 내가 지피티한테 예시로 설명까지 들어가면서 설명했던 배치는 '2장 이상이면 무조건 3x+2y 형태로 배치가 되어야 한다'였고, 저대로라면 4장은 2+2, 7장은 3+2+2, 10장은 3+3+2+2가 되어야 하는데 내 요청을 또 무시하고 3n+1로 만들어버린 것. if문 맨 위는 사진 장 수를 3으로 나누기했을 때 1이 남았거나(3n+1) 4장이면 사진을 한 줄에 두 장 넣으라는 얘기이다. 7장에서 두장 빼면 5장은 3n+2니까 기존 로직으로 해결이 된다 이거지.

 

그럼 이걸로 끝인가요? 아뇨.

 

내가 표현식을 3x+2y로 쓴 건 3장짜리가 먼저 올라오기 때문이다. 그니까 사진이 7장이면 3+2+2, 10장이면 3+3+2+2, 13장이면 3+3+3+2+2 이런 식으로. 근데 지금 보시면 7장이 2+3+2로 올라오잖음? 근데 이것까지는 내가 지시를 안 했으니 그럴 수 있다 치자.

 

let photoRemain = countNum;
let photoIndex = 1;

if (photoRemain == 1) {
    const photoInbox = document.createElement('div');
    photoInbox.className = 'grid-row';

    const photosIndiv = document.createElement('div');
    photosIndiv.className = 'photo-row';
    photosIndiv.textContent = photoIndex++;

    photoInbox.appendChild(photosIndiv);
    photoContainer.appendChild(photoInbox);

    return;

}; //사진이 한 장만 있을 때

if (photoRemain === 4) {
    for (let i = 0; i < 2; i++) {
        const photoInbox = document.createElement('div');
        photoInbox.className = 'grid-row';

        for (let j = 0; j < 2; j++) {
            const photosIndiv = document.createElement('div');
            photosIndiv.className = 'photo-row';
            photosIndiv.textContent = photoIndex++;
            photoInbox.appendChild(photosIndiv);
        }

        photoContainer.appendChild(photoInbox);
    }
    return;
}//4장은 2+2

// 그 외 케이스
let threeRows = Math.floor(photoRemain / 3);
let extra = photoRemain % 3;

// 3장씩 먼저 배치
for (let i = 0; i < threeRows; i++) {
    const photoInbox = document.createElement('div');
    photoInbox.className = 'grid-row';

    for (let j = 0; j < 3; j++) {
        const photosIndiv = document.createElement('div');
        photosIndiv.className = 'photo-row';
        photosIndiv.textContent = photoIndex++;
        photoInbox.appendChild(photosIndiv);
    }

    photoContainer.appendChild(photoInbox);
}

// 3n + 1 → 2 + 2 배치
if (extra === 1) {
    for (let i = 0; i < 2; i++) {
        const photoInbox = document.createElement('div');
        photoInbox.className = 'grid-row';

        for (let j = 0; j < 2; j++) {
            const photosIndiv = document.createElement('div');
            photosIndiv.className = 'photo-row';
            photosIndiv.textContent = photoIndex++;
            photoInbox.appendChild(photosIndiv);
        }

        photoContainer.appendChild(photoInbox);
    }
}

// 3n + 2 → 2장 배치
else if (extra === 2) {
    const photoInbox = document.createElement('div');
    photoInbox.className = 'grid-row';

    for (let j = 0; j < 2; j++) {
        const photosIndiv = document.createElement('div');
        photosIndiv.className = 'photo-row';
        photosIndiv.textContent = photoIndex++;
        photoInbox.appendChild(photosIndiv);
    }

    photoContainer.appendChild(photoInbox);
}

저게 다 뜨는 게 아니고 3n+1… 1, 7, 10에서 뜬 걸 확인했다. 4는 원래 4장이라 패스. 내가 저거 실행해보고 하도 얼척없어서 지피티한테 이게 맞냐고 물었음.

 

사실 코드가 길고 복잡해보이지만 전체적으로 보면 사진 장수를 3장씩 먼저 올리고 남은걸 두 장씩 올리면 되는 매우 간단한 코드이다. 근데 뭐가 문제임? 이게 3n+2(5, 8, 11)장일때는 3장을 n줄씩 올리고 밑에 두 장이 들어가면 되는데, 3n+1이 되니까 나머지를 계산하는 과정에서 문제가 생긴 것이다.

let threeRows = Math.floor(photoRemain / 3);
let extra = photoRemain % 3;

이게 7장일때, 8장일때는 똑같이 threeRows가 2지만 extra가 각각 1, 2다. 그러니까 6+1, 6+2인건데

 

// 3n + 1 → 2 + 2 배치
if (extra === 1) {
    for (let i = 0; i < 2; i++) {
        const photoInbox = document.createElement('div');
        photoInbox.className = 'grid-row';

        for (let j = 0; j < 2; j++) {
            const photosIndiv = document.createElement('div');
            photosIndiv.className = 'photo-row';
            photosIndiv.textContent = photoIndex++;
            photoInbox.appendChild(photosIndiv);
        }

        photoContainer.appendChild(photoInbox);
    }
}

extra가 1일때 저 코드를 돌게 된다. 아니 그럼 애초에 3n+1이 3n+4로 배치되는거니까 걍 4장 남을때까지 3씩 빼면 안되는거임…? 그러면 개적화 되나?

 

 

코드_최종.js

const photoContainer = document.querySelector('.photocontainer');
const photoCount = document.querySelector('#photocount');
const photoButton = document.querySelector('#photobutton');

photoButton.addEventListener('click',(e)=>{
    let countNum = photoCount.value;
    console.log(typeof(countNum));

    photoContainer.innerHTML = ''; //아무것도 없지만 일단 비운다

    if(isNaN(countNum) || countNum <= 0) {
        alert ('유효한 숫자를 입력해주세요!');
    }; 

    let photoRemain = countNum;
    let photoIndex = 1;

    if (photoRemain == 1) {
        const photoInbox = document.createElement('div');
        photoInbox.className = 'grid-row';

        const photosIndiv = document.createElement('div');
        photosIndiv.className = 'photo-row';
        photosIndiv.textContent = photoIndex++;

        photoInbox.appendChild(photosIndiv);
        photoContainer.appendChild(photoInbox);
        return;
    }

    if (photoRemain == 4) {
        // 4장은 2 + 2 고정
        for (let i = 0; i < 2; i++) {
            const photoInbox = document.createElement('div');
            photoInbox.className = 'grid-row';

            for (let j = 0; j < 2; j++) {
                const photosIndiv = document.createElement('div');
                photosIndiv.className = 'photo-row';
                photosIndiv.textContent = photoIndex++;
                photoInbox.appendChild(photosIndiv);
            }

            photoContainer.appendChild(photoInbox);
        }
        return;
    }

    // 여기부터는 일반 배치
    let threeRows = 0;
    let twoRows = 0;

    if (photoRemain % 3 === 1 && photoRemain !== 4) {
        // 3n + 1 → 마지막 2 + 2 필요
        threeRows = Math.floor((photoRemain - 4) / 3);
        twoRows = 2;
    } else if (photoRemain % 3 === 2) {
        // 3n + 2 → 마지막 2 필요
        threeRows = Math.floor((photoRemain - 2) / 3);
        twoRows = 1;
    } else {
        // 3n → 다 3장 줄로 채움
        threeRows = Math.floor(photoRemain / 3);
        twoRows = 0;
    }

    // 3장 줄 배치
    for (let i = 0; i < threeRows; i++) {
        const photoInbox = document.createElement('div');
        photoInbox.className = 'grid-row';

        for (let j = 0; j < 3; j++) {
            const photosIndiv = document.createElement('div');
            photosIndiv.className = 'photo-row';
            photosIndiv.textContent = photoIndex++;
            photoInbox.appendChild(photosIndiv);
        }

        photoContainer.appendChild(photoInbox);
    }

    // 2장 줄 배치
    for (let i = 0; i < twoRows; i++) {
        const photoInbox = document.createElement('div');
        photoInbox.className = 'grid-row';

        for (let j = 0; j < 2; j++) {
            const photosIndiv = document.createElement('div');
            photosIndiv.className = 'photo-row';
            photosIndiv.textContent = photoIndex++;
            photoInbox.appendChild(photosIndiv);
        }

        photoContainer.appendChild(photoInbox);
    }

});

일단 이것도 바로 나온 게 아니라 문제가 하나 더 있었다. 이번에는 한 장 올렸는데 사진이 4장 올라가서

 

if (photoRemain === 1) {
    const photoInbox = document.createElement('div');
    photoInbox.className = 'grid-row';

    const photosIndiv = document.createElement('div');
    photosIndiv.className = 'photo-row';
    photosIndiv.textContent = photoIndex++;

    photoInbox.appendChild(photosIndiv);
    photoContainer.appendChild(photoInbox);
    return;
}

if (photoRemain === 4) {
    // 4장은 2 + 2 고정
    for (let i = 0; i < 2; i++) {
        const photoInbox = document.createElement('div');
        photoInbox.className = 'grid-row';

        for (let j = 0; j < 2; j++) {
            const photosIndiv = document.createElement('div');
            photosIndiv.className = 'photo-row';
            photosIndiv.textContent = photoIndex++;
            photoInbox.appendChild(photosIndiv);
        }

        photoContainer.appendChild(photoInbox);
    }
    return;
}

여기서 ===을 ==로 바꿨다. 그래서 됐어요? 예. 

 

저게 왜 문제냐면 ===로 하면 타입까지 비교하게 되는데, 막 input을 통해 입력받은 숫자는 숫자가 아니라 string… 그니까 문자다. ===를 쓰게 되면 컴퓨터 입장에서는 얘는 문자고 쟤는 숫자니까 다른거지만, ==를 쓰면 어쨌든 1이니까 아 ㅇㅋㅇㅋ 하게 되는 것. 이 사태를 예방하려면 아예 let countNum = photoCount.value;에서 등호 오른쪽을 parseInt로 감싸는 방법도 있다.

 

.grid-row {
    background-color: var(--sub-color);
    display: flex;
    gap: 10px;
    margin-bottom: 10px;
    transition: .5s;
}

.photo-row {
    flex: 1;
    height: 150px;
    border-radius: 5px;
    background-color: #603F83;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: .5s;
    color: var(--sub-color);
}

지금까지는 flex를 주는 요소들에 크기 속성이 따로 있었지만, 이번에는 3장일때, 2장일때, 한장일때 크기가 다 다르기때문에 크기가 상황에 따라 변해야 한다. 이걸 근데 JS단에서 만지나요? 아뇨, 그냥 flex: 1;주시면 됩니다.


한장

 

3n+1(10)

 

3n+2(14)

 

3n(9)

 

4장

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

우리도 그 유리 효과인지 뭔지 해봅시다 거

Coding/JavaScript 2025. 6. 18. 22:33

이번에 애플이 iOS 26을 발표하면서 리퀴드 글래스인가 뭐시기인가를 도입한다고 했는데… 우리 쿡이는 19 20 21 22 23 24 25를 못 세나요? 왜 갑자기 26으로 건너뛰었음? 우리 쿡쪽이는 수학공부를 안 했나? 

 

각설하고 우리도 그 리퀴드 글래스인지 뭔지 하는 그 효과 좀 내 보자. 근데 애플처럼 깔쌈하게 나올거라는 장담은 못 드림. 잊지 마십시오. 이 블로그 주인장은 뉴비입니다. 애플처럼 진짜 깔쌈한 유리 효과를 내고 싶으시면 여기 말고 다른 블로그를 가십시오.


일단 유리 하면 투명하다. 아무것도 안 묻은 새삥 유리는 투명한데… 문제가 하나 있다. 이걸 그대로 CSS에 도입하게 되면, 배경에 뭐가 있을 경우 콘텐츠가 안 보일 위험이 있다. 정확히는 사용자가 콘텐츠에 집중을 못 할 위험이 크고, 내용을 알아먹는것도 빡세다. 그래서 보통은 뒤에 블러를 준다. 왜 예전에 모달창 설명 할때도 모달창을 열 동안 배경에 블러를 줬죠? 그거 비슷합니다.

 

 

일단 오늘도 참고한 사이트가 있는데 

https://www.vibeaz.co.kr/content/apple-liquid-glass-css-effect/

 

순수 CSS로 애플 Liquid Glass 효과 구현 방법

순수 CSS로 애플의 Liquid Glass 효과를 재현하는 방법. backdrop-filter, rgba, ::after 활용한 유리질감 디자인. 웹 개발자 및 UI/UX 디자이너를 위한 트렌디한 기법.

www.vibeaz.co.kr

여기다. 

 

See the Pen Untitled by koreanraichu (@koreanraichu) on CodePen.

그리고 참고한 결과가 이거. 코드가 생각보다 간단하기 때문에 놓칠 염려도 없다.

 

.glass {
  width: 290px;
  height: 240px;
  padding: 5px;
  color: #ffffff;
  background: rgba(255, 255, 255, 0.15);
  backdrop-filter: blur(2px);
  box-shadow: inset 0 4px 20px rgba(255, 255, 255, 0.3);
  border: rgba(255, 255, 255, 0.8);
}

여기서 위에 네 줄은 무시해도 된다. 아래 네 줄이 리퀴드 글래스 효과를 낼 때 필요한 요소들이다. 엥? 블러는 뭔가요? 일단 저 코드펜은 배경이 그라데이션이라 체감이 안 되겠지만 배경에 사진이 있다면 어떻게 될까?

 

이게 블러 한거고

 

이게 블러 뺀 거다. 배경을 일부러 어두운 톤으로 골라서 오른쪽 박스만 영향권에 걸쳐있긴 한데, 아래보다 위쪽 사진의 내용이 좀 더 확 들어온다. 그죠? 그래서 블러를 준 게 아닐까 생각한다. 배경은 내가 직접 만든거니까 저작권 걱정 ㄴㄴ요.

 

그럼 이 리퀴드 글래스가 겹치면 어떻게 되냐고? 

투명도가 적용되어 있는거긴 한데 알파가 0이 아니니까 겹치면 그만큼 하얀색이 진해지겠죠.

 

glass 클래스랑 애프터만 지정해두고 HTML 요소에 클래스만 주면 적용하는 건 쉽다. 저건 textarea다.

 

당연한 얘기지만 색도 바꿀 수 있다. 근데 좀 미미하긴 하다.

 

스크롤바도 원한다면 바꿀 수는 있는데 그렇게 되면 스크롤바가 잘 안 보여서 권하지는 않는다.

 

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

자바스크립트로 음악 재생기를 만들어보자

Coding/JavaScript 2025. 6. 7. 02:28

오늘 그래서 뭘 해볼거냐... 두가지를 해볼건데

 

1. 당신 컴퓨터에 있는 음악 파일(예: *.mp3)을 열어서 
2. 재생할거다. 

끝이다. 나한테 많은 걸 기대하지 말라.


파일 첨부를 할 수 있게끔 만들자

컴퓨터에 있는 파일을 불러오려면 input type="file"을 써야 한다. 그리고 이 인풋을 통해 사용자가 음악 파일만 입력하도록 해야 하는데

<input type="file" id="uploader" accept=".mp3, .wav, .wma, .flac">

내가 아는 음악파일이 이거밖에 없으니 걍 이것만 함.

 

그리고 저 파일 첨부하는 거 그대로 올라와있는 거 뵈기 싫어서 라벨 연결하고 숨겼다.

 

본격적으로 자바스크립트에 들어가자

올 것이 왔다. 아주 크나큰 난관이 예상되지만 아무튼...

 

const musicUpload = document.querySelector('#uploader');

var audio = new Audio(musicUpload.file);
audio.load();
audio.volume = 1;
audio.play();

원래 이런건 한번에 되는 게 이상한거긴 한데... 왜 안되는지 모르겠음. audio.play()에서 Uncaught (in promise) NotAllowedError: play() failed because the user didn't interact with the document first. 라는 오류가 나고 있다. 

 

const musicUpload = document.querySelector('#uploader');
const playMusic = document.querySelector('#playmusic');


playMusic.addEventListener('click', ()=>{
    var audio = new Audio(musicUpload.file);
    audio.load();
    audio.volume = 1;
    audio.play();
})

저게 브라우저에서 자동 재생을 막아서 그렇다는겨. 그래서 버튼을 만들어주고, 파일을 올린 다음 재생하게끔 했는데 저것도 재생이 안되는거다. 그래서 지피티한테 물어봤지.

 

  • .file이 아니라 **.files[0]**을 사용해야 합니다.
  • input[type="file"] 요소에서 사용자가 업로드한 파일을 가져오려면 .files[0]으로 첫 번째 파일을 접근해야 합니다.
  • 또한 Audio() 생성자에 Blob URL을 전달해야 브라우저가 로컬 파일을 재생할 수 있어요.

예? 뭘 하라고요?

 

왜 files[0]일까? 

https://velog.io/@byungju0624/Inputtypefile-%ED%8C%8C%EC%9D%BC-%EC%A0%95%EB%B3%B4-%EA%B0%96%EA%B3%A0-%EC%98%A4%EA%B8%B0

 

Input[type="file"] 파일 정보 갖고 오기

파일의 정보를 잘 확인할 수 있음을 알수있다.

velog.io

 

우연히 찾았던 이 사이트에서 그 답을 찾았다. result 코드에 FileList라고 나오길래 아 얘도 쿼리셀렉터올처럼 인덱싱이 필요하구나… 하고 이해했음. 블롭 URL은 내가 올린 음악 파일을 처리하기 위해 임시 URL을 만들어주는 거라고 생각하면 된다.

 

참고로 파일을 불러온 상태에서 console.log로 files를 가져오면 이렇게 뜬다.

 

playMusic.addEventListener('click', () => {
    const file = uploader.files[0];
    if (!file) {
        alert("오디오 파일을 업로드해주세요.");
        return;
    }

    const audio = new Audio(URL.createObjectURL(file));
    audio.volume = 1;
    audio.play();
});

아무튼 그래서 이렇게만 하면 일단 재생은 된다. 재생은. 

오디오 컨트롤러 넣기

일단 저 상태에서 열고 재생해도 되는데, 문제가 뭐냐면 이건 걍 bgm이다. 그래서 재생버튼을 누르면 오디오 콘트롤러가 뿅 하고 튀어나오게 만들건데... 이것도 지피티가 짜줌. 헤헤.

 

const playerContainer = document.querySelector('#playercontainer');

일단 기존 HTML의 버튼 밑에 새로운 div를 만든다. 여기다가 audio 태그를 붙일거다.

 

playMusic.addEventListener('click', () => {
    const file = uploader.files[0];
    if (!file) {
        alert("오디오 파일을 업로드해주세요.");
        return;
    }

    playerContainer.innerHTML = '';

    const audioElement = document.createElement('audio');
    audioElement.controls = true;
    audioElement.src = URL.createObjectURL(file);
    playerContainer.appendChild(audioElement);
});

그리고 이렇게만 하면 된다. 이렇게 하고 음악 파일을 열고 버튼을 누르면 audio태그가 뿅 하고 나온다. 이제 버튼 위치 잡아야지… ㅡㅡ

 

아, 참고로 오디오 태그가 추가된 버전은 자동재생이 안되기때문에 재생버튼을 눌러야된다.

 

오디오태그는 커마가 안되나요? 

결론부터 말하자면 길이 외에는 커마가 안 된다. 저게 길이 늘린 버전이고, 직접 본인이 만들거나 라이브러리를 받아와야 한다…

 

지피티의 힘으로 커마 완료함. 반복재생이요?

 

그것도 커마 했다.

 

여기까지 지피티의 힘을 빌려서 커마 끝… OTL 이게 하나 하면 또 추가하고싶고 이거 추가하면 또 추가하고싶고 해서 해달라는게 점점 늘어요… 얘는 과정은 따로 서술 안 하고 깃헙에 올리기만 하겠습니다. 참고로 글꼴은 변경했고 생각보다 한글 글꼴중에 한자 지원 안 하는 글꼴이 많아서 구글 웹폰트에서 일본어 폰트 따로 추가했습니다.

 

현재 지원되는 기능은


1. 플레이리스트(파일 여러개 여는거)

2. 반복재생(전체반복, 한곡반복, 반복없음)

3. 셔플(사진에는 ON되어있음)

4. 앨범 아트, 아티스트, 제목 불러와서 표시하기

5. 이전곡 일시정지(재생) 다음곡

 

이정도…

 

오늘의 결론: 지피티와 함께라면 뭐든지 만들 수 있다. 남자친구 빼고 

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

체크박스를 버튼처럼 만들어보자

Coding/JavaScript 2025. 5. 19. 23:51

왜 가끔 그런거 있다. 여러개(혹은 하나)를 선택하는 선택지인데 버튼같이 생긴 거. 그럼 이건 체크박스거나 라디오 버튼이라는 얘기인데, 이걸 체크박스로 만들 수 있나?

 

를 해볼거다. 이건 뭐 


<div class="wrapper">
  <input type="checkbox" id="checkbox1" name="pringles">
</div>

일단 체크박스를 만들고 

 

input[type="checkbox"] {
  width: 100px;
  height: 33px;
  appearance: none;
  background-color: #ffffff;
  color: #000000; 
  border: 1px solid #000000;
}

input[type="checkbox"]:checked {
  background-color: #000000;
  color: #ffffff;
}

appearance: none;을 줘서 기존의 네모 상자를 없애면 이런 식으로 커스터마이징이 가능하다. 그럼 안에 글자는요?

 

input[type="checkbox"]:before {
  content: "프링글스 오리지날";
}

나는 이렇게 줬다. :before 쓴 다음 콘텐츠에 이렇게 주면 버튼 안에 글자가 들어간다. 참고로 선택시에도 따로 줄 필요는 없다.

 

input[type="checkbox"] {
  width: 220px;
  height: 45px;
  font-size: 12pt;
  appearance: none;
  text-align: center;
  vertical-align: middle;
  line-height: 2em;
  margin: 10px;
  padding: 6px;
  border-radius: 10px;
}

.original {
  background-color: #ffffff;
  color: #cc0000;
  border: 1px solid #cc0000;
}

.original:checked {
  background-color: #cc0000;
  color: #ffffff;
}

.original:before {
  content: "프링글스 오리지날";
}

.sourcream {
  background-color: #ffffff;
  color: #58bf6d;
  border: 1px solid #58bf6d;
}

.sourcream:checked {
  background-color: #58bf6d;
  color:#ffffff;
}

.sourcream:before {
  content: "프링글스 사워크림&어니언";
}

.cheese { 
  background-color: #ffffff;
  color: #ef901b;
  border: 1px solid #ef901b;
}

.cheese:checked {
  background-color: #ef901b;
  color: #ffffff;
}

.cheese:before {
  content: "프링글스 치지치즈";
}

.hot { 
  background-color: #ffffff;
  color: #3a2c28;
  border: 1px solid #3a2c28;
}

.hot:checked {
  background-color: #3a2c28;
  color: #ffffff;
}

.hot:before {
  content: "프링글스 핫&스파이시";
}

.galbi { 
  background-color: #ffffff;
  color: #74a8bd;
  border: 1px solid #e99b55;
}

.galbi:checked {
  background-color: #e99b55;
  color: #ffffff;
}

.galbi:before {
  content: "프링글스 한국식 숯불갈비";
}

버튼이 다섯개... 

 

See the Pen checkbox button by koreanraichu (@koreanraichu) on CodePen.

맛별로 체크박스 색깔이 다 다르다. 이것때문에 CSS가 늘어난거임…

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

토스트 창을 만들어보자

Coding/JavaScript 2025. 5. 17. 00:16

토스트 창이 뭐냐… 가끔 뭐 하다보면 뿅 하고 나오는 그거 맞다. 예를 들자면 메일을 보냈을 때, 화면 한 켠에서 보냈습니다! 하고 뿅 나왔다가 사라지는 그거 말이다.


일단 들어가기 전에 생각해보자. 토스트창을 왜 쓰나요? 모달창도 있고 alert()로 알림을 띄울 수도 있는데 굳이 토스트 창을 만드는 이유가 뭘까? 모달창과 alert()로 띄우는 알림은 사용자가 상호작용을 해야 없어진다. 뭐 예를 들어서 오류가 났다, 그거는 내가 확인하고 상호작용 해야 하는거니까 모달이나 alert()를 쓰는 게 맞다. 오류가 났으면 오류메시지를 통해서 뭔 오류가 어떻게 터졌는지 보고 확인하고 문의를 하든 대처를 해야 하니까. 근데 그게 상대적으로 사소한거라 굳이 모달이나 alert()로 안 해도 되는 알림이라면?
 
예를 들어서 쿠팡에서 냉동 볶음밥 1+1 패키지를 장바구니에 담았다고 해 보자. 그럼 장바구니에 담겼다고 알림이 나한테 가면서 장바구니 아이콘에 있는 숫자가 +1 될것이다. 이건 어떻게 보면 상대적으로 사소한 알림이기도 하고, 사용자 입장에서는 장바구니 아이콘 숫자로 확인할수도 있는데 이걸 굳이 모달이나 alert()로 띄워야 할 필요가 있을까? 물론 모달창으로 만들고 장바구니 보기 버튼을 눌러서 장바구니로 가갈지말지 선택하게 할 수도 있다.
 
반대로 생각해보자. 냉동 볶음밥 1+1 패키지를 담고 결제를 하는 과정에서 뭔가 문제가 생겼다면 내가 문제가 생겼다는 걸 인지하고 다시 결제를 하던가 잔액부족이면 잔액을 채워넣던가 해야 한다. 그럴 때는 토스트 창으로 에러 메시지를 뿅 나왔다가 뿅 사라지게 하면 내 입장에서는 이게 구매가 된건지 어쩐건지 모를 것이다. 이렇게 꼭 한 가지만 고집할 필요도 없고, 꼭 한가지를 배제할 필요 없이 적재적소에 잘 쓰면 된다. 그래서 왜 비교적 사소한 알림에 토스트 창을 쓰냐고? 그런것까지 일일이 버튼 누르기 귀찮자낭.


.toast {
  width: 250px;
  height: 50px;
  font-size: 16pt;
  text-align: center;
  position: fixed;
  top: 30px;
  right: 30px;
  border-radius: 8px;
  padding: 5px 10px;
  box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}

#toast1 {
  background-color: #fefefe;
}

#toast2 {
  background-color: #dd7788;
}

#toast3 {
  background-color: #77dd88;
}

토스트창 세 개가 있는데 공통적인 CSS를 주기 위해 일단 클래스를 줬고, 각기 다른 세 개에 아이디도 부여한 상태이다. 이 상태에서는 아직까지 화면 구석에 토스트창이 '보이는'게 맞다. 왜? opacity 안 바꿨잖아.
 

const toast1 = document.querySelector("#toast1");
const toast2 = document.querySelector('#toast2');
const toast3 = document.querySelector('#toast3');
const toastBtn = document.querySelectorAll('button');

이제 고난의 자바스크립트 시간이다. 코드펜으로 하는거라 캡처는 별도로 안 올렸는데, 토스트창이 세 개 있고 버튼도 세 개다. 각 버튼당 토스트창이 하나씩 할당되는 구조이다. 즉, 버튼이 세개 주르륵 있으니까 쿼리셀렉터올로 가져온 것이다. 
 

const toast1 = document.querySelector("#toast1");
const toast2 = document.querySelector('#toast2');
const toast3 = document.querySelector('#toast3');
const toastBtn = document.querySelectorAll('button');

function toastOn(item) {
  item.style.opacity = 1;
}

toastBtn[0].addEventListener('click',()=>{
  toastOn(toast1);
});

toastBtn[1].addEventListener('click',()=>{
  toastOn(toast2);
});

toastBtn[2].addEventListener('click',()=>{
  toastOn(toast3);
});

버튼은 라디오버튼이나 체크박스처럼 if문으로 못 주나… 아무튼, 이렇게 하면 버튼을 눌렀을 때 토스트 창이 나타난다. 근데 문제가 하나 있다. 토스트창은 나타난 다음에 일정 시간이 지나면 사라져야 하는데, 이 창이 사라지질 않아요. 왜? JS로 투명도 1로 만드는 것만 했잖음. 일정 시간이 지나면 다시 투명도가 0이 되게 해야 토스트창이 사라지지.
 

function toastOn(item) {
  item.style.opacity = 1;
  setTimeout(()=>{
    item.style.opacity = 0;
  }, 1000)
}

setTimeout()함수를 써서 일정 시간 후에 다시 투명도 0으로 바꿔주십쇼~ 하면 된다.
 
See the Pen Untitled by koreanraichu (@koreanraichu) on CodePen.

눌러보면 뭔가 뿅 나올 것이다. 


그럼 여기서 이 글을 본 여러분들은 이런 궁금증을 가질 수도 있다. '이런거 말고 밑에서 올라오는(혹은 위에서 내려오는) 토스트 창은 어떻게 만들어요?'
 
그건 간단하다. CSS 속성을 위치에 주고 스타일을 위치로 바꾸면 된다. 뭔 소리냐고?

.toast {
  width: 300px;
  height: 50px;
  font-size: 16pt;
  text-align: center;
  position: fixed;
  top: -130px;
  right: 30px;
  border-radius: 8px;
  padding: 5px 10px;
  box-shadow: 0 4px 8px rgba(0,0,0,0.2);
  background-color: #eeeeee;
  transition: .5s;
}

기존 코드에서 opacity를 빼고 transition을 반값때린 코드이다. 위에서 튀어나오는 토스트 창을 만들고 싶으면 여기서 top을 음수로 바꿔주면 된다. 일단 -130px로 바꿨다.
 
 

function toastOn(item) {
  item.style.top = '30px';
  setTimeout(()=>{
    item.style.top = '-130px';
  }, 1000)
}

기존의 opacity를 바꿔주는 코드도 갖고와서 top을 바꿔주면 된다.
 
See the Pen Toast window 2 by koreanraichu (@koreanraichu) on CodePen.

참 쉽죠? 

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

아이디 생성기에 복사버튼을 달아보자

Coding/JavaScript 2025. 5. 9. 01:19

https://koreanraichu.tistory.com/634

 

클립보드 버튼을 만들어보자

오늘 해볼 건 그거다. 일반적으로 복사할때는 컨트롤씨 컨트롤브이를 눌러야 하는데, 가끔 다른 앱이나 사이트같은 거 이용하다 보면 클릭하면 복사가 되거나 옆에 있는 버튼을 누르면 복사되

koreanraichu.tistory.com

여기서 이어진다.


기존의 아이디 생성기는 생성된 아이디를 직접 드래그해서 컨트롤씨를 눌러야 복사가 됐다. 솔직히 붙여넣는건 그렇다치고 복사까지 그렇게 한다고요? 아 귀차낭. 그래서 복사버튼을 추가해봤음. 그래서 전체적으로 어떻게 바꼈느냐…

 

1. 전체적인 디자인 변경
2. 체크박스에 라벨 추가(이거 해두면 글자 눌러도 체크박스 선택됨)
3. 입력창 포커스 상태 CSS 추가
4. 생성되는 아이디에 복사버튼 추가(복사시 우상단에 토스트 메시지 생성됨)

이렇게 됐다.


일단 이전 글과 달리 이번에 추가할 버튼... 요소가 HTML에 없다. 왜냐하면 아이디 생성기는 아이디 만들 때 p태그를 동적으로 생성하고, 이번에 할 일은 p태그를 동적으로 생성할 때 버튼을 '같이' 생성해서 오른쪽에 나란히 붙여줘야 한다. 벌써부터 빡셈의 기운이 느껴진다고요? 예, 이것때문에 지피티 불렀습니다.

 

토스트 메시지

일단 쉬운것부터 보자. 

function showToast(message) {
    const toast = document.createElement("div");
    toast.innerText = message;
    toast.style.position = "fixed";
    toast.style.top = "30px";
    toast.style.right = "30px";
    toast.style.backgroundColor = "var(--main-color)";
    toast.style.color = "var(--sub-color)";
    toast.style.padding = "10px 20px";
    toast.style.borderRadius = "8px";
    toast.style.boxShadow = "0 4px 8px rgba(0,0,0,0.2)";
    toast.style.zIndex = 1000;
    toast.style.opacity = "0";
    toast.style.transition = "opacity 0.3s ease";
    toast.style.fontSize = "16pt";

    document.body.appendChild(toast);

    // 페이드 인
    setTimeout(() => {
        toast.style.opacity = "1";
    }, 10);

    // 2초 뒤 제거
    setTimeout(() => {
        toast.style.opacity = "0";
        // 페이드 아웃 후 DOM에서 제거
        setTimeout(() => {
            document.body.removeChild(toast);
        }, 300);
    }, 2000);
}

토스트 메시지는 뭐냐면 사용자가 어떤 상호작용을 할 때 뿅 하고 튀어나오는 작은 메시지이다. 뭐 예를 들어서 보내기 버튼을 눌렀다면 보냈습니다 하고 뿅 나왔다가 사라지는 메시지. 토스트 메시지는 생성된 아이디를 복사하기 위해 버튼을 눌렀을 때 복사되었습니다!가 뜬다. 그게 다다. 이건 지피티가 만들면서 동적으로 CSS 줘서 이렇게 된 거다. div로 만들고 따로 클래스를 안 줘서 저게 최선이더라...

 

그럼 이제 끝판왕 차례다.

 

복사버튼

function makeID() {
    let idLength = idLengthval.value;
    let idText = "";
    let word_list = makelist();
    for (let i = 0; i < idLength; i++) {
        idText += word_list.charAt(Math.floor(Math.random() * word_list.length));
    }
    const idValue = document.createElement("p");
    idValue.innerText = idText + " ";

    const clipBoard = document.createElement("button");
    clipBoard.innerHTML = `<i class="fa-solid fa-clipboard"></i> Copy`;
    idValue.appendChild(clipBoard);

    idValarea.appendChild(idValue);

    clipBoard.addEventListener("click", () => {
        navigator.clipboard.writeText(idText);
        showToast("ID 복사 완료!");
    });
}

벌써 뭐가 많다고요? 나도 그렇게 생각한다.

 

일단 위에도 썼듯이 아이디 생성기는 아이디를 만들때 p태그를 동적으로 생성하기때문에 HTML파일에는 아무것도 없다. 그래서 문제가 뭔데요?

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);
    };
}

이전버전 코드를 한번 봅시다. 그러면 좀 하시는 분들은 뭐가 문제인지 바로 알 수 있다. 아니 그 속도 이런거 말고. 

 

idValue가 생성된 아이디이고 얘를 그냥 다이렉트로 붙여버리기때문에 여기에 버튼을 만들어서 그냥 갖다 붙여버리면 에러가 나거나 버튼이 우리가 생각하는 그 버튼이 아닌 [HTML Object Button] 뭐 이런게 반긴다.

 

idValue.appendChild(clipBoard);

idValarea.appendChild(idValue);

다시 이 부분을 보면 두번 붙이는 걸 알 수 있는데, idValue는 만들어진 아이디고 clipBoard는 복사버튼, idValarea는 생성된 아이디가 나열되는 div이다. 그니까 얘를 p태그 버튼 이렇게 병렬로 붙이는 게 아니라 버튼을 만들어서 p태그에 붙이고 그 p태그를 나열시킨 것. 이게 정말 빡셌음.

 

나머지는 크게 1. 아무것도 선택 안 했을 때 알림 뜨게 하는 것, 2. 뭔진 모르겠지만 좀 더 빨라진 정도...

 

그래서 결과 

저게 빡세서 글치 CSS 따로 만져주고 글꼴이랑 색깔 바꿔준 거 말고 없다. 라벨이랑.

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

클립보드 버튼을 만들어보자

Coding/JavaScript 2025. 5. 8. 23:45

오늘 해볼 건 그거다. 일반적으로 복사할때는 컨트롤씨 컨트롤브이를 눌러야 하는데, 가끔 다른 앱이나 사이트같은 거 이용하다 보면 클릭하면 복사가 되거나 옆에 있는 버튼을 누르면 복사되는 뭐 그런 게 있다. 그걸 해 볼거다.\


그냥 이렇게 만들었음. 여기서 뭘 해볼거냐면 위에다가 아무거나 텍스트를 쓰고 저 클립보드 아이콘을 누르면 텍스트 상자에 입력한 게 복사가 되게 할 거다.

 

const Text = document.querySelector('#text');
const copyButton = document.querySelector('#button');

어디갑니까 가져와야지. 

 

우리야 컨트롤씨 컨트롤브이 하면 다 되니까 복붙은 쉽다고 생각할 수 있지만, 사실 복붙을 하려면 컴퓨터 입장에서는 클립보드에 적어뒀다가 그걸 붙여넣어야 한다. 그러니까 Ctrl+C 없이 저 버튼으로 복사가 되려면 저 버튼을 눌렀을 때 저 텍스트 상자 안에 있는 걸 클립보드로 기록해야 한다. 그리고 위 사진에 있는 게 클립보드다. (윈도우 기준으로 win키+v 누르면 열림)

 

const Text = document.querySelector('#text');
const copyButton = document.querySelector('#button');

copyButton.addEventListener('click',(e)=>{
    console.log(window.navigator.clipboard.writeText(Text.value));
});

일단 이렇게 하면 되는 것 같다.

 

입력하고 버튼 눌러보자.

 

여기까지 하면 된 거 맞는데, 문제가 하나 있다. 사용자들 중에 컴찐잘알이 몇이나 있겠음? 솔직히 여기 들어와서 왼도우키+v 누르면 클립보드가 나오는구나 한 분들도 계실 거 아니예요. 그리고 잘알 여부와 상관없이 복사하고 아무것도 안 뜨면 이게 된건지 아닌건지 몰라요. 그죠?

 

const Text = document.querySelector('#text');
const copyButton = document.querySelector('#button');
const Copied = document.querySelector('.copied');

copyButton.addEventListener('click',(e)=>{
    window.navigator.clipboard.writeText(Text.value);
    Copied.style.display = "block";
});

alert 줘도 되는데 alert는 확인 눌러야돼서 걍 밑에 새로 요소를 만들었다. 버튼 누르는거 귀찮음... 

 

클립보드(캡쳐하고 확인했음)

버튼을 누르면 복사가 되고 밑에 복사되었습니다! 가 뜬다. 물론 이 텍스트는 갓 페이지를 열었을때는 안 보인다.

 

const Text = document.querySelector('#text');
const copyButton = document.querySelector('#button');
const Copied = document.querySelector('.copied');

copyButton.addEventListener('click',(e)=>{
    window.navigator.clipboard.writeText(Text.value);
    Copied.style.display = "block";
    setTimeout(() => {
        Copied.style.display = "none";
    }, 500)
});

이렇게 하면 텍스트를 잠깐 나타났다가 사라지게 할 수도 있다.

 

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

::selection 색상을 부분부분 다르게 할 수 있을까?

Coding/JavaScript 2025. 4. 23. 01:46

일단 ::selection이 뭐냐면

 

이거다. 이게 뭐냐고? 마우스로 긁었을 때의 색깔을 ::selection을 통해 줄 수 있다. 기본은 파란 배경에 흰색 글씨인데, 가끔 코딩 좀 하시는 분들 블로그 가 보면 텍스트를 마우스로 긁었을 때 배경색이 다른 것을 볼 수 있다.

 

티스토리 블로그(여기)

이것도 보면 위랑 색깔이 다르죠? 

 

괴담수사대

괴담수사대는 일부러 빨간색+어두운색 기반으로 색깔을 정해서 ::selection 배경이 빨간색이다. 이런 효과를 줄 때 배경색은 걍 CSS 백그라운드 주면 되고 글자색은 color 주면 된다.

 

::selection {
    background-color: #cc0000;
    color: #ffffff;
}

이렇게 말이지. CSS는 저렇게 ::붙어있는 게 있는데 다른건 나중에 또 알아봅시다.

 

참고로 괴담수사대에 적용된 ::selection은 배경색이 #ff0000이 아닌데, #ff0000으로 하면 블록 긁다가 여러분들 눈뽕 옵니다.


어제 꼭두새벽에 뜬금없이 생각난 게 하나 있는데, 바로 ::selection을 글의 특정 비율(50% 이런 식으로 기준을 정해서)을 정해서 색을 나눌수 있을까였다. 그러니까 그 아수라백작처럼 말이다.

 

일단 결론부터 말하자면 특정 부분'부터' 블록을 씌웠을 때 색을 다르게 하는건 자바스크립트를 이용한 동적제어를 통해 가능하지만, 아예 블록을 씌웠을때 반반무마니가 되게 하는 건 자바스크립트로도 불가능하다. 아니, 사실 가능은 한데 약간 O(n)이 n!일때마냥 존내 비효율적이라 걍 CSS단에서 클래스 주는 게 낫단다. 이거 채찍피티한테 물어봄.

 

See the Pen 반반 selection by koreanraichu (@koreanraichu) on CodePen.

일단 모바일로 씌웠을때는 안보이고 PC에서 마우스로 긁어야 되긴 한데, 문단에 따라 마우스로 긁었을 때 색깔이 다르게 적용된 것을 볼 수 있다. 클래스를 다르게 주고 CSS단에서 색깔만 다르게 준 것이다. 이를 응용하면 특정 문장이나 문단을 블록 씌운 상태에서도 강조할 수 있다. 굳이 이렇게까지 할 필요가 있을지는 모르겠지만.

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

글자 수 카운터를 만들어보자

Coding/JavaScript 2025. 4. 8. 01:01

마스토돈 인스턴스 중 하나인 Hyperspace

 

Bluesky(머스크가 똥싸는 중인 트위터의 대항마(?))

우리가 해 볼 게 위 그림 두 장에 나와있다. 머스크가 인수해서 똥을 싸제끼고 있는 트위터나 마스토돈, 블루스카이에는 한 번에 올릴 수 있는 내용에 글자수 제한이 있다. 트위터는 140자, 마스토돈은 500자(하이퍼스페이스 인스턴스), 블루스카이는 300자.

 

오늘은 입력창의 글자수를 실시간으로 세 보는 걸 할 건데, 해볼 게 총 두가지이다. 첫번째는 그냥 입력창에 몇 글자가 입력됐는지를 세 주는거고, 두번째가 위 두 장의 사진에 나와있는 n자 제한에서 n자가 남았는지 표시해보는 것이다. 아, 저 원은 아쉽게도 구현을 못 한다. 내 능력 밖임.


입력창의 글자 수를 나타내보자

그러니까 일단 남은 글자수는 모르겠고 몇 글자 썼는지 실시간으로 띄우는 것 먼저 해 보자. 얘를 해야 밑에께 좀 더 쉬워진다. 뭐 해요, VScode 키셔야지.

 

여기서 h1만 뭐가 없고 밑에 textarea랑 p는 클래스나 아이디가 하나씩은 있다. 왜? JS에서 갖고와야 하니까요.

 

const inputarea = document.querySelector('#inputarea');
const counter = document.querySelector('.count');

갖고 왔다고 끝이 아닌 거 다들 아시죠? 이제 험난한 코딩이 시작될것이다.

 

const inputarea = document.querySelector('#inputarea');
const counter = document.querySelector('.count');

inputarea.addEventListener('keyup',()=>{
    counter.innerText = inputarea.value.length;
});

그렇게 생각하던 시절이 저에게도 있었습니다. 아니 코드가 진짜로 저게 다다. 그리고 우리가 여기서 봐야 할 부분은 이벤트 리스트너밖에 없다. 진짜 이게 다예요? 네! 이게 답니다! 나도 내 두 눈을 의심했다니까.

 

여기 보이죠? 텍스트 입력하니까 21자라고 나오는 거.

 

지금까지 여기서 했던 코딩들을 보면 이벤트 리스트너에 'click'이 들어가 있다. 그러니까 이 클릭은 뭐냐면 뭘 눌렀을 때 이벤트를 발동시켜라 이 얘기다. 근데 이번 코드에는 클릭이 아니라… keyup? 이게 뭐임?

 

이벤트 리스트너의 트리거 중에는 키업(keyup)과 키다운(keydown)이 있다. 각각 키보드를 놓을 때/키보드를 누를 때 이벤트를 발동시켜라 이건데 무슨 차이인지는 모르겠음… 아무튼 키보드를 누를 때 이벤트를 발동시켜서 저 네모 상자에 입력한 텍스트가 몇 글자인지 알려줘라~ 라는 것만 알아두시면 된다. 그럼 이제 다음걸로 넘어가보자.

 

남은 글자 수를 나타내보자

일단 이건 위에꺼랑 달리 단순히 몇 글자를 썼냐를 표시하는 게 아니라, 본문 맨 위에 있는 스크린샷처럼 제한 글자 수에서 남은 글자 수를 표시할거다. 예를 들어서 글자 수가 140자라면 아무것도 안 썼을 때 140부터 시작해야 한다. 이해 하셨죠?

 

이게 아까 그거인데, 총 21자 나온다. 그럼 우리는 최대 글자 수에서 저걸 뺀 만큼을 출력하면 된다. 그러니까 아까 그 코드에서 JS 코드의 이벤트 리스트너 부분을 수정하면 된다. 일단 여기서는 최대 글자 수를 200자로 정해보자.

 

const inputarea = document.querySelector('#inputarea');
const counter = document.querySelector('.count');
let maxtext = 200;
counter.innerText = maxtext;

inputarea.addEventListener('keyup',()=>{
    counter.innerText = maxtext - inputarea.value.length;
});

일단 이 코드에는 두 가지 변경점이 있다. 하나는 위에도 썼듯이 최대 글자 수에서 현재 글자 수를 뺀 걸 출력하게끔 바꾼거고… 다른 하나는 뭔가 하고 코드를 유심히 들여다 본 여러분은 뭔가 두 줄 추가된 것을 발견했다.

 

let maxtext = 200;
counter.innerText = maxtext;

엥? 이거 머임? 이거 왜 추가했어요? 얘가 없으면 페이지를 갓 띄웠을 때는 남은 글자수가 0으로 나온다. HTML코드에 0으로 되어있거든... 이게 뵈기 싫으면 아예 p태그나 span태그만 만들어놓고 innerText 박아버리는 방법도 있긴 하다. 대신 창이 열렸을 때 내용을 바꾸게 별도로 코딩은 해야겠지만.

 

그래서 남은 글자수만큼 제대로 표시해준다.

 

근데 또 여기서 끝내기는 뭔가 아쉽죠? 그래서 한가지를 더 해볼거다. 남은 글자 수에 따라서 저 밑에 숫자 색을 바꿔볼건데… 첫번째로는 전체 글자의 90% 이상을 썼을 때, 그러니까 10%가 남았을 때 주황색으로 바뀌게 할 거고, 두번째로는 글자 수가 전체 글자 수를 초과했을 때(음수가 될 때) 빨간색으로 바꿀거다.

 

남은 글자 수에 따라 색깔 변경하기

const inputarea = document.querySelector('#inputarea');
const counter = document.querySelector('.count');
let maxtext = 200;
counter.innerText = maxtext;

inputarea.addEventListener('keyup',()=>{
    counter.innerText = maxtext - inputarea.value.length;

    if (inputarea.value.length < (maxtext * 0.1)) {
        console.log(maxtext * 0.1)
        counter.classList.add('almost');
    } else if (inputarea.value.length > maxtext) {
        counter.classList.add('over');
    }
});

그래... 이게 한번에 될 리가 없지. 이렇게 해놨더니 글자를 입력하기만 하면 주황색으로 바뀐다...

 

const inputarea = document.querySelector('#inputarea');
const counter = document.querySelector('.count');
let maxtext = 200;
counter.innerText = maxtext;

inputarea.addEventListener('keyup',()=>{
    counter.innerText = maxtext - inputarea.value.length;

    if (inputarea.value.length > (maxtext * 0.9)) {
        counter.classList.add('almost');
    } else if (inputarea.value.length > maxtext) {
        counter.classList.add('over');
    };
});

뭔가가 안 될때는 코드에 원인이 있음.

 

뭐가 문제였냐면, 일단 inputarea.value.length는 현재 입력한 글자이다. 그러니까 저 변수를 안 건들고 조건을 제대로 설정하려면 현재 입력한 글자가 180자가 '넘어가야'한다. 왜? 그래야 남은 글자수가 20자 미만이 되니까. 그래서 그거 변경해줬더니 됐다.

 

남은 글자 수가 전체 글자 수의 10% 이상
남은 글자 수가 전체 글자 수의 10% 미만

여기까지는 됐는데, 글자수가 오버됐을 때 빨간색으로 안 바뀌는겨… 진짜 아무리 해도 안바껴서 채찍피티한테 물어봤다.

 

const inputarea = document.querySelector('#inputarea');
const counter = document.querySelector('.count');
const maxtext = 200;

counter.innerText = maxtext;

inputarea.addEventListener('keyup', () => {
    const length = inputarea.value.length;
    const remaining = maxtext - length;

    counter.innerText = remaining;

    counter.classList.remove('almost', 'over');

    if (length > maxtext) {
        counter.classList.add('over');
    } else if (length > maxtext * 0.9) {
        counter.classList.add('almost');
    }
});

이 녀석... 독심술 쓰나... 나 코드만 올렸는데 뭐땜시 질문한건지 다 알고 있어... 심지어 저 변수는 내가 갖고 오기만 하면 undefined떠서 에이씨 그냥 생으로 써야겠다 했던건데 저걸 또 해결봐주네… 아무튼 그렇습니다.

 

const inputarea = document.querySelector('#inputarea');
const counter = document.querySelector('.count');
let maxtext = 200;
counter.innerText = maxtext;

inputarea.addEventListener('keyup',()=>{
    counter.innerText = maxtext - inputarea.value.length;

    if (inputarea.value.length > (maxtext * 0.9)) {
        counter.classList.add('almost');
    } else if (inputarea.value.length < (maxtext * 0.9) && counter.classList.contains('almost')) {
        counter.classList.remove('almost');
    } else if (inputarea.value.length < maxtext && counter.classList.contains('over') && inputarea.value.length > (maxtext * 0.9)) {
        counter.classList.remove('over');
    } else if (inputarea.value.length > maxtext) {
        counter.classList.add('over');
        alert('글자수 제한 초과입니다!')
    }
    ;
});

조건문 우선순위 너무 복잡함... 아무튼 수정 포인트를 봅시다. 위 코드가 내가 짰던 코드인데, 보면 if문이 개같이 많아요. 그죠? 그걸 남은 글자 수가 10% 미만일때(200자 제한 기준 19자 이하 남았을 때)랑 남은 글자 수가 음수일때(글자수 초과했을 때) 두 가지로 나눴다. 그리고 우선순위를 올렸다는 건 무슨 얘기냐... if문을 잘 보자.

 

inputarea.addEventListener('keyup',()=>{
    counter.innerText = maxtext - inputarea.value.length;

    if (inputarea.value.length > (maxtext * 0.9)) {
        counter.classList.add('almost');
    } else if (inputarea.value.length < (maxtext * 0.9) && counter.classList.contains('almost')) {
        counter.classList.remove('almost');
    } else if (inputarea.value.length < maxtext && counter.classList.contains('over') && inputarea.value.length > (maxtext * 0.9)) {
        counter.classList.remove('over');
    } else if (inputarea.value.length > maxtext) {
        counter.classList.add('over');
        alert('글자수 제한 초과입니다!')
    }
    ;
});

이게 내가 쓴 코드고

 

inputarea.addEventListener('keyup', () => {
    const length = inputarea.value.length;
    const remaining = maxtext - length;

    counter.innerText = remaining;

    counter.classList.remove('almost', 'over');

    if (length > maxtext) {
        counter.classList.add('over');
    } else if (length > maxtext * 0.9) {
        counter.classList.add('almost');
    }
});

이게 채찍피티가 쓴 코드다.

 

두 코드의 if문에서 over가 어디에 위치해있는지 확인해보자. 내가 쓴 코드는 over 클래스가 붙는 조건... 그러니까 글자 수가 제한을 넘겼을 때가 맨 밑에 있고, 채찍피티가 보완해 준 코드는 글자수가 제한을 넘겼을 때를 먼저 처리해준다. 이 위치 차이만으로도 적용되고 안되고가 갈린다. 정말 복잡시럽다.

 

남은 글자 수가 전체 글자 수의 10% 이상
남은 글자 수가 전체 글자 수의 10% 미만
글자수 오버

진짜 채찍피티 있어서 다행이다…

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

모바일 뷰포트 확인하는 법(feat. 개발자 도구)

Coding/JavaScript 2025. 1. 24. 03:01

https://koreanraichu.tistory.com/572

 

미디어쿼리에 대해 알아보자

미디어쿼리? 그게 뭐죠? 에 대해 설명하기 전에 한번 잘 생각해보자. 진짜 완전 찐으로 구식인 웹사이트... 그니까 막 쌍팔년도에 만들어져서 방치된 스멜이 폴폴 풍기면서 들어

koreanraichu.tistory.com

이 글에서 미디어쿼리로 모바일에서 UI 배치를 바꿔봤는데… 읽으면서 에이 뭐 뷰포트 뭐 창 줄여서 보지 이런 분들도 계셨겠지만 아니 저거 어케봄??? 이런 분들도 계셨을 거라 생각한다. 그거 어케 하는지 알려드림. 

 

얘까지 한 글에 쓰면 내용이 번잡시러워서 포스트 분리한겁니다.


다들 알다시피 브라우저에서 F12키를 누르면 개발자도구가 열린다.

Firefox의 개발자 도구

F12 눌렀는데 안열려요? 오페라 쓰신다고요? 오페라는 단축키가 따로 있으니 구글 검색해보십시오.

 

아무튼 개발자 도구를 열었으면 저기 네모 두 개 있는 걸 눌러보자.

이거 말하는거다. 브라우저에 따라 위치는 다르지만 아이콘은 공통적으로 저렇게 생겼다.

 

이건 알아서 바뀌는 게 아니라 F5를 한번 눌러야 바뀐다.

이런 화면이 뜬 걸 볼 수 있다. 크롬의 경우 처음 이걸 켜면 Responsive라고 뜨는데... 아니 그럼 모바일 뷰포트는 일일이 외워야 하는거예요? 아뇨!

 

디바이스 목록 누르면 갤럭시 아이폰 아이패드 다 있다.

 

파폭에서는 이런 기기들을 지원하는데... 태블릿에 왜 갤탭 없냐... 아무튼 여기서 기기들을 선택하면 해당 기기에 맞는 뷰포트 크기로 바뀐다. 이걸 이용해서 기기 바꿔가면서 볼 수 있다.

 

여기서 본인이 보고싶은 뷰포트 크기를 직접 입력할 수도 있다.

 

디바이스별로 뷰포트 확인을 할 때는 아이콘이 파란색이 되는데, 저 상태에서 한번 더 누르면 디바이스별로 보는게 꺼지고 개발자 도구만 남는다.

 

근데… PC는 다크모드 껐을텐데 파폭은 왜 다크모드가 된거지…?

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

방명록