[번역] 그것을 위해 자바스크립트를 사용할 필요는 없습니다

eunbinn·2024년 1월 11일
68

FrontEnd 번역

목록 보기
27/31
post-thumbnail

출처: https://www.htmhell.dev/adventcalendar/2023/2/

📌 원글에서는 실제 component를 글 안에서 직접 보여주나, 여기서는 gif로 표시하고 있습니다. 직접 확인해보고 싶으시다면 원글 링크를 참고해주시면 감사하겠습니다.

이 글의 제목에 반감을 갖지 말아주세요. 저는 자바스크립트를 싫어하지 않습니다. 좋아합니다. 매일 엄청난 양의 자바스크립트 코드를 작성하죠. 하지만 저는 CSS도 좋아합니다. 심지어 JSX HTML도 좋아합니다. 제가 이 세 기술을 모두 좋아하는 이유는 아래의 원칙 때문입니다.

가장 적은 힘의 원칙(Rule of Least Power)

이는 웹 개발의 핵심 원칙 중 하나로, 주어진 목적에 가장 적합한 가장 낮은 수준의 언어를 선택해야한다는 의미입니다.

웹에서는 CSS보다 HTML을, JS보다 CSS를 선호한다는 것을 의미합니다. JS는 브라우저가 어떻게 작동해야 하는지 설명하는 언어이기 때문에 세 가지 중 가장 다재다능하지만, 깨지거나 로드에 실패할 수 있고, 다운로드, 구문 분석 및 실행에 추가 리소스가 필요합니다. 또한 키보드 사용자와 보조 기술을 사용하는 사람들이 배제되기 매우 쉽습니다.

명령형인 JS와 달리 HTML과 CSS는 선언적입니다. 브라우저에 무엇을 해야하는지 알려주는 것이지 어떻게 해야 하는지를 알려주는 것이 아닙니다. 이는 즉, 브라우저에서 수행 방법을 선택하기 때문에 가장 효율적인 방법으로 수행할 수 있습니다.

HTML과 CSS 기능은 브라우저에서 처리하기 때문에 성능이 좋고 브라우저에 최적화 되어있으며 사용자 설정에 따라 변경이 쉽고 일반적으로 접근성이 더 좋습니다. 항상 그렇다는 의미는 아니지만(특히 접근성 측면에서) 일반적으로 브라우저가 무거운 작업을 대신 처리해주면 최종 사용자는 더 나은 경험을 할 수 있습니다.

하지만 그걸 위해선 JS가 필요합니다!

"내가 JS를 사용하는 것은 JS가 필요하기 떄문이야"라고 생각하실 수도 있습니다. 그럴 수도 있지만, 브라우저 제작팀과 사양 작성팀은 몇 년 전까지만 해도 JS가 필요했던 많은 기능들을 CSS와 HTML로 포팅하고 있습니다. 이 글은 바로 그에 대한 이야기 입니다.

웹의 까다로운 점은 한 번 구축하는 법을 배우면 다시 배울 필요가 없다는 것입니다. 웹은 이전 버전과 호환된다는 계약이 있기 때문입니다. (극소수의 예외가 있지만, 최초의 웹페이지도 모든 최신 브라우저에서 여전히 정상적으로 실행됩니다.)

이는 또한, 한 번 배운 솔루션은 저마다의 도구 상자의 일부로 축적되어 계속해서 재사용하여도 매번 잘 동작할 것임을 의미합니다. 따라서 아래에서 소개할 예시들은 멋지지만(그래서 소개하고 있습니다), 이 글에서 여러분이 얻어갔으면 하는 것은 무언가에 자바스크립트가 필요하다는 것을 안다고 해서 그것이 여전히 필요하다는 것을 의미하지는 않는다는 것입니다. 가끔씩 이런 가정을 테스트해본다면 더 나은 웹사이트를 만들 수 있을 것입니다.

커스텀 스위치

누구나 한 번쯤 구현해 본 적이 있을 커스텀 스위치부터 이야기 해보겠습니다. 일반적인 체크박스를 사용하는 대신 멋진 스위치를 사용하려고 합니다. div와 onclick 핸들러, 그리고 내부 상태를 사용하는 JS를 통한 해결방법이 아닌 일반적인 체크박스와 :checked 의사 클래스를 사용하겠습니다. 우리가 사용할 HTML은 아래와 같습니다.

<label>
  <input type="checkbox" />
  My awesome feature
</label>

