Python(Flask)+React+SASS+mongoDB로 웹 서비스 제작하기 프로젝트(2-3) – Login Component 만들기(Animation, SASS)

진짜 진짜 진짜 하고싶었던 것 애니메이션 적용시켜서 Web-Application을 조금 더
‘트렌디’하게 보이게 하는 것… 오늘 했다 그래서 포스팅 연속으로 3개 올리고 있다.

사실 paper_lee가 만든 API를 통해 회원가입 데이터 송수신 하는 것 까지 했는데 아직
완성이 안돼서 오늘 여기까지만 쓸 것..!

먼저 여기 들어가보셈
http://anicollection.github.io
쩔음.

내가 사용한 것은, bounceIn Left Right, bounceOut Left Right, fadeIn 이렇게 5개 씀.

먼저 여기서 코드 복사해서 ‘src/Animation.css’ 에 작성

src/Animation

/*base code*/

.animated {
    -webkit-animation-duration: 1s;
    animation-duration: 1s;
    -webkit-animation-fill-mode: both;
    animation-fill-mode: both;
}

.animated.infinite {
    -webkit-animation-iteration-count: infinite;
    animation-iteration-count: infinite;
}

.animated.hinge {
    -webkit-animation-duration: 2s;
    animation-duration: 2s;
}
@-webkit-keyframes bounceInLeft {
    0%, 100%, 60%, 75%, 90% {
        -webkit-transition-timing-function: cubic-bezier(0.215, .61, .355, 1);
        transition-timing-function: cubic-bezier(0.215, .61, .355, 1)
    }
    0% {
        opacity: 0;
        -webkit-transform: translate3d(-3000px, 0, 0);
        transform: translate3d(-3000px, 0, 0)
    }
    60% {
        opacity: 1;
        -webkit-transform: translate3d(25px, 0, 0);
        transform: translate3d(25px, 0, 0)
    }
    75% {
        -webkit-transform: translate3d(-10px, 0, 0);
        transform: translate3d(-10px, 0, 0)
    }
    90% {
        -webkit-transform: translate3d(5px, 0, 0);
        transform: translate3d(5px, 0, 0)
    }
    100% {
        -webkit-transform: none;
        transform: none
    }
}
@keyframes bounceInLeft {
    0%, 100%, 60%, 75%, 90% {
        -webkit-transition-timing-function: cubic-bezier(0.215, .61, .355, 1);
        transition-timing-function: cubic-bezier(0.215, .61, .355, 1)
    }
    0% {
        opacity: 0;
        -webkit-transform: translate3d(-3000px, 0, 0);
        -ms-transform: translate3d(-3000px, 0, 0);
        transform: translate3d(-3000px, 0, 0)
    }
    60% {
        opacity: 1;
        -webkit-transform: translate3d(25px, 0, 0);
        -ms-transform: translate3d(25px, 0, 0);
        transform: translate3d(25px, 0, 0)
    }
    75% {
        -webkit-transform: translate3d(-10px, 0, 0);
        -ms-transform: translate3d(-10px, 0, 0);
        transform: translate3d(-10px, 0, 0)
    }
    90% {
        -webkit-transform: translate3d(5px, 0, 0);
        -ms-transform: translate3d(5px, 0, 0);
        transform: translate3d(5px, 0, 0)
    }
    100% {
        -webkit-transform: none;
        -ms-transform: none;
        transform: none
    }
}
.bounceInLeft {
    -webkit-animation-name: bounceInLeft;
    animation-name: bounceInLeft
}
@-webkit-keyframes bounceInRight {
    0%, 100%, 60%, 75%, 90% {
        -webkit-transition-timing-function: cubic-bezier(0.215, .61, .355, 1);
        transition-timing-function: cubic-bezier(0.215, .61, .355, 1)
    }
    0% {
        opacity: 0;
        -webkit-transform: translate3d(3000px, 0, 0);
        transform: translate3d(3000px, 0, 0)
    }
    60% {
        opacity: 1;
        -webkit-transform: translate3d(-25px, 0, 0);
        transform: translate3d(-25px, 0, 0)
    }
    75% {
        -webkit-transform: translate3d(10px, 0, 0);
        transform: translate3d(10px, 0, 0)
    }
    90% {
        -webkit-transform: translate3d(-5px, 0, 0);
        transform: translate3d(-5px, 0, 0)
    }
    100% {
        -webkit-transform: none;
        transform: none
    }
}
@keyframes bounceInRight {
    0%, 100%, 60%, 75%, 90% {
        -webkit-transition-timing-function: cubic-bezier(0.215, .61, .355, 1);
        transition-timing-function: cubic-bezier(0.215, .61, .355, 1)
    }
    0% {
        opacity: 0;
        -webkit-transform: translate3d(3000px, 0, 0);
        -ms-transform: translate3d(3000px, 0, 0);
        transform: translate3d(3000px, 0, 0)
    }
    60% {
        opacity: 1;
        -webkit-transform: translate3d(-25px, 0, 0);
        -ms-transform: translate3d(-25px, 0, 0);
        transform: translate3d(-25px, 0, 0)
    }
    75% {
        -webkit-transform: translate3d(10px, 0, 0);
        -ms-transform: translate3d(10px, 0, 0);
        transform: translate3d(10px, 0, 0)
    }
    90% {
        -webkit-transform: translate3d(-5px, 0, 0);
        -ms-transform: translate3d(-5px, 0, 0);
        transform: translate3d(-5px, 0, 0)
    }
    100% {
        -webkit-transform: none;
        -ms-transform: none;
        transform: none
    }
}
.bounceInRight {
    -webkit-animation-name: bounceInRight;
    animation-name: bounceInRight
}

