LINE Corporation이 2023년 10월 1일부로 LY Corporation이 되었습니다. LY Corporation의 새로운 기술 블로그를 소개합니다. LY Corporation Tech Blog

Blog


LINE Search UI 개선기

사용자에게 유려한 UI(User Interface)와 좋은 UX(User eXperience, 사용자 경험)를 제공하는 일은 까다롭습니다. 간단한 동작을 만들 때도 많은 것들을 고민하고 만들지만 항상 좋은 결과물이 나오진 않습니다. 사용자를 생각하며 여러가지 시도를 멈추지 않는 것이 프론트엔드 개발자의 숙명이 아닐까 생각합니다.

안녕하세요. 저는 LINE UIT 조직에서 프론트엔드 업무를 담당하고 있는 이상원입니다. 이번 글에선 제가 LINE Search 프로젝트를 담당할 때 새로 추가된 UI를 조금 더 개선하기 위해 노력했던 내용을 공유하려고 합니다.

배경 설명

제가 LINE Search 프로젝트를 담당할 당시, LINE Search에서 이미지 뷰어를 실행할 때 화면 상단에 위치한 검색 바(bar)를 가리는 기능이 추가되었습니다. 개발 당시 여러 일정과 상황이 맞물려서 검색 바가 사라질 때 애니메이션 효과를 추가할 수 없었는데요. 결론적으로 프론트엔드에서 웹 뷰(web view) 영역만 제어하여 사용자가 최대한 좋은 UI를 경험할 수 있도록 만들어야 하는 상황이었습니다. 구현 당시 일정에 쫓기는 바람에 이 글에서 고려했던 모든 사항을 고려하지 못하고 단순하게 클라이언트 팀에서 제공한 API를 호출해 검색 바를 숨길 수 있는 기능을 추가하는 것으로 작업을 완료했습니다.

작업 완료 후 QA(quality assurance)가 시작되었고, QA 팀에서 해당 기능의 동작과 관련된 이슈를 등록했습니다. 아래 동영상은 QA에서 이슈 등록과 함께 제공한 동영상입니다.

위 동영상을 살펴보면서 발견한 첫 번째 문제점은 이미지 뷰어가 실행되기 전에 검색 바가 사라지면서, 웹 뷰 영역이 늘어나며 콘텐츠가 위로 올라가는 현상이 보인다는 점이었습니다. 처음엔 단순하게 '콘텐츠가 위로 밀려 올라가는 모습이 보이지 않으면 되겠지'라는 생각으로 로직 순서를 수정해서 구현해 봤습니다(아래 샘플은 이해를 돕기 위해 실제로 구현된 상태보다 느리게 재생한 상태입니다).

순서
샘플

1. 콘텐츠 영역을 가립니다.


2. 검색 바를 없앱니다.


3. 웹 뷰 영역 크기 변경이 완료되길 기다립니다.


4. 이미지 뷰어를 실행합니다.

첫 번째 시도에서 얻은 결과물을 함께 프로젝트를 진행하고 있던 팀원에게 공유한 뒤 여러 의견을 종합한 결과, 애니메이션을 추가하기로 결정했습니다. QA 시작 전에는 다른 기능들을 구현하느라 애니메이션을 추가할 여건이 되지 않았지만, 기획, 디자인, QA 팀과 협의해 보니 이번엔 추가할 수 있겠다고 판단했습니다. 애니메이션 효과를 추가하는 과정은 일반적인 개발 순서와 크게 다르지 않습니다. 먼저 애니메이션을 설계하고 코드를 작성한 뒤, 코드 리뷰와 QA 과정을 거쳤습니다. 

애니메이션 설계 및 코드 작성

간단하게 CSS로 동작하는 애니메이션을 추가해서 몇 가지 샘플을 제작해 봤습니다.

샘플 1
샘플 2

제작한 샘플 중 팀원들과 함께 결정했던 건 첫 번째 샘플이었습니다. 하지만 좀 더 많은 사람들의 피드백을 받아보니 두 번째 샘플이 좋다는 의견이 더 많아 두 번째 샘플의 애니메이션 형태로 구현했습니다. 애니메이션은 아래와 같이 Vue.js 프레임워크의 transition 기능을 사용해 간단하게 구현할 수 있었는데요. 이 기능을 사용하면 CSS 애니메이션이 시작되고 종료되는 시점을 쉽게 제어할 수 있다는 장점이 있습니다.

