Input 창을 멋지게 만들고싶다

krkorklo·2022년 12월 30일
0
post-thumbnail

로그인을 구현하는데 input을 조금 멋드러지게 만들어보고 싶었다.

원한 디자인 및 인터랙션은 위와 같다. input에 커서가 focus되어있거나 입력되어있는 상태이면 label이 위로 가고, focus되어있지 않고 값도 없는 경우에는 label이 다시 내려온다.
이걸 정확히 뭐라 칭해야할지 모르겠는데 그냥 input 애니메이션이라고 하겠다

input 애니메이션 구현하면서 생각보다 많은 시행착오를 거쳤다. 몇 시간을 들였던 것 같다🥹 다시는 실수하지 않겠다는 의미로 하나하나 적어보려고 한다.

첫 번째 시도

특정 컴포넌트에 스타일 부여

현재 프로젝트에서 emotion을 사용하고 있다. 그래서 가장 먼저 한 생각은 emotion의 component selector를 사용해서 구현하는 것이었다. label 컴포넌트를 만들고, input이 클릭되면 해당 label 컴포넌트에 새로운 스타일을 부여하는 방법이다.

export const Label = styled(Body3)`
  position: absolute;
  left: 0;
  top: 0;

  color: ${({ theme }) => theme.gray_5};

  transition: 0.3s;
`;

export const TextInput = styled.input`
  ...
  :focus,
  :valid {
    ${Label} {
      transform: translateY(-1.6rem);

      font-size: 1.2rem;
      font-weight: 300;
      color: ${({ theme }) => theme.primary};
    }
  }
`
export default function InputBar({ value, type, handleTextChange }: IInputBar) {
  return (
    <Container>
      <TextInput value={value} onChange={handleTextChange} />
      <Label>{type}</Label>
    </Container>
  );
}

구현에 필수적인 코드만 나열하자면 위와 같다.

Input과 label을 담당하는 태그를 두 개 놓고 Input이 focus되거나 유효한 경우 label에서 변화가 생기게 해두었다.

focus되거나 유효한 경우 transform으로 label의 위치를 위쪽으로 옮기고 폰트 크기, weight, color까지 변경해주었다. 또한 애니메이션처럼 서서히 속성들을 변화시키기 위해 transition을 부여했다.

그런데? 안 된다.
focus나 유효한 경우 속성값이 변경되지도 않고 Label때문에 아이디 텍스트는 클릭해도 input으로 넘어가지를 않는다.

왜 안될까

export const TextInput = styled.input`
  ...
  :focus,
  :valid {
    ${Label} {
      ...
    }
  }
`

가장 근본적인 원인은 component selector를 저기다가(Input 태그 안에) 썼다는 것이다.

component selector를 input 안에 사용하면 당연히 부모나 자식 태그(css에서는 부모 요소에는 접근 불가)에 해당하는 경우에만 적용이 가능하다. 굉장히 기본적인 부분인데 구현만 하느라 생각을 못 했다,,,,,,,,,,,,,,,,,

두 번째 시도

인접 요소에 접근

Label은 자식 요소가 아니니까 어떻게 접근하지?

→ 인접 선택자 +를 사용하자. a + b는 a 뒤에 오는 b 태그라는 의미이다.

export const Label = styled(Body3)`
  position: absolute;
  left: 0;
  top: 0;

  color: ${({ theme }) => theme.gray_5};

  transition: 0.3s;
`;

export const TextInput = styled.input`
	...
	:focus + ${Label}, :valid + ${Label} {
    transform: translateY(-1.6rem);

    font-size: 1.2rem;
    font-weight: 300;
    color: ${({ theme }) => theme.primary};
  }
`

이전 코드에서 component selector 적용되는 부분만 인접 연산자로 바꾸어주었다.

어잉,,, 적용이 되긴 했는데 focus나 유효한 경우에만 애니메이션이 안 나오고 그냥 냅다 selector로 선언된 스타일이 적용되었다.

왜 안될까

지금 Input 스타일을 보면 focus된 경우와 valid한 경우에 Label에 스타일을 변경하고 있다. 그런데 valid가 어떤 거였지?

valid는 가상 클래스로 input 내의 유효성 검사가 true인 경우 나타난다.

여기서 유효성 검사는 input에 pattern이나 type을 준 경우 해당 부분을 확인한다. 그런데 지금 코드를 보면

<TextInput value={value} onChange={handleTextChange} />

유효성 검사를 지정해주지 않았으니 무조건 true로 반환이 된다. 결국 항상 valid 가상 클래스가 나타난 상태이기 때문에 초기값에서 valid, focus로 변경되는 애니메이션이 안 나타나고 그냥 변경된 상태가 적용된 것이다.

세 번째 시도

input 유효성 검사

지금 valid가 되어야하는 경우는 값이 존재하는 경우이다.

그러면? required 옵션을 넣어주면 되겠다ㅎ

<TextInput value={value} onChange={handleTextChange} required />

와우 이제 애니메이션이 나온다.

근데? placeholder를 위해 선언된 아이디 부분을 누르면 input 입력이 되질 않는다.

왜 안될까

이번에도 당연한 이유다. Label이라는 텍스트 컴포넌트가 input을 가로막고있다. 그러면,,, input과 Label을 연결시켜줘야한다.

네 번째 시도

input과 label 연결

label 태그는 input 태그와 연결할 수 있는 태그이다. labelfor 속성으로 input의 id를 부여해주면 input과 label이 연결된다.

export const Label = styled.label`
  position: absolute;
  left: 0;
  top: 0;

  color: ${({ theme }) => theme.gray_5};

  transition: 0.3s;
`;
export default function InputBar({ value, type, handleTextChange }: IInputBar) {
  return (
    <Container>
      <TextInput id="text-input" value={value} onChange={handleTextChange} />
      <Label htmlFor="text-input">{type}</Label>
    </Container>
  );
}
  • for는 JS의 예약어로 react에서는 label의 for를 htmlFor로 사용해야한다.

이렇게 해주면?

label과 input이 연결되어 label을 클릭해도 input을 클릭한 것처럼 입력이 가능하다!!!

드디어 성,,,공,,,😇

1개의 댓글

comment-user-thumbnail
2023년 10월 12일

멋있어요!!!👍👍
저도 한 번 따라해보고 싶네요..ㅎ

답글 달기