@-webkit-keyframes bounceOutLeft {
    20% {
        opacity: 1;
        -webkit-transform: translate3d(20px, 0, 0);
        transform: translate3d(20px, 0, 0)
    }
    100% {
        opacity: 0;
        -webkit-transform: translate3d(-2000px, 0, 0);
        transform: translate3d(-2000px, 0, 0)
    }
}
@keyframes bounceOutLeft {
    20% {
        opacity: 1;
        -webkit-transform: translate3d(20px, 0, 0);
        -ms-transform: translate3d(20px, 0, 0);
        transform: translate3d(20px, 0, 0)
    }
    100% {
        opacity: 0;
        -webkit-transform: translate3d(-2000px, 0, 0);
        -ms-transform: translate3d(-2000px, 0, 0);
        transform: translate3d(-2000px, 0, 0)
    }
}
.bounceOutLeft {
    -webkit-animation-name: bounceOutLeft;
    animation-name: bounceOutLeft
}


@-webkit-keyframes bounceOutRight {
    20% {
        opacity: 1;
        -webkit-transform: translate3d(-20px, 0, 0);
        transform: translate3d(-20px, 0, 0)
    }
    100% {
        opacity: 0;
        -webkit-transform: translate3d(2000px, 0, 0);
        transform: translate3d(2000px, 0, 0)
    }
}
@keyframes bounceOutRight {
    20% {
        opacity: 1;
        -webkit-transform: translate3d(-20px, 0, 0);
        -ms-transform: translate3d(-20px, 0, 0);
        transform: translate3d(-20px, 0, 0)
    }
    100% {
        opacity: 0;
        -webkit-transform: translate3d(2000px, 0, 0);
        -ms-transform: translate3d(2000px, 0, 0);
        transform: translate3d(2000px, 0, 0)
    }
}
.bounceOutRight {
    -webkit-animation-name: bounceOutRight;
    animation-name: bounceOutRight
}

/*the animation definition*/
@-webkit-keyframes fadeIn {
    0% {
        opacity: 0
    }
    100% {
        opacity: 1
    }
}
@keyframes fadeIn {
    0% {
        opacity: 0
    }
    100% {
        opacity: 1
    }
}
.fadeIn {
    -webkit-animation-name: fadeIn;
    animation-name: fadeIn
}

사이트 들어가기 귀찮으면 이거 복붙하면 됨!

pages/Main.js 수정

import React, { Component } from 'react';
import Join from 'components/Join';
import Login from 'components/Login';
import '../Animation.css'
import queryString from "query-string";
import 'style/Main.scss';
import leftImg from '/home/linesys/OLLOC/src/img/main.png'

class Main extends Component {
    state = {
        direction: 'animated fadeIn'
    }

    loginsplit = (query) => {
        if(query === 'login') return false;
        else return true;
    }

    move = (getDirection) => {  // 자식 컴포넌트에서 값을 가져옴
        this.setState({
            direction: getDirection,
        })
    }
    render() {
        const {location} = this.props;
        const query = queryString.parse(location.search);
        const detail = this.loginsplit(query.detail);

        return (
            <div id = "main">
                <img className = "animated fadeIn" src={leftImg} alt="이미지"/>  // 이미지도 초기 로드시 fadeIn효과로 불러지게 함
                {
                    detail
                        ? <Join
                            animation={this.move}
                            direction={this.state.direction}
                        />
                        : <Login
                                animation={this.move}
                                direction={this.state.direction}
                        />
                }
            </div>
        );
    };
}

