HTML CSS 8일 완성하기 : 8일 차 미디어쿼리, 그밖의 효과들
대망의 마지막 날!
잡다한 소리는 마지막 포스팅에서 하기로.
마지막 두 섹션은
1. 반응형 웹페이지 - 데스크톱 버전으로 만들었던 것을
더 작은 화면 (작은 데스크탑 화면, 태블릿 가로-세로, 핸드폰 화면) 까지 스타일이 깨지지 않고 적용될 수 있도록 하는 과정
2. 그밖의 작지만 적용하면 멋져지는 몇 가지 효과 를 추가
3. 실제로 라이트하우스 기능을 사용해 내가 만든 웹페이지가 배포하기에도 적당한지 알아보는 법
4. 이미지 용량 압축
5. 실제로 (무료로) 배포해보는 법
... 등 알차게 구성되어 있었다.
여기서 기록해둘 것은 2번까지로 나머지는 기록해 두기 애매해서?? 넘어가기로 했다.
각각의 사이트에 들어가서 하라는대로(ㅋㅋ) 하면 되기 때문에 적어두지 않아도 될 것 같다 (괜찮겠지?)
그럼 시작!
미디어쿼리 ?
미디어쿼리는 breakpoint(이하 중단 지점)를 설정하고, 중단 지점에 맞춰 스타일을 변경-적용할 수 있게 해주는 기능이다.
즉 특정 뷰포트 너비에 CSS의 특정 부분을 재정의한다는 의미.
중단 지점은 어디로 해야 할까?
🤔 예전에는 (아마도 미국 기준) 아이폰, 아이패드, 맥북 등 애플 기기를 많이 사용하므로 이 기기들의 크기에 맞췄다고 한다.
하지만 이 방법은 굉장히 위험하다. 왜냐하면, 다른 기기를 사용하는 사용자들에게 안 좋은 경험을 선사할 수 있고 계속해서 나오는 신 기기들에 맞춰서 대대적으로 뒤집어엎어야 할 수도(?) 있기 때문이다.
✨기본적으로 디자인이 깨지는 지점을 중단 지점으로 설정한다.
오로지 디자인만을 확인하면서 틀어지는 곳을 중단 지점으로 설정하는 것이다. 그리고 이에 맞춰 스타일을 적용하면 되는데, 중요한 점은 각각의 중단 지점 사이의 거리는 약 200px 정도로 스타일을 변경할 때 200px 정도를 커버할 수 있도록 설정해야 한다는 점이다.
📌 각 기기의 대략적인 화면 크기
핸드폰 3-500px
태블릿 (세로) 6-900px
태블릿 (가로) 9-1100px
컴퓨터 1200px ~
너비 설정 시 주의사항
✨ px 대신 rem / em을 사용한다.
저번 반응형 웹페이지 섹션에서도 말했던 것처럼, px은 크기를 고정시켜 버린다. 반응형 웹페이지에서는 rem, em을 생활화하자!
주의해야 할 점은 미디어쿼리 안에서는 이미 설정해 두었던 html 요소의 (전역) font-size: 62.5%; 가 적용되지 않는다는 점이다.
즉, 10px을 퍼센트로 바꿔서 설정해 두었는데 다시 기본설정인 16px으로 돌아와 있다는 점! 절대 잊지 말자.
(원래 css에서 설정해 두었던 1 rem = 1em = 10px / 미디어쿼리 1rem = 1em = 10px )
max-width 값 계산하기
예를 들어서 중단지점을 1350px로 정했다고 하자.
1em은 미디어쿼리 상태에서는 16px (브라우저 기본설정에서 바꾸지 않았다는 전제 하).
1350 / 16 = 84.nnnn... => 84em으로 설정.
@media (max-width: 84em) {}
부담스러워진 font-size 한 번에 바꾸기
먼저 정해둔 10px에 맞춘 폰트사이즈 62.5%는 화면이 작아질수록 왠지 부담스러워진다.
각각 요소마다 font-size를 맞춰두었으니 그 각각의 요소 개별적으로 바꿔줘야 할까? 당연히 아니다!
62.5%라는 폰트 사이즈를 더 작게 바꿔주면 당연히 그 속성을 기준으로 정해두었던 모든 요소의 폰트 사이즈도 자동적으로 작아진다. 이게 바로 반응형, rem의 힘이다!
다시 한번 정리하자면 지금까지 유동적인 웹사이트(px이 아니라 rem을 사용한 웹사이트)를 만들어왔다.
rem 값은 html(root요소)의 폰트사이즈 설정을 기준으로 정해진다.
즉, html 폰트사이즈를 변경하면 rem으로 설정된 모든 크기가 그 기준에 맞춰 변한다는 것이다. 정말 멋지다!
html {
font-size: 56.25%;
/* 9px / 16px = 0.5625 */
}
숨겨지는 메뉴(nav) 만들기
뷰포트 너비가 좁아지면 (대개 핸드폰 화면) nav의 메뉴들이 사라지고 햄버거 버튼만 보이게 된다.
햄버거 버튼을 누르면 숨겨져 있던 메뉴들이 튀어나온다!
먼저 css 효과를 주고, js 를 이용해서 완성한다.
css
1. 미디어쿼리로 핸드폰 화면정도의 뷰포트 너비를 설정한다.
2. 너비가 좁아졌을 때 어떻게 보일지 설정한다.
일단 눈에 보이지 않는 상태로 시작해야 한다 !
3. x축 이동 설정을 이용해서 화면에서 보이지 않도록 숨긴다 (이때, overflow를 사용해서 숨겨진 nav를 확인할 수 없도록 만들어야 한다)
4. nav를 보이게 할 때의 클래스명을 추가한다.
5. 클래스명을 추가했을 때 X축 이동 속성을 다시 한번 사용해 보이는 곳으로 이동한다.
코드
.main-nav {
background-color: rgba(255, 255, 255, 0.4);
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100vh;
transform: translateX(100%);
display: flex;
align-items: center;
justify-content: center;
transition: all 0.5s ease-in;
opacity: 0;
pointer-events: none;
visibility: hidden;
}
.nav-open .main-nav {
opacity: 1;
transform: translateX(0);
pointer-events: auto;
visibility: visible;
}
🤔 왜 opacity 속성을 이용한 걸까?
🧑💻 display는 애니메이션 효과를 적용할 수 없기 때문이다.
보통 보이지 않게 만들 때는 display: none; 을 사용하는 게 일반적이다. (나도 언제나 이 방법만 사용했었다...) 하지만 말 그대로 display 속성에는 애니메이션 효과를 줄 수 없고, opacity는 가능하다.
☝🏻여기서 한 가지 주의해야 할 점이 있다.
display: none을 적용하면 아무런 반응도 일으키지 않는다. 눈에도 보이지 않고, 정말 아예 "없는" 처리가 되어 마우스/키보드 이벤트도 일어나지 않고 스크린리더도 읽지 않는다.
하지만 opacity는 보이지"만" 않을 뿐 마우스/키보드 등 이벤트를 (내가 원하지 않아도) 일으킬 수도 있고 스크린리더도 이를 읽을 수 있다. 때문에 이걸 방지하는 처리를 꼭 해주어야 한다.
그게 바로 코드에서도 보이는
pointer-events: none; 👉 이벤트 대상에서 제외시킨다.
visibility: hidden; 👉 스크린리더가 읽지 못하게 한다.
이다.
JS
js 코드는 간단하다.
햄버거 버튼을 눌렀을 때 nav를 보이게 하는 클래스명이 없다면 추가하고 반대의 경우에는 삭제하는 기능을 만든다.
classList의 toggle 함수를 사용했다.
const btnNavEl = document.querySelector(".icon-mobile-nav");
const headerEl = document.querySelector(".header");
btnNavEl.addEventListener("click", function () {
headerEl.classList.toggle("nav-open");
});
부드럽게 이동하는 스크롤 액션
이동하고자 하는 요소에 id를 부여하고 앵커(a)의 href 속성에 #아이디를 적용해 보자.
그 앵커 요소를 누르면 id 이름에 맞는 요소로 이동한다.
하지만 눈 한 번 깜빡하는 것처럼 이동해 버린다.
이러한 스크롤 액션을 부드럽게 바꿔줄 수 있는데 scroll-behavior:smooth속성을 html 요소에 적용시키면 된다!
지금은 모든 브라우저가 적용되는 속성이지만 이 강의가 만들어질 때는 사파리 등에서 적용되지 않았다고 한다.
현재에는 속성 한 줄 쓰면 되는 일이지만 그때는 js로 만져줬어야 했다고...
어딘가에 응용해서 사용할 수 있지 않을까 해서 기록해 둔다.
///////////////////////////////////////////////////////////
// Smooth scrolling animation
const allLinks = document.querySelectorAll("a:link");
allLinks.forEach(function (link) {
link.addEventListener("click", function (e) {
e.preventDefault();
const href = link.getAttribute("href");
// scroll back top
if (href === "#")
window.scrollTo({
top: 0,
behavior: "smooth",
});
// scroll to other links
if (href !== "#" && href.startsWith("#")) {
const sectionEl = document.querySelector(href);
sectionEl.scrollIntoView({ behavior: "smooth" });
}
// close mobile navigaition
if (link.classList.contains("main-nav-link")) {
headerEl.classList.toggle("nav-open");
}
});
});
이해가 어려운 코드는 아니다.
앵커 요소를 클릭했을 때 href 속성을 받아와 변수에 담고, 이 변수를 if 문을 통해 각 상황별로 대처하도록 되어있다.
여기서 몰랐던 건 window.scrollTo({object}), scrollIntoView({object})!!
scrollTo()는 문서의 지정된 위치로 이동하는 것으로 x좌표, y좌표를 사용하기도 한다. MDN
scrollIntoView()는 scrollIntoView()가 호출된 요소가 표시되도록 상위 컨테이너로 스크롤한다. MDN
둘의 차이라고 한다면, 전자는 좌표만 있어도 이동이 가능하다는 것 후자는 이동하기 위한 요소를 알고 그 요소에 호출되어야 한다는 것!
Sticky navigation
웹사이트를 보다가 스크롤로 아래로 일정 부분 내렸을 때 메뉴가 사라지지 않고 위에 딱 붙어있는 걸 본 적이 있을 것이다.
바로 이게 sticky navigation이다.
1. 먼저 sticky 요소로 사용하기 위해 header에 고정 크기를 설정했다.
.header {
/* 나중에 sticky를 사용하기 위해서 고정 높이를 설정*/
height: 9.6rem;
padding: 0 4.8rem;
position: relative; /* sticky 후 fixed position의 기준*/
}
2. sticky 클래스가 추가되면 (추가되는 기능은 js로 구현한다) 가장 위에 붙어서 사라지지 않고 고정된다.
/* STICKY NAVIGAITION */
.sticky .header {
position: fixed;
top: 0;
bottom: 0;
width: 100%;
padding-top: 0;
padding-bottom: 0;
background-color: rgba(255, 255, 255, 0.8);
z-index: 999;
box-shadow: 0 1.2rem 3.2rem rgba(0, 0, 0, 0.03);
/*공간을 벗어나버리기 때문에 위에 붕 뜨고 다른 요소들이 그 공간을 채운다*/
}
3. 이때, fixed로 설정되면 absolute처럼 공간 위에 붕 떠버리고, 비어버린 공간을 다른 요소가 채우는데 이를 방지하기 위해서 sticky가 설정되었을 때 hero section은 margin-top을 header의 크기만큼 가지도록 설정했다.
.sticky .section-hero {
margin-top: 9.6rem;
}
4. js를 이용해 구현한다.
///////////////////////////////////////////////////////////
// Sticky navigation
const sectionHeroEl = document.querySelector(".section-hero");
const obs = new IntersectionObserver(
function (entries) {
const ent = entries[0];
console.log(ent);
if (ent.isIntersecting === false) {
document.body.classList.add("sticky");
}
if (ent.isIntersecting) {
document.body.classList.remove("sticky");
}
},
{
// 이 요소가 나타나야 하는 곳(root)
root: null,
// 뷰 포트 안에서 sectionHeroEl을 감시하겟다
// null 은 뷰포트를 의미한다.
threshold: 0,
// 감시하는 섹션의 0%부분-가장시작인부분이 뷰 포트안에 있으면 이벤트 등을 갖게 한다
// 추가로 1일 경우에는 뷰포트에 모두 보여야 이벤트 등 갖게함
rootMargin: "-80px",
//시작점을 조정
}
);
obs.observe(sectionHeroEl);
js observer에 관해 잘 이해가 되지 않았다는 점이 아쉽다.
css 부분은 문제없이 이해한 것 같은데... 후에 굉장히 유용해 보이니까 관련 공부를 꼭 해야겠다.
하여튼 이렇게 멋진 sticky navigation이 완성되었다!
🍔 8일 차 (마지막날!!) 후기
강의자가 마지막에 했던 이야기처럼 이걸 보는 것만으로는 개발자가 될 수 없다는 점을 명심하자.
내가 만든 스케줄에 맞춰 목표 달성을 한 건 대단한 일이지만 이걸로 완성되었다고 생각하지 말자.
직접 만들어보고 시행착오를 몇 번이라도 겪어야만 개발자라고 불릴 수 있는! 그 지점에 오를 수 있다는 걸 잊지 말자.
그렇구나 하고 넘어가지 말고 실제로 뭐라도 만들어보자. 🤸🏻