label 요소 안에 체크박스가 있습니다. 이 방식의 좋은 점은 브라우저가 이미 작업을 수행하고 있다는 것입니다. input이 label 안에 있기 때문에 브라우저는 이 둘을 연결했고 onclick 핸들러 없이도 label의 아무 곳이나 클릭해서 체크박스를 켰다 끌 수 있습니다. 브라우저는 이 기능을 기본으로 제공합니다. 기능적인 측면에서는 이미 구현이 완료됐습니다.

물론 디자이너는 이 모양이 마음에 들지 않을 수 있고 더 멋진 커스텀 스위치를 만들고 싶을 수 있습니다. 그러니 CSS를 추가해 보겠습니다.

input {
  appearance: none;
  position: relative;
  display: inline-block;
  background: lightgrey;
  height: 1.65rem;
  width: 2.75rem;
  vertical-align: middle;
  border-radius: 2rem;
  box-shadow: 0px 1px 3px #0003 inset;
  transition: 0.25s linear background;
}
input::before {
  content: "";
  display: block;
  width: 1.25rem;
  height: 1.25rem;
  background: #fff;
  border-radius: 1.2rem;
  position: absolute;
  top: 0.2rem;
  left: 0.2rem;
  box-shadow: 0px 1px 3px #0003;
  transition: 0.25s linear transform;
  transform: translateX(0rem);
}

여기서 스타일링의 모든 세부 사항은 크게 중요하지 않지만 첫 번째 규칙인 appearance: none에 대해서는 한 번 살펴보겠습니다.

이미지와 함께 폼 요소는 "대체된 콘텐츠"라고 합니다. 실제로 HTML의 일부는 아니지만 브라우저에서 제공하는 것을 의미합니다. 브라우저는 HTML을 렌더링할 때 대체된 콘텐츠를 발견하면 해당 콘텐츠를 위한 상자를 만든 다음 그 상자를 실제 콘텐츠로 대체합니다. 그렇기 때문에 이미지와 폼 요소는 의사 요소를 가질 수 없습니다. 브라우저가 전체 요소를 대체할 때 같이 대체됩니다.

appearance는 브라우저에 해당 작업을 중지하도록 지시하는 방법입니다. "고맙지만, 폼 컨트롤의 스타일을 직접 지정하고 싶어" 라고 브라우저에 이야기 하는 것이죠. 그러면 ::before 의사 요소를 사용할 수 있습니다. 이제 input 자체가 스위치의 배경이 되고, ::before 의사 요소는 토글할 수 있는 작은 점이 됩니다.

스위치를 클릭하면 체크 박스가 선택되고 취소되지만 요소를 대체했기 때문에 눈으로 보이게끔 하는 작업을 따로 수행해야 합니다. 여기서 :checked 의사 클래스가 필요합니다.

:checked {
  background: green;
}
:checked::before {
  transform: translateX(1rem);
}

이제 체크박스를 클릭하면 :checked 의사 클래스에 매칭되어 스타일이 업데이트됩니다.

네이티브 HTML 요소와 약간의 CSS를 사용해서 멋진 커스텀 스위치를 만들었지만 아직 완성된 것은 아닙니다. 마우스 사용자의 경우 폼 컨트롤을 가리키고 클릭하기 때문에 어떤 폼 컨트롤과 상호작용하고 있는지 명확하게 알 수 있지만, 키보드를 사용하는 사용자에게는 쉽지 않습니다.

여러분들도 아래의 CSS에 익숙하실 겁니다. 보기 흉하거나 점선으로 되어있거나 박스형으로 되어있는 윤곽선을 없애는 CSS입니다.

input:focus {
  outline: none;
}

이 글을 읽고 계시다면 윤곽선을 없애는 것이 좋은 생각이 아니라는 것을 알고 계실 겁니다. 그럼 어떻게 하면 더 보기 좋게 만들 수 있을까요? 이와 관련해서도 브라우저의 업데이트가 있었습니다. 이제 outline 이 요소의 테두리 반경을 따르며 요소의 바깥쪽이나 안쪽에서 offset을 설정할 수도 있습니다.

input:focus-visible {
  outline: 2px solid dodgerblue;
  outline-offset: 2px;
}

이제 사용자가 키보드를 사용하여 요소와 상호 작용할 때 (요소를 클릭한 후 스페이스바를 누르거나 탭을 누를 때) :focus-visible 과 매칭되고(마우스를 사용할 떄는 매칭되지 않습니다) 요소 주위에 은은하게 파란색 윤곽선이 표시됩니다.