<transition name="layer" @after-enter="afterEnter">
  <v-image-viewer />
</transition>
   
<script type="text/javascript">
  export default {
    methods: {
      afterEnter () {
        // call api : close search bar
      }
    }
  }
</script>
 
<style>
  .layer-enter-active, .layer-leave-active {
    transition: all ease 0.2s;
  }
  .layer-enter, .layer-leave-to {
    opacity: 0;
  }
  .layer-leave-to {
    transition: all linear 0.1s;
  }
</style>

위 코드에서 이미지 뷰어를 실행하고 종료하는데 걸리는 시간을 각각 0.2초와 0.1초로 설정한 것도 피드백을 반영한 부분입니다. 단 0.1초 차이일 뿐이지만, 이미지 뷰어가 종료될 때 조금 더 빠른 반응을 보여주는 게 좋다는 의견이 많았습니다.

코드 리뷰와 QA

QA 기간에 추가 기능을 개발하는 것은 금지되어 있었지만, 애니메이션을 추가하기로 결정한 시점엔 이미 QA가 진행되고 있었습니다. 또한 팀원들과 함께 여러 사람의 피드백을 받아 반영하는 와중에 QA 담당자도 현재 상황에서 조금 더 개선해 줄 수 있겠냐는 요청을 보내왔습니다.

아래 동영상을 보면 가장 먼저 화면 하단에 이미 렌더링된 콘텐츠가 이미지 뷰어 앞으로 올라온 것처럼 보인다는 점이 눈에 띕니다.

두 번째 문제는 이미지 뷰어가 종료될 때, 적용해 놓은 애니메이션이 정상적으로 동작하지 않는다는 점이었습니다.

완벽함을 도모하기 위해 아래와 같이 슬로우 모션으로 촬영해 보았더니 육안으로는 파악하기 힘들었던 몇 가지 문제점을 더 발견할 수 있었습니다.

발견한 문제점

이미지 뷰어를 종료할 때 애니메이션이 정상 동작하지 않는다

이 문제는 이미지 뷰어에 슬라이드로 적용된 DOM 객체가 많을 경우 애니메이션 동작에 영향을 미치게 되는 문제였습니다. 이미지 뷰어가 닫히는 시점에서 이미지 뷰어의 슬라이드 DOM 객체를 모두 삭제한 뒤 애니메이션이 동작하도록 수정하여 간단하게 문제를 해결할 수 있었습니다. 추후 사용자가 보고 있는 영역 근처의 DOM만 이미지 슬라이더에 그리도록 개선하는 계기가 되었습니다.

이미지 뷰어를 실행할 때 화면 하단에 콘텐츠 영역이 잠시 보인 뒤 사라진다

이 문제를 해결하기 위해서 z-index를 설정하여 뒤에 그려진 콘텐츠가 왜 이미지 뷰어 앞에 보이는지 원인을 파악했습니다. 원인 파악을 위해 아래 샘플 코드 조건에 맞는 단순한 샘플 코드를 작성해서 테스트해 보았습니다.

샘플 코드의 조건
  • 버튼을 눌러 검색 바를 가리거나 다시 나타나게 조작할 수 있어야 합니다.
  • 첫 번째 DOM 객체는 z-index 값이 없어야 합니다.
  • 두 번째 DOM 객체는 position : fixed 값과 z-index 값을 갖고 첫 번째 DOM 객체 안에 존재해야 합니다.
<html style="margin: 0; padding: 0;">
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,viewport-fit=cover,user-scalable=no,target-densitydpi=medium-dpi">
  </head>
  <body style="margin: 0; padding: 0;">
    <div style="width: 100%; height: 150%; display: block; background-color: red;">
      <div style="width: 100%; height: 100%; display: block; z-index: 1; position: fixed; background-color: yellow; zoom: 1;">
        <button id="openSearchBar"> open Search bar </button>
        <button id="closeSearchBar"> close Search bar </button>
      </div>
    </div>
  </body>
  <script>
    window.document.querySelector('#openSearchBar').addEventListener('click', function () {
        // call open search bar
    })
    window.document.querySelector('#closeSearchBar').addEventListener('click', function () {
        // call close search bar
    })
  </script>