export default Main;

사용법은 간단하게 class명에 animated와 ‘적용시킬 애니메이션 이름’을 추가해주면 된다.
페이지 새로고침을 했을 때 이전에 애니메이션이 중복되서 나오는 것을 방지하기 위해
direction을 fadeIn으로 초기화 시켰다.
join, login 서로 컴포넌트가 전환될 때 어떤 애니메이션을 불러올지 통신을 위해 animation, direction props를 전달해준다.

components/Join.js 수정

import React, {Component} from "react";
import "../Animation.css"
import { Redirect } from 'react-router-dom';

class Join extends Component{

    changeView = () => {
        this.setState({
            animation: 'animated bounceOutLeft'
        });
        this.props.animation('animated bounceInRight');
        setTimeout(()=>{
            this.setState({
                redirect: true
            })
        }, 300);
    }

    constructor(props){
        super(props);
        this.state ={
            animation: this.props.direction,
            redirect: false,
        }
    }

    render(){ 
        if(this.state.redirect){
            return <Redirect push to ='main/?detail=login' />;
        }
        return(
            <div id ="join">
                <form id="loginForm" className={this.state.animation}>
                    <h1>OLLoc</h1>
                    <span className="loginText">친구들의 지도에 그려진 사진과 글을 보려면 가입하세요</span>
                    <button className="mainBtn">facebook으로 로그인</button>
                    <div id="line">또는</div>
                    <input
                        className="textInput"
                        placeholder="휴대폰 번호 또는 이메일 주소"
                    />
                    <input
                        className="textInput"
                        placeholder="성명"
                    />
                    <input
                        className="textInput"
                        placeholder="사용자 이름"
                    />
                    <input
                        className="textInput"
                        placeholder="비밀번호"
                    />
                    <button className="mainBtn">가입</button>
                    <span className="loginText">가입하면 OLLoc의 약관, 데이터 정책 및 쿠키 정책에 동의하게 됩니다.</span>
                </form>
                <div id="checkMem" className={this.state.animation}>
                    계정이 있으신가요? <span id = "loginBtn" onClick={this.changeView} >로그인</span>
                </div>
            </div>
        );
    }
}

export default Join;

부모에게 받은 props.direction으로 state.animation을 초기화한다.
#loginForm -> div안에있는 form 태그에 state.animation을 className으로 정해줘
애니메이션 효과를 준다.
이렇게 하면 초기 로드시 fadeIn 효과를 줄 수 있다.
이어서 하단의 ‘<span id = “loginBtn” onClick={this.changeView} >로그인</span>’ 을 클릭하면 changeView라는 함수를 통하게 된다.

먼저 현재 state.animation을 bounceOutLeft로 변경하여 re-rendering 되며 왼쪽으로 사라지는 애니메이션을 주게된다.
그리고 부모 컴포넌트로부터 받은 props.animation 함수를 통해 매개변수로 bounceInRight를 줬다. 이렇게 하면 부모가 Join.js의 형제(?)인 Login.js에게 bounceInRight를 줄 것이고 Login Component가 오른쪽에서부터 왼쪽으로 나타나는 애니메이션을 주며 뷰가 전환될 것이다. (새로고침 없이! react-router!)
그리고 이어서 setTimeout이 있다 사실 componentWillUnmount 메소드를 통해
애니메이션을 주려 했으나 저기에서 state를 변경해도 새로운 DOM이 나타나 버려서
애니메이션이 진행될 시간인 300ms의 딜레이 후 /main/?detail=login로 리다이렉트 하게 했다.

state.redirect가 true가 되며 render(){if(this.state.redirect)}를 통해 리다이렉팅 되게 된다.

components/Login.js수정

import React, {Component} from 'react';
import { Redirect, Link } from 'react-router-dom';

class Login extends Component{
    changeView = () => {
        this.setState({
            animation: 'animated bounceOutRight'
        });

        this.props.animation('animated bounceInLeft');

        setTimeout(()=>{
            this.setState({
                redirect: true
            })
        }, 300);
    }

    constructor(props){
        super(props);
        this.state ={
            animation: this.props.direction,
            redirect: false,
        }
    }

