탭으로 상호작용 할 수 있는 웹을 만들자.
마우스로 손을 옮기기 귀찮아서 탭을 쓴 경험이 다들 있을것이다.
무선마우스를 쓴다면 배터리가 닳아서 하는 수 없이 키보드로만 컴퓨터를 써야 할 때도 분명 있었다.
다른것도 시급하지만... 그래서 이부분을 먼저 리팩토링해보기로 했다.
마음먹은 거에 비해 그리 대단히 손이 가는 건 아니었다.
'tabindex'를 쓰면 된다.
tabindex: 요소가 포커스될 수 있음을 나타낸다.
tab 키를 연속적으로 눌렀을 때 어느 순서로 이동할지 설정해준다.
-1
: tab으로 접근할 수는 없지만 마우스 클릭으로는 포커스 가능. 위젯 접근성 확보를 위해 사용한다고 한다.<input tabIndex={1} />
생각보다 적용하는데 시간이 많이 들었다.
input, button 등 포커스가 필요한 요소에는 기본적으로 tab 키를 눌렀을때 포커싱이 간다.
하나하나 주려고 하다 보니 순서가 이상해졌다.
선택적으로 주는 것이 오히려 좋아보임.
css 가상선택자 중에서 :focus
라는 것이 있는데, focus로 스타일링하면 tab했을때 스타일링이 되긴 하지만 마우스로 클릭했을때도 스타일링되버린다.
그럴때 쓰는것이 :focus-visible
이다.
마우스로 클릭했을때는 스타일링이 되지 않고 tab을 사용했을때 스타일이 적용된다.
(+) 앗, 그런데 버튼은 잘 작동되는데 비해 input은 마우스로 포커스를 주었을때도 스타일이 적용되는 것을 볼 수 있었다.
:focus-visible
를 사용해보니 border가 생기기 때문에 레이아웃이 어느정도 무너진다.그래서 나는 가상 클래스 선택자를 하나 더 사용했다.
바로 :after
이다.
내가 가장 많이 사용해본 가상선택자 중 하나가 아닐까 싶은데,
아래와 같이 사용한다.
비슷하게 :before
도 있다.
export const ButtonStyle = styled.button`
position: relative;
&:focus-visible::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100%;
height: 100%;
border: 0.1rem solid #171e71;
border-radius: 0.3rem;
}
`;
버튼을 기준으로(relative) 가상요소를 정렬한다.
transform과 top, left를 위와 같이 주어 가상요소의 한가운데를 기준으로 하도록 조정한다.
너비와 높이를 버튼만큼 차지하도록 한다.
그리고 content에 빈문자열을 꼭 넣어주어야 한다.
위의 과정을 다 거쳐야 가상요소가 화면에 나타난다.
하나라도 빠지면 안나옴.
그리고 이젠 내가 주고싶은 스타일을 추가하면 된다.
보통 포커스할때 하는것처럼 보더를 주었다.
가상클래스를 사용하면 기존의 레이아웃에 영향을 주지 않고 스타일링 할 수가 있다.
tabIndex를 todo item에 주었다.
투두는 버튼도 인풋도 아니라서 tabIndex를 주어야 tab으로 선택 가능하다.
<TodoListStyle.TodoUl>
{
todos.map((todo, i) => {
return (
<div key={todo.id}>
<TodoItem key={todo.id} todo={todo} refresher={refresher} tabIndex={i+1}
/>
</div>
)
})
}
</TodoListStyle.TodoUl>
이렇게 tabIndex에 i+1을 주었다. 0이 되면 안되고 1부터 시작해야하니까!
그리고 엔터를 쳐서 이벤트핸들러가 발생할 수 있도록 함수를 하나 더 만들었다.
onKeyPress로
// 마우스로 동작하는 기존함수. onClick
const handleOpenDetailPage = () => {
curParams === todo.id ?
navigate('/') :
navigate(`${todo.id}`);
};
// 엔터키를 눌렀을 때 위 함수를 콜한다. onKeyPress
const OpenDetailWithEnter = (e) => {
if(e.key === 'Enter') {
handleOpenDetailPage();
}
}
return (
<>
<TodoItemStyle.TodoItemBox
key={todo.id}
onClick={handleOpenDetailPage}
onKeyPress={OpenDetailWithEnter}
tabIndex={tabIndex+1}>
<TodoItemStyle.TodoTitle />
</TodoItemStyle.TodoItemBox>
</>
)
이제 탭으로만 자유자재로 동작하는 투두가 되었다!! 후후😝
리베하얀님 유튜브 쇼츠 보다가 좋은거 발견해서 추가해본다.
https://youtube.com/shorts/Geu8Un0CwMs?feature=share
.check:focus + label::before {
outline: 20x dashed blue;
outline-offset: 3px; //간격 벌리기도 가능
outline-width: 12px; //px | thin | medium | thick
outline-style: none; | dotted | solid | groove | inset
}
//border-radious를 주면 둥글게도 할 수 있다.
check가 포커싱되면 인접한 라벨의 가상요소에 보더가 생긴다.
border는 박스 사이즈에 px이 추가된다. 박스 크기가 늘어나는것!
그러나 아웃라인은 박스 사이즈가 라인을 주기 전과 동일하다.
왜 이게 중요하냐면... 레이아웃이 달라지기때문.
내가 위에서 보더를 주었기 때문에 레이아웃이 망가져 가상선택자를 사용해 아주 복잡한 방법으로 보완을 했다.
사실상 아웃라인이라는 좋은 방법이 있었음에도 불구하고.... ㅠㅠ 이제라도 알아서 아주 다행이다...!!!!