▶ 목차
🪐 1. id & password 타이핑 시 기능들
🪐 2. id, password Validation
🪐 3. 검색창 선택 시 아이콘 & 글자 이동
🪐 4. 댓글 추가, 좋아요, 삭제 (🌟로그인 시 입력한 id로 댓글 달기!🌟)
🪐 5. 게시물 좋아요 (클릭 시 애니메이션)
🪐 6. id 검색 기능
🪐 7. 인스타 스토리 바
🪐 8. 스크린 이미지 전환
우선 CSS에 keyframes로 표현하고 싶은 애니메이션을 설정했다. 인스타그램 좋아요는 누르면 빈 하트가 커지면서 빨간하트로 바뀌고 다시 작아지는... 그런 효과니까 일단 커지게 하고 작아지게 하는 pop&shrink(내맘대로 지음) 애니메이션을 아래처럼 만들었다.
@keyframes pop { 0% {transform: scale(1);} 100% {transform: scale(1.2);} } @keyframes shrink { 0% {transform: scale(1.2);} 100% {transform: scale(1);} }
그 다음에 각각의 하트에 저 키프레임들로 애니메이션 속성을 줘야 하는데... 속성값들이랑 순서를 까먹어서 테이블로 다시 정리!
animation: | name | time | func | delay | iteration | dir | fill | play |
---|---|---|---|---|---|---|---|---|
디폴트 | - | 0s | ease | 0s | 1 | normal | none | running |
저렇게 애니메이션을 생성하고 또 고민에 빠졌다,,, hover로 커서 올릴 때 효과를 주는 건 알겠는데, 클릭했을 때 바꾸려면 어떻게 해야 되지...? 하다가 클래스에 애니메이션 스타일 속성을 주고, 자바스크립트로 클릭할 때 클래스네임을 추가하면 되지 않을까? 근데 그러면 클릭할 때 애니메이션이 정상적으로 시작되나? (궁금증 폭발🤔) 머.. 잘 모르겠지만 일단 시도해봄~~~
.tab img:first-child.pop { animation: pop 0.2s linear 0s 1 alternate; }
&
function addLike() { likeBtn.classList.toggle("pop"); } likeBtn.addEventListener("click", addLike);
그랬더니 클릭할 때 애니메이션이 정상적으로 실행됐다! 🥳🥳🥳
근데 이제 그걸 빨간 하트로 바꿔야 하는데,, 저 addLike 함수 안에 빨간 하트 요소를 선택해 redHeart.style.display = "inline"으로 바꿨더니(원래 display: none) 클릭하자마자 빨간하트가 나오면서 빈 하트 애니메이션이 묻혀서 안 보였다 🙁
그래서 setTimeout을 써서 애니메이션 재생시간(0.2s = 200ms) 만큼 지난 후 inline으로 바뀌도록 만들었다. 그리고 좋아요 숫자를 표시하는 텍스트도 바뀌도록 하는 코드도 추가했다. 그래서 최종적인 코드!function addLike() { likeBtn.classList.toggle("pop"); setTimeout(() => {redHeart.style.display = "inline"}, 200); likes.innerHTML = "좋아요 20개"; }
오브젝트를 실제로 써 보니까 얼마나 유용한지 알게 되었다! 저 많은 사람들의 엘리먼트를 html로 하나하나 만드는 건 너무 비효율적이기 때문에... 배열을 만들고, 그걸 forEach나 filter, map으로 돌려서 원하는 코드를 딱 한 번만 짜면 모든 계정에 적용할 수 있는 것!
근데 계정 정보가 id 스트링 하나만 있는 게 아니라 프로필 사진도 있고, 설명도 있어서 아래처럼 오브젝트를 담은 배열을 만들었다.const USERS = [{ id: 'dlwlrma', profileImg: 'img/dlwlrma.jpg', description: '이지금 IU' }, { id: 'songkang_b', profileImg: 'img/songkang_b.jpg', description: '송강' }, { id: 'cafeknotted', profileImg: 'img/cafeknotted.jpg', description: '노티드 Cafe Knotted' }, . . . ];
인스타에서 연예인들의 실제 계정과 각종 베이커리카페 계정을 따왔다😗 처음엔 검색 기능 구현하려고 만든 거였는데, 나중에 스토리랑 추천친구에 뜨는 요소들 만들 때도 매우 유용했음!
이제 저 오브젝트들 id와 사용자가 입력한 값을 비교해서! 앞부분이 일치하는 오브젝트들만 따로 배열을 만들기 위해 filter와 startsWith 메서드를 이용했다. 원래는 빈 배열을 선언해 놓고 이것도 forEach문을 돌면서 조건에 맞는 오브젝트들을 빈 배열에 추가하게 했는데, 리팩토링 과정에서 멘토님의 코멘트를 보고 filter 함수로 수정했다.
const matchedUsers = USERS.filter(users => users.id.startsWith(typing.value));
그리고나서 matchedUsers라는 배열을 이용해 각 검색리스트 안에 들어갈 엘리먼트들을 생성하고, 검색리스트에 append했다. forEach와 템플릿 리터럴(``)을 사용했다.
matchedUsers.forEach(i => { const matchedId = document.createElement('div'); matchedId.innerHTML = `<div class="searchedUser"> <img alt="user's profile image" src=${i.profileImg}> <div class="userId"> <p class="id">${i.id}</p> <p class="gray twelve" id="description">${i.description}</p> </div> </div>` searchList.appendChild(matchedId); })
그리고 매치되는 오브젝트가 없을 경우 '검색결과가 없습니다'라는 메세지를 추가하도록 조건문을 추가해서 아래처럼 showMatchedId라는 함수를 완성했다! 함수 짜놓고 확인해 볼 때 잘 작동하면 넘 뿌듯해>.<
function showMatchedId() { searchList.innerHTML=""; const matchedUsers = USERS.filter(users => users.id.startsWith(typing.value)); if(matchedUsers.length === 0) { searchList.innerHTML='<p>검색결과가 없습니다.</p>'; } matchedUsers.forEach(i => { const matchedId = document.createElement('div'); matchedId.innerHTML = `<div class="searchedUser"> <img alt="user's profile image" src=${i.profileImg}> <div class="userId"> <p class="id">${i.id}</p> <p class="gray twelve" id="description">${i.description}</p> </div> </div>` searchList.appendChild(matchedId); }) }
뭔가 예뻐 보여서 빨리 만들고 싶었던(ㅎㅎ) 스토리 목록~~~
위에서 만든 USERS를 그냥 쓸 수 도 있었지만, 다양한 계정들을 보여주고 싶어서 USERS의 순서를 뒤집어서 적용하고 싶었다.
처음엔 그냥 USERS.reverse()를 해서 forEach를 돌렸더니 USERS의 원본 배열까지 수정되어서 검색창에도 뒤집힌 순서대로 계정들이 나와버렸다🤨
그래서 아래처럼 새 변수에 USERS를 담아서 그 새 변수에 revers()를 하면 되지 않을까?! 했는데const usersForStory = USERS; usersForStory.reverse();
똑같이 USERS까지 reverse가 적용됐다. usersToSearch가 단순히 USERS를 복사하는 게 아니라 아예 USERS를 참조하는 걸루 되나 보다....
방법이 없을까 하고 찾아보다가 옛날에 얼핏 본 전개연산자('...')로 원본 배열 수정 없이! 배열을 뒤집는 방법을 알아냈다.const usersForStory = [...USERS].reverse();
그래서 검색리스트 요소 순서랑은 반대로 스토리 요소들을 찍어낼 수 있었다!
추가적으로 저 스토리 요소가 hover 상태일 때 스토리링에 transform:rotate와 transition값을 줘서 커서를 올리면 링이 빙글빙글 돌도록 만들었다.
.story:hover #storyring { transform: rotate(3turn); transition: 4s; }
근데 가로로 정렬을 하다 보니까 id가 짧으면 괜찮은데, 긴 경우엔 사진처럼 양옆 요소들이랑 겹쳐서 보기가 싫었다. 그래서 id가 10자 이상인 경우엔 8자만 보여주고 말줄임표로 처리하도록 아래 코드를 추가했다.const ids = document.querySelectorAll(".story p"); ids.forEach(id => { if(id.innerHTML.length > 10) { const longId = id.innerHTML; id.innerHTML = longId.slice(0, 8)+'...'; } })
근데 여기서 의문점! innerHTML을 innerTEXT로 바꾸면 오류가 생긴다. 콘솔창에 innerHTML이랑 innerTEXT를 찍어 보면 똑같이 오브젝트의 id값이 나오는데.... 멀까? 나중에 더 고민해 봐야겠당. (이러고 안할듯)
👉4.26 추가: innerText인데 innerTEXT라고 써서 그런 거였다ㅜ!!! 참나 완전 바보아님?
우선 overflow: hidden 스타일을 준
<div class="storyBox">
안에, 전체 스토리 리스트를 포함하는<div class="storySpan">
을 하나 만들었다(박스는 유지한 채 스토리들만 양 옆으로 움직여야 하니까). 그리고 storyBox 안, storySpan 밖에 (버튼은 움직이면 안 되니까!) 버튼도 하나씩 추가했다.<div class="storyBox"> <i class="fas fa-chevron-circle-left"></i> <i class="fas fa-chevron-circle-right"></i> <div class="storySpan"> </div> </div>
그리고 자바스크립트에서 storySpan을 선택해서 버튼을 누를 때마다 right를 조정해 주면 되겠지! 하고 let right를 선언해
storySpan.style.right
를 담았다. 근데 +- 연산을 하려면 스트링이 아닌 숫자 형태여야 하는데 style.right는 "0px", "100px" 이런 식으로 나오니까 이걸 숫자로 변환하기 위해 parseInt를 사용했다.const prevBtn = document.querySelector(".fa-chevron-circle-left"); const nextBtn = document.querySelector(".fa-chevron-circle-right"); let right = parseInt(storySpan.style.right); const F00 = 265; function next() { if(right < 795) { right += F00; storySpan.style.right = `${right}px`; } } function prev() { if(right > 0) { right -= F00; storySpan.style.right = `${right}px`; } } nextBtn.addEventListener("click", next); prevBtn.addEventListener("click", prev);
근데! 버튼을 눌러도 작동이 안됨ㅠ!!! 내 로직은 완벽한데~~~;;; 머지????😒 하고 console.log로 이것저것 찍어보다가 알게된 사실! css 파일에서 지정한 style값은 자바스크립트로 접근이 안 되고 undefined로 뜬다...
자바스크립트에서 style.color 등으로 들어가서 속성을 부여하는 건 가능한데! 그건 html 태그에 inline style로 지정하게 되는 것. 그래서 애초에 storySpan.style.right은 undefined인데 if(right < 795), if(right > 0) 등으로 undefined랑 숫자를 비교하려고 하니까 false가 되고 if문에서 통과가 안 된 거였다.
그래서 생각해낸 두 가지 해결 방법!
1. html에서 storyBox 태그에 인라인으로 style="right: 0px"을 삽입한다.<div class="storySpan" style="right: 0px;">
- javascript에 style.right가 없을 경우(버튼 한 번도 안 누른 초기 상태일 때) right를 0으로 할당하는 코드를 추가한다.
storySpan.style.right? right = parseInt(storySpan.style.right) : right = 0;
두 경우 다 실험해 봤는데 잘 작동했다! 그냥 기능을 만들었다는 사실보다 새로운 걸 알게 되어서 더 뿌듯✨✨
이건 사실 선경님이 javascript의 promise로 구현할 수 있다고 하셨는데 그게 뭔지 몰라서(...) 다음에 추가해 봐야지 했던 효과인데, 저 위에 CSS animation 개념 정리하다가 문득...! 이걸로도 되지 않을까? 싶어서 실험해 봤다. 근데 얼렁뚱땅 되길래 신남~~~
첨에 keyframes로 진행 퍼센트를 균일하게 쪼개고, 각각 다른 이미지를 넣어서 iteration을 infinite로 주면 되지 않을까! 생각했는데 img src는 스타일값이 아니라 html attribute니까 안 되겠구나 싶었다. 근데 또 문득 background-image는 스타일값이니까 되지 않을까?! 해서 도전해 봄.
우선 html에서 빈 div를 만들어 저 아이폰 화면이랑 딱 맞는 위치&크기로 맞춰 놓고, CSS에서 아래처럼 keyframes를 지정했다.
@keyframes imgchange { 0% {background-image: url("img/screen1.jpg");} 25% {background-image: url("img/screen2.jpg");} 50% {background-image: url("img/screen3.jpg");} 75% {background-image: url("img/screen4.jpg");} 100% {background-image: url("img/screen1.jpg");} }
그리고 저 스크린 CSS 값으로 애니메이션을 한 줄 더 추가했다.
animation: imgchange 6s linear infinite;
그랬더니 keyframes 자체가 %로 진행되는 거라서 부드럽게 넘어감! 아마 자바스크립트에서도 setTimeOut 같은 걸로 하면 이미지 src 자체를 바꾸면서 구현할 수 있을 것 같당.
클론코딩보다 벨로그 쓰는 게 더 힘든 것 같은 기분....ㅎㅎ 그래도 기록해 놓으니까 뿌듯하고 다시 한 번 개념들을 정리할 수 있어서 좋았다~~~🍀🍀
역시 도은님.. 정리까지 완전 잘하시네요 .... 잘보고갑니다~! 👍👍👍👍