</html>

위 샘플 코드로 확인해 본 결과 검색 바를 가리도록 조작했을 때 배경 색상(background-color)을 빨간색(red)으로 넣은 DOM이 보이는 동일한 현상을 발견할 수 있었습니다. Chrome 개발자 도구에서 layers에 그려진 DOM 객체 상황을 확인해 보니 좀 더 쉽게 이유를 파악할 수 있었습니다.

Layers 파악된 문제
  • 검색 바가 사라지며 웹 뷰의 크기를 변경합니다.
  • window → resize 이벤트가 발생합니다.
    • 이때 이미 렌더링된 첫 번째 DOM 객체는 repaint 이벤트 이전에도 하단이 노출됩니다.
  • 두 번째 DOM 객체에 적용된 height: 100%가 두 번째 DOM 객체의 크기를 변경하여 repaint를 실행합니다.

문제의 원인은 파악했지만 해결은 생각처럼 쉽지 않았습니다. height 값을 120%, 혹은 calc(100% + 150px)로 변경해도 position이 fixed로 지정된 상황에서는 브라우저가 화면을 넘어간 부분을 렌더링하지 않았습니다. 이 문제는 샘플 코드에서 몇 가지 테스트를 해 본 뒤 min-height 값을 브라우저 크기보다 조금 더 크게 설정하여 해결할 수 있었습니다.

화면 하단에 콘텐츠 대신 흰 부분이 보인다

이 이슈는 Android에서만 발생하는 이슈였는데요. 검색 바가 가려지면서 웹 뷰의 사이즈가 변경될 때 repaint되는 순서가 문제였습니다.

1. 검색 바 숨김 요청 2. 웹 뷰 영역의 크기 변경 3. 렌더링 트리의 루트 노드(<html>) 크기 변경 후 repaint 발생 4. 변경된 루트 노드(<html>)의 크기에 맞춰 DOM 객체 repaint 발생

이미지 뷰어의 배경이 검은색이라서 너무 눈에 띄었는데요. HTML에서 동일한 색상을 배경에 적용하는 방식으로 문제를 회피한 상태입니다(참고로 orientationchange 이벤트가 발생할 때는 해당 현상이 나타나지 않았습니다). 

개선 전후 비교

첫 구현 결과물
마지막 구현 결과물

첫 구현 당시 예상치 못한 많은 문제점을 발견했고, 해결하고 나서 이미지 뷰어를 실행, 종료하는 과정에서 어색한 부분이 눈에 띄게 줄어든 것을 확인할 수 있습니다. 보통 애니메이션을 적용하면 실행할 때 더 많은 시간이 걸리게 되지만, 이미지가 뒤틀리는 현상 같은 게 보이지 않아 오히려 더 빨리 실행되는 것처럼 보이기도 합니다. 사용자들이 이미지 뷰어를 사용하면서 조금도 불편함을 느끼지 않길 바라 봅니다.

마치며

이쪽 분야를 잘 모르는 사람들의 관점에선 사용자의 UI와 UX를 개선하는 일이 별것 아닌 일에 큰 공수를 들이는 것처럼 보일 수 있습니다. 하지만 이렇게 심혈을 기울여 제작된 결과를 통해 사용자는 작은 경험들을 쌓아 나가고, 그렇게 쌓인 경험들이 결국 앱에 대해 사용자가 갖는 전체적인 이미지를 결정하게 됩니다. 제가 현재 몸담은 LINE UIT 조직엔 이렇게 작은 부분도 놓치지 않고 꼼꼼하게 확인하며 만들어 나가는 사람들이 함께 있습니다. 덕분에 사용자를 배려하는 코드가 무엇인지 고민하는 개발자로 조금씩 성장하고 있습니다. 이 글을 통해 함께 고민하고 노력해 주시는 팀원분들께 감사 인사를 전해 봅니다.