마지막으로 outline: none을 다른 것으로 대체하길 추천드립니다.

input:focus {
  outline-color: transparent;
}

이 경우에도 결과는 동일합니다. 윤곽선이 숨겨져서 보이지 않는 것이 아니라 투명해서 보이지 않는 것입니다. 그러나 고대비 모드(색 강제 모드(forced colors)라고도 합니다)를 켠 사용자의 경우 투명한 색상이 사용자가 선택한 색상으로 바뀌기 때문에 윤곽선이 다시 보이게 되어 마우스를 사용하더라도 상호작용하는 대상을 볼 수 있습니다.

이 글에서는 색 강제 모드에 대해 자세히 설명하기에는 충분하지 않으니 더 자세히 알고 싶으시다면 색 강제 모드에 대한 제 아티클을 한번 읽어보세요.

Datalist, 기본적인 자동 제안 기능

$프레임워크-자동-제안을 설치하는 대신 다음 프로젝트에서는 datalist를 사용해 보세요. Datalist는 사용자가 입력할 때 옵션 목록을 표시하는 브라우저에서 기본 제공하는 방식입니다.

<input list="frameworks" />

<datalist id="frameworks">
  <option>Bootstrap</option>
  <option>Tailwind CSS</option>
  <option>Foundation</option>
  <option>Bulma</option>
  <option>Skeleton</option>
</datalist>

이를 사용하려면 ID와 option 집합이 있는 datalist 요소를 HTML에 추가하면 됩니다. 요소는 표시되지 않으니 걱정하지 마세요. 그 후 input에 list 속성을 사용해 둘을 연결합니다.

이제 사용자가 입력하면 브라우저는 datalist를 드롭다운으로 표시하고 사용자가 입력하는 대로 옵션을 자동으로 필터링합니다. 하지만 일반적인 입력이기 때문에 사용자가 직접 값을 입력할 수 있는 옵션도 여전히 있습니다. 또한 사용자는 입력을 선택하고 화살표 키를 사용하여 목록을 탐색하거나 브라우저가 추가한 드롭다운 아이콘을 클릭해서 모든 옵션을 볼 수 있습니다.

더 많은 기능을 갖춘 색상 선택기

시중에는 멋진 캔버스 UI와 슬라이더를 갖춘 수백 줄의 자바스크립트로 구축된 멋진 색상 선택기가 수없이 많습니다. 하지만 기본적인 색상 선택기를 사용할 수도 있다는 사실을 알고 계셨나요?

<label> <input type="color" /> Color </label>

이 한 줄의 HTML로 멋진 UI를 갖춘 색상 선택기가 제공되기 때문에 많은 JS를 절약할 수 있습니다. 뿐만 아니라 브라우저에서 처리하도록 맡겼기 때문에 더욱 많은 기능을 무료로 사용할 수 있습니다. Chromium 브라우저에서는 기본 색상 선택기를 통해 내 사이트뿐만 아니라 화면의 어느 곳에서나 색상을 선택할 수 있습니다. 꽤 멋지죠.

여기서 한 가지 주의할 점은 브라우저에 멋진 색상 선택기가 표시되더라도, 이를 모든 사용자가 사용할 수 있는 것은 아니라는 점입니다. 따라서 일반적인 텍스트 입력과 같이 다른 색상 선택 방법을 제공하는 것도 좋은 생각입니다.

아코디언 메뉴

아코디언 메뉴는 내용이 많은 페이지를 사용자가 필요로 할 때까지 깔끔하게 정리하여 보다 체계적이고 정돈된 페이지로 만들 수 있는 좋은 방법입니다. 브라우저는 details와 summary 요소로 아코디언 메뉴를 무료로 제공합니다.

<details>
  <summary>My accordion</summary>
  <p>My accordion content</p>
</details>

기본적으로 details 요소 안의 모든 내용은 summary를 제외하고 숨겨집니다. 그 후 사용자가 summary 요소를 클릭하면 브라우저는 나머지 콘텐츠를 표시합니다.

아코디언들 중 하나는 이미 열려있고 나머지는 닫혀있는 경우도 종종 볼 수 있습니다. open 속성을 사용하여 이렇게 표시할 수 있습니다.

<details open>
  <summary>My accordion</summary>
  <p>My accordion content</p>
</details>

