✍️ 자기소개 페이지를 만들어보면서 메모한 내용들 + 문제해결 과정
선택자 띄어쓰기
.box .box1
: box 컨테이너안에 들어있는 자식 요소중에 box1을 꾸며줄 때
.box.box1
: box이면서 box1인 요소를 꾸며줄때 (즉 두개의 클래스를 다 가지고 있는 교집합)
포지션 적용이 안되는 이유 / sticky & fixed
❗️주의할 점
- width와 height 값을 주어도 변동이 안되는 것은 기본 static 속성 때문이다.
- sticky 속성은 포지션(top, left) 지정을 해주지 않으면 반영되지 않는다.
- sticky는 그자리에서 고정, fixed는 기존 위치에서 나와서 뷰포트 기준이다.
sticky와 fixed의 차이
- sticky : sticky를 선언한 영역의 부모 요소 안에서만 고정되어 스크롤에 따라 움직이게 된다. (부모요소 기준)
- fixed : 페이지 전체 영역을 기준으로 고정된다.
margin: auto
시 가운데로 정렬되는 것이다. (이때의 margin은 수평만 따지므로 수평정렬이다.)text-align:center
left: 50%;
transform: translateX(-50%);
다른 방법도 있겠지만 플렉스박스에 포함되지 않는 간단한 요소에는 이 방법을 썼다. translate는 자기 자신의 퍼센트를 계산하므로 부모의 50%로 이동시켜줘야 한다.
예제 코드 ▼
.item {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
top: 50%
, left: 50%
로 요소를 이동시키면, 좌측상단 모서리가 부모 요소의 중앙으로 이동한다. 그래서 요소의 중심은 부모의 중심과 일치하지 않게되기 때문이다. [참고한 글]display
속성이 block
, inline-block
으로 지정된 박스 블록 요소에는 line-height
속성을 적용해 박스 안에 있는 인라인 요소, 또는 인라인 태그에 행 높이를 적용할 수 있다.
- 요소를 배치할 때 부모 요소의 높이만큼 행 높이를 line-height
값으로 지정하면 인라인 속성인 요소가 수직으로 가운데 정렬이 된다.
❗️한줄 텍스트에만 유효하다. 이 뜻은, 인라인 요소가 2줄 이상으로 표시되는 경우, 2번째 줄 이후의 내용이 부모 요소의 바깥에 표시되는 문제생긴다. 그러므로 짧은 인라인 요소를 수직 중앙정렬할 때 유의해서 사용하자.
#navbar ul li {
padding: 20px;
border: 1px solid var(--color_white);
border-radius: 50%;
width: 16px;
height: 16px;
margin-top: 20px;
text-align: center;
line-height: 16px;
color: var(--color_white);
transition: all 300ms ease-in-out;
font-weight: bold;
}
li
태그 세로로 나열하기 (오류해결)#promise .profile_text {
display: flex;
flex-direction: column;
}
🤔 이렇게 주어도 column이 적용되지 않았다. 즉 세로로 정렬되지 않았다. 부모에게 준 속성을 뜯어보고 정확히 원하는 요소에 속성을 맞게 주었는지 확인해 보았지만 원하는 모양이 나오지 않았다.
css 파일을 만들고 초반에 정의해 둔 all:unset
을 지웠다.
→ 해결 ! 세로로 정렬되었다.
❗️all: unset
유의할 점
- 리스트의 중점 ·
은 사라지지만 리스트 목록들을 세로로 정렬하는 순수 리스트의 기능 마저 사라진다. 때문에 가로로 나열되어 원하지 않는 결과를 볼 수 있으니 조심하자.
→ 이럴 땐 ul {list-style: none;}
으로 하기
혹은 all:unset
을 그대로 주면서 원하는 배치를 할 수 있을지 시도해 보았다.
#promise ul {
display: flex;
flex-direction: column;
}
이렇게 선택자명을 ul로 두었더니 지우지 않고도 원하던 대로 수직정렬 되었다. 😀
- 가상 요소는 요소의 특정 부분에 스타일을 적용하기 위하여 사용된다.
::before
콘텐츠의 뒤에 위치하는 공간을 선택한다. (지정한 요소 뒤에 가상 콘텐츠를 삽입한다.)
::after
콘텐츠의 앞에 위치하는 공간을 선택한다. (지정한 요소 앞에 가상 콘텐츠를 삽입한다.)
::first-letter
콘텐츠의 첫 글자를 선택한다.
::first-line
콘텐츠의 첫 줄을 선택한다.
::selection
드래그한 콘텐츠를 선택한다.
.home_section::before {
display: block;
position: relative;
content: "";
width: 150px;
height: 2px;
top: 142px;
left: 70px;
transform: translateX(-50%);
margin-top: 8px;
background-color: var(--color_lightGray);
}
content="";
은 가짜 속성이다. html 문서에 정보로 포함되지 않은 요소를 CSS에서 새롭게 생성시켜 준다.✍️ 디자인을 제어할 때 효율적으로 스타일을 주기에 좋은 방법이지만, 접근성 이슈들 또한 고려해야할 것 같다. (특정 스크린리더나 브라우저(IE)에서는 읽히지 않는 등) [참고한 글]
.like_this_texts {
position: absolute;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
left: 50%;
transform: translateX(-50%);
color: var(--color_white1);
background-color: rgb(172, 139, 139);
width: 100%;
height: 100%;
transition: all 300ms ease-in-out;
}
.like_this_texts:hover {
background-color: rgba(0, 0, 0, 0.1);
opacity: 0;
}
처음엔 .like_this_texts:hover
에서 display:none
으로 설정 했는데 글자가 끊기듯이 사라졌다.
✍️ 애니메이션 효과까지 적용하기를 원한다면 display:none
처럼 보이지 않도록 하는 속성대신, 투명도 속성 opacity
로 설정하자. 이렇게 수정해서 원하는 애니메이션 효과를 얻을 수 있었다.
처음에 이미지의 사이즈 설정을 background-size: cover
로 했더니 이미지 내부의 가로세로 비율이 맞지 않았다.
구글링을 통해서 object-fit: cover
이라는 속성을 알게 되었고, 적용 후 비율이 깨지지 않게 되었다.
object-fit
[MDN_object-fit]
<img>
나 <video>
같은 오브젝트의 사이즈를 컨테이너의 크기에 맞춰 조절할 수 있다.
❗️ IE에서는 지원되지 않는다.
object-fit:cover
를 쓰면 가로세로 비율은 유지하면서 컨테이너에 꽉 차도록 설정된다. 너비와 높이값을 입력하고, object-fit
속성을 지정한다.
object-fit의 속성
object-fit: fill
- 이미지가 강제로 늘어나거나 줄어들면서 지정한 넓이, 높이에 꽉 찬다. 원본 이미지의 비율을 유지하지 않는다.
object-fit: contain
- 원본 이미지의 비율을 유지하면서 지정한 넓이, 높이안에서 최대한 확대시킨다. 잘리는 부분이 발생하지 않는다.
object-fit: cover
- 원본 이미지의 비율을 유지하면서 지정한 넓이, 높이에 꽉 찬다. 잘리는 부분이 발생한다.
object-fit: none
- 지정한 넓이, 높이에 상관없이 원본 이미지의 크기를 유지한다. 원본 이미지 가운데 부분이 보여지게 된다.
object-fit: scale-down
- 지정한 넓이, 높이값보다 원본 이미지가 작으면 그대로 두고, 크면 원본이미지 사이즈를 줄여서 보여준다.
추가)
const titleText = document.querySelector("h1.title");
let i = 0;
let txt = "Hello, I'm seulbi !";
let speed = 90;
function typeWriter() {
if (i < txt.length) {
titleText.innerHTML += txt.charAt(i);
i++;
setTimeout(typeWriter, speed);
}
}
typeWriter();
변수 i
와 txt
를 선언하고, if문 안에서 글자를 만들어 낸다음 setTimeout
을 통해서 동작 효과를 구현한다.
String.prototype.charAt()
: charAt() 함수는 문자열에서 특정 인덱스에 위치하는 유니코드 단일문자를 반환한다.
ex.)
const anyString = 'Brave new world';
console.log(`The character at index 1 is '${anyString.charAt(1)}'`);
// "The character at index 1 is 'r'"
// css(scss)
@keyframes appear {
from {
top: 50px;
opacity: 0;
}
to {
top: 105px;
opacity: 1;
}
}
@keyframes disappear {
from {
top: 105px;
opacity: 1;
}
to {
top: 50px;
opacity: 0;
}
}
// js
const promiseContainer = document.querySelector(".promise_container");
window.addEventListener("scroll", () => {
let value = window.scrollY;
if (value > 1700) {
promiseContainer.style.animation = "appear 1s ease-out forwards";
} else {
promiseContainer.style.animation = "disappear 1s ease-out";
}
});
const nav = document.querySelector(".navbar_container");
const liH = document.querySelector(".navMenu:nth-child(1)");
const li1 = document.querySelector(".navMenu:nth-child(2)");
const li2 = document.querySelector(".navMenu:nth-child(3)");
const li3 = document.querySelector(".navMenu:nth-child(4)");
const home = document.querySelector("#home");
const profile = document.querySelector("#profile");
const likes = document.querySelector("#likes");
const promise = document.querySelector("#promise");
liH.addEventListener("click", () => {
home.scrollIntoView({ behavior: "smooth", block: "start" });
});
li1.addEventListener("click", () => {
profile.scrollIntoView({ behavior: "smooth", block: "start" });
});
li2.addEventListener("click", () => {
likes.scrollIntoView({ behavior: "smooth", block: "start" });
});
li3.addEventListener("click", () => {
promise.scrollIntoView({ behavior: "smooth", block: "start" });
});
✍️
<li>
태그를 다 가져와서 하나씩 이벤트리스너를 다 등록하는 것은 좋지 않다.<ul>
태그를 한번만 가져와서 리스너를 등록하는게 좋음!
- ❗️ 만약
ul
을가져올 때querySelectorAll
로 가져오면 배열을 리턴하기 때문에 배열에다가는 addEventListener()를 추가 할 수 없으니 에러가 난다. 유의!- 이벤트 버블링 : 부모 컨테이너는 어떤 자식 요소에서 이벤트가 발생하는 모든 이벤트를 다 들을 수 있다.
const nav = document.querySelector(".navbar_container");
nav.addEventListener("click", (e) => {
const target = e.target;
const li = target.dataset.li;
scrollF(li);
});
function scrollF(selector) {
const scrollSec = document.querySelector(selector);
const scrollY = scrollSec.offsetTop;
window.scrollTo({ top: scrollY, behavior: "smooth" });
}
querySelectorAll
이 아닌, querySelector
로 가져온다.📌 추가적으로 nav
컨테이너 안에서 li
가 아닌 여백을 클릭해도 이벤트 콜백함수가 계속 실행된다.
버튼이 클릭 되었을 때만 이벤트 처리를 하도록 코드를 추가하자.▼
if (target.tagName !== "LI") return;
이제 버튼을 클릭할 때만 동작된다! 😀
position:absolute
과 위치속성을 이용했지만, 이번에는 transform: translateY
속성으로 이동시켰다. @keyframes up {
from {
transform: translateY(55px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
@keyframes down {
from {
transform: translateY(0);
opacity: 1;
}
to {
transform: translateY(55px);
opacity: 0;
}
}
❗️ 문제점)
ul
컨테이너를 가져오듯 querySelector
로 가져올 수 없는 상황이다. 따라서 querySelectorAll
로 해당 div 태그를 전부 한번에 가져오면, 유사배열객체 NodeList 를 반환한다.✍️ 해결과정)
❓ 작성한 코드를 보다보니 scroll이 될 때마다 window.scrollY를 호출해서 그 값을 value 변수에 담아 함수 내부에서 사용되고 있는데 이 코드를 전역 변수로 선언하면 scroll 이벤트 발생시마다 위치값을 알 수가 없을테고, 어떻게 리팩토링하면 좋을까? 🤔
const likesTitleContainer = document.querySelectorAll(".like_title div");
const likesTitleArray = Array.from(likesTitleContainer);
window.addEventListener("scroll", () => {
let value = window.scrollY;
likesTitleArray.map((i) => {
if (value > 1100) {
i.style.animation = "up .6s ease-out forwards";
} else {
i.style.animation = "down 1s ease-out ";
}
});
});
const navLis = document.querySelectorAll(".navbar_container .navMenu");
const navLisArray = Array.from(navLis);
window.addEventListener("scroll", () => {
let value = window.scrollY;
navLisArray.map((li) => {
if (value > 200) {
li.classList.add("dark");
} else {
li.classList.remove("dark");
}
});
});
Array.from
와 map()
함수를 이용했다. 😀✍️ 추가로 css를 scss로 리팩토링도 해보았다. :root 대신 $ 변수로 바꾸고 정리해보았는데 전체적으로 잘 작동되었다.