트윈맥스와 스크롤트리거 리액트에서 사용하기

|

트윈맥스 리액트에서 사용하기

나중에 참고용으로 쓰일 자료라 코드만 복붙해놓겠습니다.

import React, { useRef, useEffect } from 'react';
import styled from 'styled-components';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';

import image9 from '../../assets/about-image/9.jpg';
import image10 from '../../assets/about-image/10.jpg';
import image11 from '../../assets/about-image/11.jpg';
import image11_11 from '../../assets/about-image/11-11.jpg';
import image12 from '../../assets/about-image/12.jpg';
import image20 from '../../assets/about-image/20.jpg';
import image21 from '../../assets/about-image/21.jpg';
import image22 from '../../assets/about-image/22.jpg';
import image23 from '../../assets/about-image/23.jpg';
import image24 from '../../assets/about-image/24.jpg';

gsap.registerPlugin(ScrollTrigger);

const Container = styled.div`
  overflow-x: hidden;
  font-family: 'Quicksand', sans-serif;
`;

const Section = styled.section`
  position: relative;
  width: 100vw;

  &.top {
    min-height: 300vh;

    .about-contents {
      display: flex;
      align-items: flex-end;
      padding: 1rem;
      margin-top: 20rem;
      img {
        width: 500px;
        height: 579px;
      }
      .about-text {
        margin-left: 1rem;
        .about-title {
          line-height: 90%;
          font-weight: bold;
          margin-bottom: 1rem;
          font-size: 6vw;
        }

        .about-desc {
          margin-top: 3rem;
          font-size: 2.5vw;
          font-weight: 400;
        }
      }
    }

    .title {
      position: relative;
      color: black;
      text-align: center;
      padding-top: 20vh;
      font-size: 6vw;
      font-weight: 400;
      div {
        display: inline-block;
        letter-spacing: -7px;
      }
      .space {
        margin-right: 1rem;
      }
    }
  }

  &.bottom {
    min-height: 60vh;

    .contWrap {
      margin: 0 auto;
      width: 100%;
      max-width: 700px;

      ul {
        li {
          display: inline-block;
          margin: 0 2%;
          width: 130px;
          height: 200px;
          border-radius: 6px 6px 6px 6px;
          padding: 10px;
          cursor: pointer;

          p {
            font-size: 20px;
            color: #fff;
            border-bottom: 2px dashed #fff;
          }

          &:nth-child(1) {
            background: linear-gradient(45deg, #f7b733, #fc4a1a);
          }
          &:nth-child(2) {
            background: linear-gradient(45deg, #fc00ff 0%, #401241 100%);
          }
          &:nth-child(3) {
            background-image: linear-gradient(45deg, #ce713b 0%, #f7ce68 100%);
          }
          &:nth-child(4) {
            background-image: linear-gradient(
              45deg,
              #fa8bff 0%,
              #2bd2ff 52%,
              #2bff88 90%
            );
          }

          transition: all 1s cubic-bezier(0.075, 0.82, 0.165, 1);
          &:hover {
            transform: translateY(-20px);
          }
        }
      }
      h2 {
        text-align: center;
        color: black;
        margin-top: 30px;
        font-weight: 100;
      }
    }
  }

  .topBtn {
    position: absolute;
    bottom: 10%;
    right: 70px;
    padding: 10px;
    border-radius: 6px 6px 6px 6px;
    background-color: #eee;
    border: 2px solid #eee;
    transition: all 0.3s ease-out;
    cursor: pointer;

    &:hover {
      background-color: #000;
      border-color: #333;
      color: #fff;
    }
  }
`;

const sections = [
  {
    title: 'V',
  },
  {
    title: 'I',
  },
  {
    title: 'N',
  },
  {
    title: 'T',
  },
  {
    title: 'A',
  },
  {
    title: 'G',
  },
  {
    title: 'E',
    className: 'space',
  },

  {
    title: 'V',
  },

  {
    title: 'E',
  },

  {
    title: 'L',
  },
  {
    title: 'L',
  },
  {
    title: 'A',
  },
];