리액트에 익숙하시다면 이 코드를 보고 "open 프로퍼티가 있어 더 이상 닫히지 않겠네" 라고 생각하실 수 도 있습니다. 다행히 그렇지 않습니다. open 속성은 시작 상태일 뿐이며 사용자가 아코디언과 상호작용할 때 업데이트됩니다.

스타일링에 관해서는 details 요소도 도움이 됩니다. 디자이너가 보자마자 바꾸고 싶을 것 같은 작은 삼각형은 스타일을 지정할 수 있는 ::marker 의사 요소 입니다.

summary::marker {
  font-size: 1.5em;
  content: "📬";
}
[open] summary::marker {
  font-size: 1.5em;
  content: "📭";
}

콘텐츠를 변경하면 보조 기술이 아코디언을 알리는 방식에 영향을 줄 수 있다는 점에 유의하세요. 이에 대한 Manuels의 글인 details/summary 불일치를 읽어보세요. 또한 Safari의 경우 ::-webkit-details-marker 의사 요소를 사용해야 합니다.

marker 의사 요소는 다른 요소들 만큼 광범위하게 스타일을 지정할 수는 없지만(완전히 다른 위치에 배치하는 것과 같은 많은 CSS 속성이 작동하지 않습니다), 이모티콘으로 콘텐츠를 바꾸거나 배경색 또는 이미지를 설정하고 글꼴 크기를 변경할 수 있습니다.

open 속성을 사용하면 닫힌 상태와 다른 스타일을 쉽게 부여할 수 있습니다.

마지막으로 summary 요소에 무언가를 해보겠습니다. 클릭할 수 있지만 링크와 달리 포인터 커서가 표시되지 않고, 버튼과 달리, 사실 버튼처럼 보이지도 않습니다. 따라서 호버와 포커스 상태를 추가해서 방문자가 summary가 클릭 가능하다는 것을 알 수 있도록 해야합니다.

summary:hover,
summary:focus {
  cursor: pointer;
  background: deeppink;
}

여기서 "링크에만 포인터 커서가 있어야 한다"는 논의는 차치하고, 제가 말씀드리고 싶은 부분은 클릭 가능함을 알 수 있도록 여러분이 무언가 해야한다는 것입니다.

대화 상자 모달

사용자에게 무언가를 알려주거나, 물어보거나, 확인해야 할 때가 있습니다. 자바스크립트에서는 alert(), prompt(),confirm() 함수가 이러한 기능을 수행합니다. 하지만 이 함수들은 메인 스레드를 잠그기 때문에 페이지에서 다른 작업을 할 수 없다는 큰 단점이 있습니다. 또한 브라우저 기본 기능이기 때문에 디자인에 맞게 스타일을 지정할 수 없습니다.

직접 대화 상자를 만드는 것도 문제가 있습니다. 접근성을 위해서는 포커스를 대화 상자 안에 유지해야 하고, 모달임을 알리고, 사용자가 실수로 대화 상자를 종료하지 못하도록 해야하며, 2147483647의 z-index를 차지하는 채팅 위젯(아는 사람은 아실겁니다)과도 싸워야 합니다.

이것이 바로 이제 브라우저가 기본 대화 상자 요소를 제공하는 이유입니다.

<dialog>
  <form method="dialog">
    <h3>This is a pretty dialog</h3>
    <button type="submit">Close</button>
  </form>
</dialog>

이 요소는 자동으로 표시되지 않으므로 지금은 약간의 편법으로 자바스크립트를 사용하겠습니다.

document.querySelector("button").addEventListener("click", () => {
  document.querySelector("dialog").showModal();
});

현재 자바스크립트 없이도 대화 상자를 열 수 있도록 변경 작업이 진행 중이지만, 아직 구현은커녕 완전한 사양도 지정되지 않았습니다. 따라서 아직은 자바스크립트를 사용해서 대화 상자를 열어야 합니다. 나머지는 모두 기본 HTML과 CSS입니다.

대화 상자 요소에는 대화 상자를 표시할 수 있는 showModal() 함수가 있습니다. 이 대화상자는 브라우저의 새로운 개념인 top-layer에서 열립니다. 처음 들어보셨다면 자세한 내용은 MDN의 최상위 계층(Top layer)에서 살펴보세요.

최상위 계층은 HTML과 분리된 새 레이어로, 요소를 이 레이어로 "승격"할 수 있습니다. 최상위 계층의 요소는 요소의 z-index와 컨텍스트 중첩에 관계 없이 항상 다른 모든 요소보다 위에 위치합니다.

