3-8. 마무리 작업
이번 섹션에서는, 더 자연스러운 유저경험을 위한 부가적인 작업을 진행해보겠습니다.
스피너 보여주기
데이터를 로딩을 할 때, 화면에 빙글빙글 돌아가는 스피너를 보여주면 흐름이 더욱 자연스러워집니다. 스피너가 나타나는 케이스는 총 2가지 입니다.
- 초기 메모를 불러올 때
- 이전 메모를 불러올 때
우선, 스피너 컴포넌트부터 만들어보겠습니다.
처음부터 직접 만들지는 않고, 인터넷에 공개 된 svg-loaders 를 기반으로 만들어보겠습니다.
스피너의 SVG 코드는 다음과 같습니다:
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
<svg width="38" height="38" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" stroke="#fff">
<g fill="none" fill-rule="evenodd">
<g transform="translate(1 1)" stroke-width="2">
<circle stroke-opacity=".5" cx="18" cy="18" r="18"/>
<path d="M36 18c0-9.94-8.06-18-18-18">
<animateTransform
attributeName="transform"
type="rotate"
from="0 18 18"
to="360 18 18"
dur="1s"
repeatCount="indefinite"/>
</path>
</g>
</g>
</svg>
이걸 리액트에서 사용하기 위에서는, 조금 바꿔주어야하는데요. 내용을 보시면 fill-rule
,stroke-opacity
처럼 -
가 사이에 들어있는것들을 camelCase 로 전환하시면 됩니다.
그리고 스피너의 색상은 맨 윗줄의 stroke
를 수정하면 되고, 사이즈는 width
, height
를 수정하면됩니다.
그럼 한번, 이걸 리액트 컴포넌트로 만들어보겠습니다.
src/components/Spinner.js
import React from 'react';
import styled from 'styled-components';
import oc from 'open-color';
import PropTypes from 'prop-types';
const Wrapper = styled.div`
text-align: center;
margin-top: 32px;
margin-bottom: 32px;0
`;
const Spinner = ({visible}) => visible ? (
<Wrapper>
<svg width="64" height="64" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" stroke={oc.gray[7]}>
<g fill="none" fillRule="evenodd">
<g transform="translate(1 1)" strokeWidth="2">
<circle strokeOpacity=".5" cx="18" cy="18" r="18"/>
<path d="M36 18c0-9.94-8.06-18-18-18">
<animateTransform
attributeName="transform"
type="rotate"
from="0 18 18"
to="360 18 18"
dur="1s"
repeatCount="indefinite"/>
</path>
</g>
</g>
</svg>
</Wrapper>
) : null;
Spinner.propTypes = {
visible: PropTypes.bool
}
export default Spinner;
그 다음엔, App 컴포넌트를 리덕스에 연결하는 부분에서, state.pender.pending
를 pending
으로 연결해주세요.
src/containers/App.js
- 하단
export default connect(
(state) => ({
cursor: state.memo.getIn(['data', 0, 'id']),
endCursor: state.memo.getIn(['data', state.memo.get('data').size - 1, 'id']),
pending: state.pender.pending
}),
(dispatch) => ({
MemoActions: bindActionCreators(memoActions, dispatch)
})
)(App);
그리고, Spinner 컴포넌트를 불러오고, render 함수를 다음과 같이 변경하세요. Spinner 컴포넌트를 MemoListContainer 앞뒤에 한개씩 넣어주면 됩니다.
src/containers/App.js
- 상단
import Spinner from 'components/Spinner';
src/containers/App.js
- render
render() {
const { pending } = this.props;
return (
<Layout>
<Header/>
<Layout.Main>
<WriteMemo/>
<MemoListContainer/>
<Spinner visible={pending['memo/GET_INITIAL_MEMO'] || pending['memo/GET_PREVIOUS_MEMO']}/>
</Layout.Main>
<MemoViewerContainer/>
</Layout>
);
}
이렇게 하고나면 페이지에서 데이터를 불러오는중일 때 스피너가 렌더링될것입니다. 하지만, 지금은 서버가 여러분의 PC에 있기때문에 아주 짧은 시간동안만 보일겁니다.
스피너가 제대로 돌아가는지 한번 보고싶다면, 브라우저의 네트워크 속도를 제한시키시면 됩니다.
개발자 도구에서 Network 탭을열은다음에, No Throttling 을 클릭하여 GPRS 로 설정해보세요. 그리고 새로고침을 하시면, 스피너가 조금 더 오래 보이게될것입니다.
(테스팅이 끝났으면 다시 No Throttling 으로 설정하세요)
카드에 애니메이션 설정하기
카드가 생기고, 사라질때마다 애니메이션을 보여주도록 설정을 해보겠습니다.
우선, style-utils
에 다음과 같이 transition 관련 코드를 작성하세요.
src/lib/style-utils.js
import { css, keyframes } from 'styled-components';
// (...)
export const transitions = {
stretchOut: keyframes`
0%{
opacity: 0;
transform: scale(0.25,0.25);
}
100% {
opacity: 1;
transform: scale(1, 1);
}
`,
shrinkIn: keyframes`
0% {
opacity: 1;
transform: scale(1,1);
}
100% {
opacity: 0;
transform: scale(0.25,0.25);
}
`
}
stretchOut 은 확대가 되면서 투명도가 사라지고, shrinkIn 은 작아지면서 투명해집니다.
그 다음엔, MemoList 컴포넌트에서 CSSTransitionGroup 을 통하여 애니메이션을 설정하시면 됩니다.
Wrapper 안에 .memo-enter
, .memo-leave
클래스를 지정하고 CSSTransitionGroup 을 렌더링 하게 될 때에 transitionName 을 memo
로 설정하세요.
그리고 애니메이션 실행 시간은, 메모가 나타날대는 0.3초, 사라질때는 0.15 초로 지정하세요.
src/components/MemoList/MemoList.js
// (...)
import { media, transitions } from 'lib/style-utils';
import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup';
const Wrapper = styled.div`
display: block;
margin-top: 0.5rem;
font-size: 0px; /* inline-block 위아래 사이에 생기는 여백을 제거합니다 */
${media.mobile`
margin-top: 0.25rem;
`}
.memo-enter {
animation: ${transitions.stretchOut} .3s ease-in;
animation-fill-mode: forwards;
}
.memo-leave {
animation: ${transitions.shrinkIn} .15s ease-in;
animation-fill-mode: forwards;
}
`;
const MemoList = ({memos, onOpen}) => {
const memoList = memos.map(
memo => (
<Memo
key={memo.get('id')}
memo={memo}
onOpen={onOpen}
/>
)
);
return (
<Wrapper>
<CSSTransitionGroup
transitionName="memo"
transitionEnterTimeout={300}
transitionLeaveTimeout={150}>
{memoList}
</CSSTransitionGroup>
</Wrapper>
);
};
// (...)
이제 정말로 모든 기능을 다 완성하셨습니다! 다음 섹션에서는 간단한 최적화를 진행하고 히로쿠에 프로젝트를 배포하는 방법을 알아보겠습니다.