const About = () => {
  const titleRef = useRef();
  const contentsRefs = useRef([]);
  const revealRefs = useRef([]);
  revealRefs.current = [];
  contentsRefs.current = [];

  useEffect(() => {
    //스크롤 이벤트
    window.addEventListener('scroll', function (event) {
      // scrollTop = document.documentElement.scrollTop;
      var scroll = window.scrollY;
      // starBg.style.transform = 'translateY(' + -scroll / 3 + 'px)';
      titleRef.current.style.transform = 'translateY(' + scroll / 1.7 + 'px)';
    });

    //텍스트 모션
    revealRefs.current.forEach((el, index) => {
      gsap.from(el, {
        autoAlpha: 0,
        delay: Math.random() * 1,
        ease: 'power3.easeInOut',
      });
    });

    contentsRefs.current.forEach((el, index) => {
      gsap.fromTo(
        el,
        {
          autoAlpha: 0,
        },
        {
          duration: 1,
          autoAlpha: 1,
          ease: 'none',
          scrollTrigger: {
            id: `section-${index + 1}`,
            trigger: el,
            start: 'top center+=100',
            toggleActions: 'play none none reverse',
          },
        }
      );
    });
  }, []);

  // div들 집어넣음
  const addToRefs = (el) => {
    if (el && !revealRefs.current.includes(el)) {
      revealRefs.current.push(el);
    }
  };

  // about-contents들 집어넣음
  const contentsAddToRefs = (el) => {
    if (el && !contentsRefs.current.includes(el)) {
      contentsRefs.current.push(el);
    }
  };

  return (
    <Container>
      <Section className='top'>
        <h1 className='title' ref={titleRef}>
          {sections.map((title, index) => (
            <div
              ref={addToRefs}
              key={index}
              className={title.className && 'space'}
            >
              {title.title}
            </div>
          ))}
        </h1>

        <div className='about-contents' ref={contentsAddToRefs}>
          <img src={image23} alt='' />
          <div className='about-text'>
            <p className='about-title'>For Your Conscious Closet.</p>

            <p className='about-desc'>Sustainable Supply, Selling Ethically.</p>
          </div>
        </div>
      </Section>

      <Section className='bottom'>
        <div className='contWrap'>
          <ul>
            <li>
              <p>card</p>
            </li>
            <li>
              <p>card</p>
            </li>
            <li>
              <p>card</p>
            </li>
            <li>
              <p>card</p>
            </li>
          </ul>
          <h2>별이 쏟아지는, 인터랙티브</h2>
        </div>
        <button button='type' className='topBtn'>
          TOP
        </button>
      </Section>
    </Container>
  );
};

export default About;

함수 호이스팅

|

함수 호이스팅

자바스크립트의 Guru로 알려진 더글러스 크락포드는 함수 표현식만을 사용할 것을 권하고 있는데, 이는 함수 호이스팅때문이다.

함수 표현식이란?
var add = function (){} 이런 형태의 함수식을 말한다.


add(2,3) // 5      (1)

// 함수 선언문 형태로 add() 함수 정의    (2)
function add(x,y){
    return x + y;
}

add(3,4); // 7   (3)


위의 코드에서 add()함수가 정의되지 않았음에도 (2)에 정의된 add()함수를 호출하는 것이 가능하다.
이것은 함수가 자신이 위치한 코드에 상관없이 함수 선언문 형태로 정의한 함수의 유효 범위는 코드의 맨 처음부터 시작한다는 것을 확인 할 수 있다. 이것을 함수 호이스팅이라고 부른다.



add(2,3) // uncaught type error      (1)

// 함수 표현식 형태로 add() 함수 정의    (2)
var add = function add(x,y){
    return x + y;
}

add(3,4); // 7   (3)


함수 표현식 형태로 정의되어 있어 호이스팅이 일어나지 않는다.

호이스팅의 발생원인

함수 호이스팅이 발생하는 원인은 자바스크립트의 변수 생성초기화의 작업이 분리돼서 진행되기 때문이다.

….추후 계속 정리할 예정

몽고DB exec(),find(),findById()차이

|

몽고DB exec(),find()와 findById() 차이

exec()참고자료

exec()

  • 쿼리를 프로미스(비동기)처리를 해주기 위해서 사용함 몽고4버전부턴 생략가능 하다고는 하는데, 그래도 써주는거를 추천함
  • async/await로 대체 가능하다.

find()와 findById()

  • find는 조건에 해당하는 모든것을 처리해주고, findById는 조건에 해당하는 id를 찾아준다고 한다.

  • 1개 이상의 문서를 반환 할 수 있는 find()와는 달리, findById()는 0개 또는 한개의 문서만 반환 할 수 있다.

  • id의 스키마가 ObjectId 유형이 아닌 경우 function : findbyId ()로 작업 할 수 없다.

더보기 버튼 만들기

|

LIMIT와 SKIP

LIMIT과 SKIP은 몽고DB의 메소드 이다.

LIMIT

처음 데이터를 가져올때와 더보기 버튼을 눌러서 가져올때 최대 얼만큼의 데이터를 가져올지 정해주는 메소드이다.

SKIP

어디서부터 데이터를 가져오는지에 대한 위치
EX)처음에는 0부터 시작, Limit이 6이라면, 다음번에는 2rd Skip = 0 + 6