    render(){
        if(this.state.redirect){
            return <Redirect push to ='/main' />;
        }
        return(
            <div id ="login">
                <form className={this.state.animation} id="loginForm">
                    <span id="prevBtn" onClick={this.changeView}><i className="fas fa-arrow-left"></i></span>
                    <h1 id="mainTitle">OLLoc</h1>
                    <span className="loginText">친구들의 지도에 그려진 사진과 글을 보려면 가입하세요</span>
                    <button className="mainBtn">facebook으로 로그인</button>
                    <div id="line">또는</div>
                    <input
                        className="textInput"
                        placeholder="휴대폰 번호 또는 이메일 주소"
                    />
                    <input
                        className="textInput"
                        placeholder="비밀번호"
                    />
                    <Link to ="/" className="noUnderLine" ><button className="mainBtn">로그인</button></Link>
                    <span className="loginText">가입하면 OLLoc의 약관, 데이터 정책 및 쿠키 정책에 동의하게 됩니다.</span>
                </form>
            </div>
        );
    }
}

export default Login;

‘components/Join.js’ 와 같은 원리로 작동한다.

스타일을 주자! ‘style/Main.scss’ 작성

sass는 기존 css에 변수와 조건문, 반복문 등 기존의 css를 확장시켜 좀 더 편리하게
해주는 스타일시트 언어다.
사용하기 위해서는 ‘node-sass’라는 라이브러리가 필요하다. 웹브라우져가 기존의 css코드로 이해할 수 있도록 컴파일 해주는 역할을 한다.

$ yarn add node-sass

이제 코드를 작성하자.

#main{
  text-align: center;
  img{
    width: 389px;
    height: 618px;
    vertical-align: top;
    display: inline-block;
    z-index: 1;
  }

  #login {
    #prevBtn{font-size: 30px; float: left; display: inline-block; margin-left: 10px;}
    #mainTitle{display: inline-block; text-align: center; margin-left: -35px; }
  }

  #join, #login{
    overflow: hidden;
    display: inline-block;
    width: 400px;

    #loginForm {
      display: inline-block;
      margin: 0 auto;
      background-color: #fff; border: solid 1px #E6E6E6; border-radius: 1px; padding: 10px 0; width: 348px; height: 524px;
      #line{
        color: #999999; font-size: 0.8rem; text-weight: bold; margin-bottom: 20px;
        &:before{
          content: "";
          display: inline-block;
          height:1px; width: 110px; border-bottom: solid 1px #999999;
          margin-right: 15px;
          margin-bottom: 3px;
        }
        &:after{
          content: "";
          display: inline-block;
          height:1px; width: 110px; border-bottom: solid 1px #999999;
          margin-left: 15px;
          margin-bottom: 3px;
        }
      }
      .textInput {
        width: 264px; height: 34px; background-color: #F9F9F9; padding: 0 5px; border: solid 1px #EBEBEB; margin-bottom: 5px;
      }

      .noUnderLine{text-decoration: none;}
      .mainBtn{
        display: block;
        text-align: center;
        text-decoration: none !important;
        margin: 15px auto;
        background-color: #3897F0;
        color: white;
        width: 264px;
        height: 34px;
        border-radius: 5px;
        font-size: 1rem;
        font-weight: bold;
        line-height: 30px;
      }

      .loginText{margin: 0 40px; display: block; color: #999999; font-weight: bold;}
    }
  }

  #checkMem {
    margin : 10px auto;
    background-color: #fff;
    width: 348px;
    height: 48px;
    padding: 10px 0;
    border: solid 1px #E6E6E6;
    border-radius: 1px;
    text-align: center;
    line-height: 48px;


    #loginBtn{
      color: blue;
      text-decoration: underline;
      &:hover{
        cursor: pointer;
      }
    }
  }
}

기존 css를 아는 사람 기준에서 너무나도 직관적으로 이해할 수 있기 때문에 문법에
대한 설명은 생략한다. (사실 나도 잘 모르고 직관대로 쓰고 있다. 나중에 정리할 예정)

#join, #login{ overflow: hidden; display: inline-block; width: 400px;}

다만 위 코드만 설명하면  상단에 이 부분은 애니메이션이 bounceIn&Out될 때
양쪽 끝에서 움직이기
때문에 제한을 걸기 위해 overflow:hidden, display: inline-block; width: 400px로
크기를 정해주고 내부 요소들이 부모 요소 밖으로 넘치는 경우를 예방했다.

결과

홈으로 접속하여 회원가입 화면으로 전환될 때 fadeIn 효과
로그인 버튼 누르고 이전 버튼을 눌렀을 때 bounceIn&Out 효과
그리고 다시 새로고침을 했을 때 fadeIn 효과가 나오는 것을 확인할 수 있다.

Python(Flask)+React+SASS+mongoDB로 웹 서비스 제작하기 프로젝트(2-3) – Login Component 만들기(Animation, SASS)”의 1개의 생각

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다