디자인이 너무 이쁜 Toggle 버튼이 있어서 해당 코드를 베이스로 CSS 공부를 하려고 한다.
컴포넌트 분석
// components/toggle
import { useState } from "react";
import "./toggle.style.css";
const Toggle = () => {
const [toggle, setToggle] = useState(false);
const handleClickToggle = () => {
setToggle((prev) => !prev);
};
const btnClassName = [
"toggle-btn",
toggle ? "toggle-btn-on" : "toggle-btn-off",
].join(" ");
return (
<label className="toggle-container" aria-label="Toggle">
<input
className="toggle-input"
type="checkbox"
checked={toggle}
onClick={handleClickToggle}
data-testid="toggle-input"
/>
<span className={btnClassName} />
</label>
);
};
export default Toggle;
CSS를 공부하기 전에 컴포넌트가 어떻게 구성되었는지 먼저 확인하자
const [toggle, setToggle] = useState(false);
const handleClickToggle = () => {
setToggle((prev) => !prev);
};
Toggle 버튼을 누르면 상태가 변화하는 부분이다.
간단하게 useState를 사용해서 상태를 나타내고 함수에서도 변경 말고는 하는 게 없다.
const btnClassName = [
"toggle-btn",
toggle ? "toggle-btn-on" : "toggle-btn-off",
].join(" ");
디자인을 위함 ClassName이다.
기본적으로 toggle-btn이 있고, 상태에 따라 on / off로 나뉜다.
<label className="toggle-container" aria-label="Toggle">
<input
className="toggle-input"
type="checkbox"
checked={toggle}
onClick={handleClickToggle}
data-testid="toggle-input"
/>
<span className={btnClassName} />
</label>
렌더링 부분이다.
특별한 것은 없고 전체 틀은 toggle-container, 그리고 실질적인 input은 toggle-input
그 외 디자인 요소인 span은 앞서 만든 btnClassName을 넣었다.
디자인
.toggle-container {
display: block;
}
.toggle-btn {
box-sizing: initial;
display: inline-block;
outline: 0;
width: 8em;
height: 4em;
position: relative;
cursor: pointer;
user-select: none;
background: #fbfbfb;
border-radius: 4em;
padding: 4px;
transition: all 0.4s ease;
border: 2px solid #e8eae9;
}
.toggle-input:focus + .toggle-btn::after,
.toggle-btn:active::after {
box-sizing: initial;
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1), 0 4px 0 rgba(0, 0, 0, 0.08),
inset 0px 0px 0px 3px #9c9c9c;
}
.toggle-btn::after {
left: 0;
position: relative;
display: block;
content: "";
width: 50%;
height: 100%;
border-radius: 4em;
background: #fbfbfb;
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275),
padding 0.3s ease, margin 0.3s ease;
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1), 0 4px 0 rgba(0, 0, 0, 0.08);
}
.toggle-btn.toggle-btn-on::after {
left: 50%;
}
.toggle-btn.toggle-btn-on {
background: #86d993;
}
.toggle-btn.toggle-btn-on:active {
box-shadow: none;
}
.toggle-btn.toggle-btn-on:active::after {
margin-left: -1.6em;
}
.toggle-btn:active::after {
padding-right: 1.6em;
}
.toggle-btn[disabled] {
opacity: 0.7;
cursor: auto;
}
.toggle-input {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
white-space: nowrap;
}
어질어질한 CSS 코드이다...
.toggle-container {
display: block;
}
먼저 label의 display를 block으로 변경하였다.
이건 label의 영역을 자식 전체를 포함하게 하기 위해서 설정한 것 같다.
이걸 하지 않는다고 Toggle이 작동하지 않는 것은 아니다. 하지만 aria-label을 사용한다면
자식 전체를 포함해야 스크린리더를 사용했을 때 버튼 영역을 알 수 있어서 설정한 것 같다. ( 디테일 ㄷㄷ )
Toggle Input
.toggle-input {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
white-space: nowrap;
}
input 부분이다. 화면에서 input을 숨기기 위해서 사용한 것 같다.
근데 이 부분은 개인적으로 그냥 opacity 또는 visibility를 사용하는 게 더 간편하지 않을까 생각이 든다.
물론 position: absolute는 필요하다 ( 그래야 영역을 차지하지 않기 때문에 )
그리고 opacity와 visibility 모두 렌더링을 시키지만 화면에서 투명하게 나타내는 속성인데, 차이점으로는
opacity는 뒤에 있는 요소를 클릭할 수 없지만, visibility는 클릭할 수 있다.
그러므로 visibility가 좀 더 어울릴 것 같다.
정리하면
.toggle-input {
visibility: hidden;
position: absolute;
}
나라면 이 부분은 이렇게 작성할 것 같다.
Toggle 버튼
.toggle-btn {
box-sizing: initial;
display: inline-block;
outline: 0;
width: 8em;
height: 4em;
position: relative;
cursor: pointer;
user-select: none;
background: #fbfbfb;
border-radius: 4em;
padding: 4px;
transition: all 0.4s ease;
border: 2px solid #e8eae9;
}
다음은 Toggle 버튼의 디자인 요소이다.
display: inline-block;
먼저 display이다.
본인은 왜 block이 아니라 inline-block을 했는지 이해가 잘 되지 않았다.
이해하려면 block과 inline의 차이를 알아야 했다.
간단하게 정리하면 inline은 줄 바꿈 없이 한 줄에 다른 요소들을 배치할 수 있다.
하지만 height와 width, margin-top / down을 가질 수 없다.
이것을 block은 가질 수 있다. 그런데, inline-block은 둘의 특징을 모두 가지고 있다.
줄 바꿈이 없고, width, height, margin-top/down을 가질 수 있다.
outline: 0;
outline은 브라우저마다 제각기 다르게 표현될 수 있다.
그래서 디자인의 통일을 위해서 0으로 두고 border 또는 box-shadow를 사용해서 테두리를 그려준다.
user-select: none;
user-select 속성은 사용자의 드래그, 텍스트 선택을 설정하는 속성이다.
나도 처음 알게 된 속성인데, 굳이 Toggle 버튼에 설정한 이유는 모르겠다. 추후 span 영역에 텍스트를 넣을 경우를
예상한 거라면 그럴 수 있겠다 싶다.
transition: all 0.4s ease;
추후 애니메이션의 딜레이를 0.4초로 설정하였다.
.toggle-btn::after {
left: 0;
position: relative;
display: block;
content: "";
width: 50%;
height: 100%;
border-radius: 4em;
background: #fbfbfb;
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275),
padding 0.3s ease, margin 0.3s ease;
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1), 0 4px 0 rgba(0, 0, 0, 0.08);
}
필자가 개인적으로 제일 어려워하는 ::after이다.
after는 가상 선택자로 본 컨텐츠의 마지막에 생성된다.
display: block;
width: 50%;
height: 100%;
display를 block으로 설정해서 width와 height를 설정했다.
width를 50%로 설정했더라도 block의 특징으로 전체 라인을 가지고 있기 때문에 나머지 50%에 다른 것이 들어갈 수 없다.
left: 0;
position: relative;
position:relative는 left 속성을 사용할 수 있게 하기 위해서 설정했다.
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275),
padding 0.3s ease, margin 0.3s ease;
기본적으로 전체적으로 0.3초 딜레이를 주었고, cubic-bezier는 진행 속도를 나타낸다.
0.175초가 흘렀을 때 애니메이션은 885 / 1000 % 진행되고, 0.32초가 흘렀을 때 1275 / 1000 %가 진행된다는 뜻.
이것을 ease, linear, ease-in 등 키워드로 나타낼 수 있고, 다음과 같이 수치로 직접 줄 수 있다.
.toggle-btn.toggle-btn-on::after {
left: 50%;
}
.toggle-btn.toggle-btn-on {
background: #86d993;
}
Toggle이 활성화된다면 left를 50% 이동시켜 가운데로 이동시켜주는 역할인 것 같다.
또한 배경색을 지정해서 on / off를 표시하였다.
.toggle-btn.toggle-btn-on:active::after {
margin-left: -1.6em;
}
.toggle-btn:active::after {
padding-right: 1.6em;
}
active 가상 선택자로 마우스를 클릭했을 때를 나타낸다.
기본적으로 Toggle을 클릭하면 padding-right로 1.6em이 커진다.
여기서 Toggle이 활성화가 되었을 때 버튼은 오른쪽으로 이동하므로 그것을 margin-left로 1.6em 이동시켜
실제로는 오른쪽으로 1.6em 커졌지만 왼쪽으로 커지는 애니메이션을 만들었다.
.toggle-btn[disabled] {
opacity: 0.7;
cursor: auto;
}
disabled 시 설정을 주어서 Toggle이 비활성화 시 따로 디자인을 주었다.
'Web' 카테고리의 다른 글
[CSS] 사파리 환경 input 스타일 초기화 (0) | 2023.03.13 |
---|---|
[HTML] 크롬 & 사파리 Video (1) | 2023.03.12 |
[CSS] 가상 요소 vs 가상 클래스 (0) | 2023.02.17 |
[CSS] user-select (0) | 2023.02.15 |
[CSS] Block VS Inline (0) | 2023.02.14 |