열리긴 했지만 브라우저가 아무런 UI도 제공하지 않는 것을 알 수 있습니다. 대화 상자는 button이 아닌 div이며 닫는 UI를 제공하는 것은 사용자의 몫입니다. 위 코드에서 form이 바로 그 역할을 합니다. method에 "dialog"라는 것이 있다는 것을 눈치채셨을 수 있습니다. 이 폼이 제출되면 브라우저는 이를 대화 상자를 다시 닫으라는 신호로 받아들입니다.

이를 통해 각각 고유한 값을 가진 두 개의 버튼으로 확인 대화 상자를 만들 수도 있습니다.

<dialog>
  <form method="dialog">
    <p>Tabs or spaces?</p>
    <button type="submit" value="wrong">Tabs</button>
    <button type="submit" value="correct">Spaces</button>
  </form>
</dialog>

사용자가 클릭한 버튼은 대화 상자의 close 이벤트를 수신하고 returnValue 속성을 읽어 찾을 수 있습니다.

dialog.addEventListener("close", function () {
  console.log(dialog.returnValue);
});

다른 폼 데이터가 있는 경우에는 formData를 사용해 읽을 수도 있습니다.

대화 상자는 기본적으로 div이기 때문에 원하는 대로 스타일을 지정할 수 있습니다. 브라우저에서 자동으로 화면 중앙에 배치해주지만 그 외의 모든 것은 사용자가 결정할 수 있습니다.

대화 상자에는 ::dackdrop이라는 새로운 의사 요소도 있습니다. 이 레이어는 대화 상자와 페이지의 나머지 부분 사이에 위치하며, 페이지의 나머지 부분을 어둡게 하거나 사용자의 주의를 대화 상자로 유도하는 등의 스타일을 지정할 수 있습니다. 예를 들면 흰색 레이어를 오버레이하고 페이지를 흐리게 처리할 수 있습니다.

dialog::backdrop {
  background: #fff5;
  backdrop-filter: blur(4px);
}

대화 상자 요소 자체와 마찬가지로 배경도 브라우저에 의해 배치되므로 스크롤, 고정 요소 및 브라우저 크기 조정에 대해 걱정할 필요가 없습니다. 이 모든 것들이 브라우저에서 자동으로 처리됩니다.

마치며

이 글을 통해 다음 프로젝트에서는 자바스크립트를 조금 더 적게 사용할 수 있다는 사실을 깨닫는 계기가 되셨기를 바랍니다. 이미 검증된 구현을 새로운 것으로 변경할 때는 특히 접근성 측면에서 누구도 배제되지 않도록 테스트하는 것이 좋습니다.

이 글에 추가할 수 있는 예제는 수십 가지가 더 있지만, 살펴보면 좋을 몇 가지는 다음과 같습니다.

  • scroll-behavior: smooth를 통한 부드러운 스크롤(단 prefers-reduced-motion: no preference 일 때에만)
  • scroll-snap을 포함한 기본 캐러셀
  • position: sticky를 갖는 "In-view" 요소
  • 컨테이너 쿼리의 개념들

그리고 미래에는 훨씬 더 멋진 일들이 기다리고 있습니다.

  • 스크롤 기반 애니메이션
  • masonry.js를 사용하지 않고 grid-template-rows: masonry를 통한 Masonry 레이아웃
  • 새로운 selectlist 요소로 완전히 스타일링 가능한 select (함께 제공되는 모든 기본 기능을 해치지 않고 select의 각 부분을 스타일링 할 수 있습니다)
  • 전체 JS 선택 클래스를 제거할 수 있는 :has() 선택자

이 글은 이러한 주제와 기타 주제에 대해 더 자세히 다룬 컨퍼런스 강연을 각색한 것으로 그것을 위해 자바스크립트를 사용하지 마세요: JS에서 CSS 및 HTML로 기능 이동하기에서 원강연을 보실 수 있습니다.

마지막으로 이 글의 요점을 다시 한 번 강조하겠습니다.

자바스크립트가 필요하다는 것을 알고 있다고 해서 여전히 자바스크립트가 필요하다는 의미는 아닙니다. 때때로 이러한 가정들을 테스트해보면 더 나은 웹사이트를 만들 수 있습니다.

1개의 댓글

comment-user-thumbnail
2024년 1월 14일

잘 보고 갑니다 감사합니다

답글 달기