실제로 해보기

Front 코드

import React, { useEffect, useState } from 'react';
import { FaCode } from 'react-icons/fa';
import axios from 'axios';
import { Icon, Col, Card, Row, Button, Carousel } from 'antd';
import Meta from 'antd/lib/card/Meta';
import ImageSlider from '../../utils/ImageSlider';
function LandingPage() {
  const [Products, setProducts] = useState([]);
  const [Skip, setSkip] = useState(0);
  const [Limit, setLimit] = useState(8);
  const [PostSize, setPostSize] = useState(0);

  useEffect(() => {
    let body = {
      skip: Skip,
      limit: Limit,
    };

    getProducts(body);
  }, []);

  const getProducts = (body) => {
    axios.post('/api/product/products', body).then((response) => {
      if (response.data.success) {
        console.log(response.data.productInfo);
        if (body.loadMore) {
          // 더보기 버튼이 클릭됬을시
          setProducts([...Products, ...response.data.productInfo]);
        } else {
          setProducts(response.data.productInfo);
        }
        setPostSize(response.data.PostSize);
      } else {
        alert('상품들을 가져오는데 실패 했습니다.');
      }
    });
  };

  const loadMoreHandler = () => {
    let skip = Skip + Limit;

    let body = {
      skip: Skip,
      limit: Limit,
      loadMore: true,
    };

    getProducts(body);
    setSkip(skip);
  };

  const renderCards = Products.map((product, index) => {
    return (
      <Col lg={6} md={8} xs={24} key={index}>
        <Card cover={<ImageSlider images={product.images} />}>
          <Meta title={product.title} description={`$${product.price}`} />
        </Card>
      </Col>
    );
  });

  return (
    <div style=>
      <div style=>
        <h2>
          Let's Travel Anywhere
          <Icon type='rocket' />
        </h2>
      </div>

      {/* Filter */}

      {/* Search */}
      <Row gutter={[16, 16]}>{renderCards}</Row>

      <br />

      {PostSize >= Limit && (
        <div style=>
          <Button onClick={loadMoreHandler}>더보기</Button>
        </div>
      )}
    </div>
  );
}

export default LandingPage;

PostSize를 Limit과 비교하여 더보기 버튼의 모습을 보여줄지 말지 정해준다.
PostSize는 서버쪽에서 받아오는 전체 게시글의 갯수다. (배열로 넘어와짐)

Server 코드

router.post('/products', (req, res) => {
  // product collection에 들어 있는 모든 상품 정보를 가져오기

  let limit = req.body.limit ? parseInt(req.body.limit) : 20;
  let skip = req.body.skip ? parseInt(req.body.skip) : 0;

  Product.find()
    .populate('writer')
    .skip(skip)
    .limit(limit)
    .exec((err, productInfo) => {
      if (err) return res.status(400).json({ success: false, err });

      return res
        .status(200)
        .json({ success: true, productInfo, postSize: productInfo.length });
    });
});



비교적 간단한 코드들로 구성되어있고, 완벽한 코드는 아니다.
강의의 질문들을 보면 몇개의 허점들이 있는듯하지만, 초보자가 원리를 파악하는데에 도움이 되는것 같고 이런식으로 적용을 해보고 스스로 허점들을 고쳐나가는 방향으로 나아간다면 이것이야 말로 폭풍성장의 비결이 아닐까?

grid 레이아웃, flex:1 핵심 정리

|

gird와 flex를 활용한 반응형 레이아웃 구현하기를 보고 나중에 잊어먹더라도 기억을 되새김질하고자 핵심만을 정리했습니다.

width 조정

  /* 1240px를 넘어가는 브라우저에서 보면, max1240px로 보여주고, */
  /* 1240보다 더 적어지면 적어진 너비의 90%를 너비로쓰게끔 세팅 */
  width: 90%;
  max-width: 1240px;

미디어 쿼리

/* 핸드폰 */
@media screen and (min-width: 500px) {
  .band {
    grid-template-columns: 1fr 1fr;
  }

  .item-1 {
    grid-column: 1/3;
  }
}

/* 테블릿 */
@media screen and (min-width: 850px) {
  .band {
    /* grid-template-columns: 1fr 1fr 1fr 1fr; */
    grid-template-columns: repeat(4, 1fr);
  }
}

flex:1 속성

flex:1은 flex: 1 1 0; 속성의 줄임말로,요소를 채워준다고 생각하면 된다.
순서대로,첫번째 속성은 넘치는걸 채워주고 두번째 속성은 모자란걸 늘려준다.
주로 짧든 길든 컨텐츠 높이(길이)를 꽉 채워주고 싶을때 사용한다.