{"id":27250643,"url":"https://github.com/jbee37142/ux-lab","last_synced_at":"2025-04-11T00:45:04.132Z","repository":{"id":61648693,"uuid":"201196905","full_name":"jbee37142/ux-lab","owner":"jbee37142","description":null,"archived":false,"fork":false,"pushed_at":"2019-08-30T04:45:22.000Z","size":11062,"stargazers_count":56,"open_issues_count":0,"forks_count":1,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-04-11T00:44:56.033Z","etag":null,"topics":["animation","transition","ux","webview"],"latest_commit_sha":null,"homepage":null,"language":"CSS","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jbee37142.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-08-08T06:53:34.000Z","updated_at":"2023-12-07T23:56:58.000Z","dependencies_parsed_at":"2022-10-19T21:30:13.558Z","dependency_job_id":null,"html_url":"https://github.com/jbee37142/ux-lab","commit_stats":null,"previous_names":["jbee37142/ux-lab"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbee37142%2Fux-lab","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbee37142%2Fux-lab/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbee37142%2Fux-lab/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbee37142%2Fux-lab/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jbee37142","download_url":"https://codeload.github.com/jbee37142/ux-lab/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248322604,"owners_count":21084336,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["animation","transition","ux","webview"],"created_at":"2025-04-11T00:45:03.564Z","updated_at":"2025-04-11T00:45:04.126Z","avatar_url":"https://github.com/jbee37142.png","language":"CSS","funding_links":[],"categories":[],"sub_categories":[],"readme":"# UX-Lab\n\n이게 웹이야? 하는 그날까지 🚀\n\n### Table of Contents\n\n- [Phase 1. War of Input](#phase-1-War-of-Input-tag)\n- [Phase 2. App Like Animation](phase-2-app-like-animation')\n\n# Phase 1. War of Input tag\n\nWebview에서 input과 관련하여 맞딱뜨린 이슈들을 정리한다. 기본적으로 input 태그를 클릭하면 키패드가 올라온다. 그리고 그 때부터 UX 담당자 분들과의 불협화음이 시작된다.\n\n## Q1. 키패드 위에 버튼을 둘 수 있나요?\n\n하단에 fixed position으로 button이 있는 상황을 가정하자. 하단에 fixed position으로 줬으니 이 버튼은 키패드 바로 위쪽에 붙어있기를 기대하기 마련이다.\n\n**하지만,**\n\n### iOS\n\n- 하단 버튼을 덮으면서 키패드가 올라온다.\n- 그리고 스크롤이 없었다가 스크롤이 발생한다.\n  - 즉 화면에서 보이는 영역이 viewport보다 작아졌다고 인식을 하게 된다.\n\n### AND\n\n- 기대한 것처럼 키패드 바로 위쪽에 붙어있는다.\n- 그리고 스크롤도 발생하지 않는다.\n  - 즉 viewport를 화면에서 보이는 영역으로 잡는다.\n\n## Q2. 화면으로 진입하자마자 키패드를 띄울 수 있나요?\n\n한 화면에 input tag 하나만 존재하는 경우가 있을 수 있다. 보통 이럴 경우 화면에 진입하자마자 키패드를 띄우면 사용성이 증대될 것이다. 사용자가 input 태그를 한 번 더 클릭하여 키패드를 띄우는 것보다 action이 하나 줄어들기 때문이다.\n\n이 요구 사항은 iOS, AND 둘 다 `autoFocus={true}`를 주면 된다.\n\n→ 특정 모달 안에 있는 input도 가능하다! 즉 모달이 노출됨과 동시에 키패드를 띄울 수 있다.\n\n![route-immediate-open-keypad](./assets/route-immediate-open-keypad.gif)\n![modal-immediate-open-keypad](./assets/modal-immediate-open-keypad.gif)\n\n## Q3. 키패드 바깥 부분을 클릭해도 키패드가 내려가지 않도록 할 수 있나요?\n\n### Try 1. `onBlur` 이벤트 발생 시, focus를 강제한다.\n\n- 키패드에 있는 '완료' 버튼을 클릭하더라도 닫히지 않아서 onBlur를 건드릴 수 없다.\n\n### Try 2. 특정 target을 잡은 후, focus를 강제한다.\n\n- 특정 target이 여러개 일 경우, 전부 click handler를 등록해줘야 한다.\n- 이 방식으로 가능은 하지만, 코드로 focus를 강제해줄 경우, 커서 깜빡임이 사라진다.\n\n![prevent-hide-keypad](./assets/prevent-hide-keypad.gif)\n\n## Case 4. 키패드에 영향받지 않고 정중앙 위치\n\n- 모달의 크기에 따라 다르겠지만...\n  - iOS인 경우에는 viewport를 조정하지 않기 때문에 키패드가 올라오기 전과 동일하다.\n    - 즉 키패드가 없는 경우를 키패드 올라왔을 경우의 정중앙으로 위치시키면 vertical 중앙에 위치시킬 수 있다.\n  - AND의 경우에는 디바이스에서 viewport를 조정하기 때문에 적당한 위치로 알아서 조정한다.\n\n\u003cbr /\u003e\n\n# Phase 2. App Like Animation\n\nApp에서는 화면 간 이동 시, 부드러운 애니메이션과 함께 transition 같은 것이 진행된다. 웹에서도 해보자.\n\n|                 Drawer                  |               Pagination                |\n| :-------------------------------------: | :-------------------------------------: |\n| ![](./assets/transition_bottom_box.gif) | ![](./assets/pagination_transition.gif) |\n\n## 애니메이션?\n\n\u003cdiv align='center'\u003e\n\n![](./assets/animation.gif)\n\n네이버 스마트어라운드 서비스\n\n\u003c/div\u003e\n\n## 기본적인 Toggle 동작 (non-animation)\n\n1. A 클릭 → A: `display: none`\n2. B: `display: block`\n\n## With Animation\n\n결국 사라지지만 애니메이션 동안 그 형태를 유지하며 애니메이션을 노출한다. (잔상)\n\n1. A 클릭\n2. A가 사라지기 전에 **clone ⇒ A'**\n3. A 사라짐\n4. B가 렌더링 되지만 보이지 않도록 한다.\n5. A' 애니메이션과 함께 사라지기\n6. A' 애니메이션이 끝날 때, B가 애니메이션과 함께 노출되기\n7. Clone 된 A' 삭제\n\n## 핵심\n\n- Clone\n- 애니메이션이 시작하는 시점과 끝나는 시점 컨트롤\n\n### Code\n\n- keyframes (or transition)\n- `animationstart/animationend` event (`transitionstart/transitionend` event)\n- Promise (control async)\n\n\u003e keyframes 다루는 hepler\n\n### Start Animation\n\n```\nconst animate = (props: AnimationProp) =\u003e {\n  return new Promise((resolve, reject) =\u003e {\n    // Animation Start\n    target.addEventListener(\"animationend\", onAnimationEnd);\n    target.classList.add(className);\n  })\n}\n\n```\n\n\u003csmall\u003ehttps://github.com/JaeYeopHan/animation-helper/blob/master/src/index.ts\u003c/small\u003e\n\n## react-transition-group\n\n[reactjs/react-transition-group](https://github.com/reactjs/react-transition-group)\n\nReact 유틸 라이브러리 중 위에서 언급한 핵심 역할을 해주는 라이브러리\n\n\u003e 사용해보신 분은 아시겠지만 공식 문서가 매우 불친절...🤬\n\n### 해주는 역할은 딱 2가지\n\n- children을 clone하여 prev와 next를 둘 다 렌더링\n- 렌더링 시점에서 life cycle을 추가하여 class selector를 붙였다 뗐다 함.\n\n### Inspect\n\n![](./assets/clone_inspect.gif)\n\n### 1. Clone Element\n\n- prev element\n- next element\n\n### 2. Life Cycle (with selector)\n\n- next element\n  - `*-enter` (ready)\n  - `*-enter-active` (trigger)\n  - `*-enter-done` (done)\n- prev element\n  - `*-exit` (ready)\n  - `*-exit-active` (trigger)\n  - `*-exit-done` (done)\n\n+`*`에는 prefix\n\n### 장점\n\n애니메이션의 Life cycle을 사용하여 `animationend` 또는 `transitionend` 이벤트를 알 필요가 없다.\n\n즉, Life Cycle 에 따라 추가되고 제거되는 CSS Selector에 알맞은 스타일만 추가해주면 된다.\n\n### 몇 가지 한계점\n\n1. 스마트어라운드에서 했던 keyframes 기반의 복잡한 애니메이션은 한계가 있음.\n2. transition을 적용할 element에 timeout을 여러 가지 둘 수 없다.\n\n## Code\n\n\u003e Talk is cheap. show code.\n\n### Drawer Transition Container\n\n```ts\nimport React, { ReactChild } from 'react'\nimport { CSSTransition } from 'react-transition-group'\n\ninterface IDrawerTransitionProps {\n  children: ReactChild | ReactChild[]\n  in: boolean\n}\n\nexport const DrawerTransition = (props: IDrawerTransitionProps) =\u003e {\n  return (\n    \u003cCSSTransition timeout={400} in={props.in} classNames={'drawer'}\u003e\n      {props.children}\n    \u003c/CSSTransition\u003e\n  )\n}\n```\n\n### Transition Styles\n\n```css\n/* DRAWER TRANSITION */\n.drawer-enter {\n  transform: translate3d(0, 100%, 0);\n}\n.drawer-enter-active {\n  transform: translate3d(0, 0, 0);\n  transition: transform 400ms;\n}\n.drawer-exit {\n  transform: translate3d(0, 0, 0);\n}\n.drawer-exit-active {\n  transform: translate3d(0, 100%, 0);\n  transition: transform 400ms;\n}\n```\n\n### 추가적으로 고려했던 Detail\n\n- 뒤로 가기 시에는 reverse transition\n- Parallax Effect\n- Opacity와 transform 시간 차이\n\n(애니메이션을 비롯한 UX 연구는 계속 진행 중 🤟)\n\n\u003csmall\u003e\n\n\u003ca href=\"https://github.com/JaeYeopHan/ux-lab\"\u003ehttps://github.com/JaeYeopHan/ux-lab\u003c/a\u003e\n\n\u003c/small\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n\u003csub\u003e\u003csup\u003eProject by \u003ca href=\"https://github.com/JaeYeopHan\"\u003e@Jbee\u003c/a\u003e\u003c/sup\u003e\u003c/sub\u003e\u003csmall\u003e✌\u003c/small\u003e\n\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjbee37142%2Fux-lab","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjbee37142%2Fux-lab","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjbee37142%2Fux-lab/lists"}