기본 스티커 메모를 아시나요?

기본스티커 메모

헤더 부분을 드래그하면 이동되는 것과 메모 입력하는 기능을 흉내내보았는데요.

제 사이트에는 딱 저 2가지 기능만 필요하여 저 부분만 작업했습니다! 

 

Memo.js

import React, { useCallback, useRef, useState } from 'react';
import styled from 'styled-components';

const Wrap = styled.div`
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 200px;
    background: yellow;
    border: 1px solid #c0c0a4;
    box-sizing: border-box;
`;
const Header = styled.div`
    padding: 5px;
    width: 100%;
    font-size: 14px;
    font-weight: 700;
    color: yellow;
    background: #c0c0a4;
    text-align: center;
`;
const Content = styled.div`
    padding: 5%;
    max-height: 300px;
    overflow-y: auto;

    textarea {
        padding: 5%;
        width: 100%;
        min-height: 150px;
        box-sizing: border-box;
        background: none;
        outline: none;
        border: none;
    }
`;

const Memo = () => {
    const [memoText, setMemoText] = useState('');
    const wrapRef = useRef(null);
    const headerRef = useRef(null);
    let lastX = 0;
    let lastY = 0;
    let startX = 0;
    let startY = 0;

    const onFocusout = useCallback(({ target }) => {
        setMemoText(target.value);
    }, [memoText]);

    const onMove = useCallback((e) => {
        e.preventDefault(); 
        lastX = startX - e.clientX;
        lastY = startY - e.clientY;

        startX = e.clientX;
        startY = e.clientY;

        wrapRef.current.style.top = `${wrapRef.current.offsetTop - lastY}px`;
        wrapRef.current.style.left = `${wrapRef.current.offsetLeft - lastX}px`;
    }, []);

    const removeEvent = useCallback(() => {
        document.removeEventListener('mouseup', removeEvent);
        document.removeEventListener('mousemove', onMove);
    }, []);

    const onMouseDown = useCallback((e) => {
        e.preventDefault(); 
        startX = e.clientX;
        startY = e.clientY;

        document.addEventListener('mouseup', removeEvent);
        document.addEventListener('mousemove', onMove);
    }, []);

    return (
        <Wrap ref={wrapRef}>
            <Header 
                ref={headerRef}
                onMouseDown={onMouseDown}
            >
                Memo
            </Header>
            <Content>
                <textarea 
                    placeholder="메모를 입력해주세요..."
                    onBlur={onFocusout}    
                ></textarea>
            </Content>
        </Wrap>
    );
};

export default Memo;

포커스가 빠져나갈때 value값을 저장하도록 해두었습니다.

 

아! 코드는 js에서 사용하던 그대로 사용했으며.. ↓

okayoon.tistory.com/entry/%EB%A7%88%EC%9A%B0%EC%8A%A4%EB%A1%9C-%EC%B0%BD%EC%9D%84-%EC%9B%80%EC%A7%81%EC%97%AC%EB%B3%B4%EC%9E%90?category=835827

 

ref를 통해 style 값을 넣는 부분을 안하고 싶지만..

useState를 사용할 경우 안되더라구요. 비동기여서 그런듯한데....

동기로 잘 사용할 방법을 찾으면 해결 가능할 것 같은데....

velog.io/@aerirang647/32setState-%EB%B9%84%EB%8F%99%EA%B8%B0

 

[+32]setState -> 비동기

동기와 비동기동기: 코드 한 줄 한 줄 끝날 때까지 기다렸다가 다음줄로 넘어가는 성질.ex. forEach비동기: 코드 한 줄이 하고 있는 일이 끝나지 않았는데 일을 시켜놓고 내려가다가 일이 끝났다는

velog.io

여튼.. 그래서 그냥 ref를 통해 style을 수정해주고있습니다.

더 좋은 방법이 있을 것 같은데 아직 찾지를 못해서...ㅠ

쪼랩에게 좋은 의견이 있으면 말해주심 감사하겠습니다.

 

완성된 모양과 동작입니다.

메모 완성!

 

 

폴더창이나 인터넷 창 등.....

보통 상단부 부분을 마우스로 꾹!누르고 움직이면 창을 이동시킬수 있잖아요?

 

이렇게요 ↓

창 움직이는 gif

 

오늘은 이 기능을 만들어 보도록 하겠습니다.!

^^

 

 

먼저 움직일 객체를 만들기위한 작업을 합니다.

HTML

<div class="wrap">
    <div class="header">
        이곳을 드래그 하세요.
    </div>
</div>

 

 

대충 모양을 꾸며줄게요.

CSS

.wrap{
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 300px;
  height: 300px;
  border: 1px solid #ddd;
  border-radius: 5px;
  box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.5);
  overflow: hidden;
}

.header{
  padding: 5px;
  text-align: center;
  font-size: 14px;
  color: #333;
  background: #ddd;
  cursor: default;
}

.header.active{
	cursor: move;
}

 

 

그다음 기능을 추가해줍니다.

JS

const wrap = document.querySelector(".wrap");
const header = document.querySelector(".header");
let lastX = 0;
let lastY = 0; 
let startX = 0; 
let startY = 0; 

// 1.
header.addEventListener('mousedown', function(e){
  e.preventDefault(); 
  startX = e.clientX; 
  startY = e.clientY; 
	
  // 2.  
  header.classList.add('active');
  
  // 3.
  document.addEventListener('mouseup', onRemoveEvent); 
  
  // 4.
  document.addEventListener('mousemove', onMove); 
});

function onRemoveEvent() { 
  header.classList.remove('active');
  document.removeEventListener('mouseup', onRemoveEvent); 
  document.removeEventListener('mousemove', onMove); 
} 

function onMove(e) { 
  e.preventDefault(); 
  lastX = startX - e.clientX; 
  lastY = startY - e.clientY; 

  startX = e.clientX; 
  startY = e.clientY; 

  wrap.style.top = `${wrap.offsetTop - lastY}px`;
  wrap.style.left = `${wrap.offsetLeft - lastX}px`;
}

1, 2, 3, 4에 대해 설명하겠습니다.

 

1. 상단부에 마우스를 꾹 눌렀을때 작동하게 하기 위해서 header 영역을 타겟으로 잡았습니다.

또한, 타겟에 마우스를 꾹 누른상태에서 움직일때 이벤트 발생하기 위해 mousedown 이벤트를 먼저 사용합니다.

이때 최초의 client 좌표 값을 변수에 담습니다.

 

2. mousedown시에 클래스(active)를 추가하여 스타일로 마우스커서를 바꾸어줍니다. 

 

3. 마우스를 꾹 누른것을 풀었을 경우(mouseup)에 객체가 이동되면 안되므로 내부에서 이벤트를 해제하는 함수를 바인딩해줍니다.

 

4. 핵심 함수입니다.

mousedown + mousemove가 함께 작동할 때 동작해야합니다.

이벤트 타겟은 header이나 움직이는 객체는 wrap이기때문에 좌표값을 구해서 wrap 객체 속성을 수정해줘야합니다.

mousedown에서 저장했던 client 좌표값에서 현재 client 좌표값을 뺀 값을 wrap 객체 스타일 속성으로 추가합니다.

 

 

완성된 모습입니다.

완성 예제

 

 

 

 

react에서 리다이렉션 시키려면 모듈을 설치하여 작업해주어야합니다.

하지만 next에서는 따로 설치 없이 간단히 사용할 수 있습니다.

 

로그인하지 않았다면 로그인 화면으로 보내는 작업을 하겠습니다.

next/router 를 통해 작업하기 위해 import를 해줍니다.

import Router from 'next/router';

 

컴포넌트가 생성될 때 실행되어야하기 때문에 useEffect를 통해 작업합니다.

import React, { useEffect } from 'react';

코드는 아래와 같습니다.

useEffect(() => {
  if(!nickname){
  	Router.replace('/login');
  }
}, [nickname]);

if문을 통해 해당 state 값이 없으면 Router.replace를 통해 login 페이지로 리다이렉션시켜줍니다.

여기서 Router.push를 이용하지 않고 replace를 이용한 것은 history를 남기지 않아 뒤로가기 버튼을 동작하지 않게 만들기 위함입니다.

 

저는 state 값으로 nickname을 사용했는데, 로그인에 관련 된 state값이면 되겠죠? 뭐 id나 useInfo등등

마지막 인자로 [ nickname ] 을 적어주면됩니다.

nickname state가 수정되면 useEffect가 다시 실행됩니다.

 

 

이렇게하면 index에 접근했을때 /login 페이지로 리다이렉트 됩니다.!!

짱간단^^

 

 

 

이번 작업은 배터리 만들기입니다.

핸드폰들 보면 몇 % 남았는지 보이는 것있쬬?

기능은 약간 다르지만 흉내내보겠습니다.

 

제가 원하는 기능은 하루를 기준으로 시, 분을 통해 얼마나 지났는 지를 체크하는 것입니다.

즉 24시가 되면 100%, 오후 12시면 50% 겠죠? 로직을 수정하면 거꾸로도 할 수 있겠죠?

(지금 중요한건 이런 기능에 대한 설명은 아니니깐요.)

 

제로초님 강의에서는 moment를 사용해서 날짜를 구해 사용했는데요,

잠깐 다른 라이브러리에 대해서도 설명해주십니다.

그 중 구글 트렌드를 보시고 요즘 많이 사용한다는 dayjs에 대해 간단한 소개를 해주시는데요.

용량이 가볍기 때문에 많이들 쓴다고 합니다.

저는 dayjs를 통해 만들어보겠습니다.

(사실 moment나 dayjs나 문서보면 비슷비슷한 듯 싶습니다.)

 

찾다보니 moment를 사용해 디지털 시계같은것을 구현한 라이브러리도 있더라구요.ㅎㅎ;

 

moment와 dayjs 대체에 관한 블로그 글

https://john015.netlify.app/moment-js%EB%A5%BC-day-js%EB%A1%9C-%EB%8C%80%EC%B2%B4%ED%95%98%EA%B8%B0

 

dayjs 라이브러리를 써봅쉬다.

https://day.js.org/en/

https://github.com/iamkun/dayjs

 

먼저 install 해주시구요.

npm i dayjs

 

코드는 아래와 같습니다.

뭔가 최적화 시킬 수 있을 것 같기도 한데.

react 공부 중이라 정확히는 아직 잘 모르겠네요.

프로젝트를 더 진행하면서 좋은 수가 떠올르길 바랍니다.ㅠ

import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import dayjs from 'dayjs';

function getCurrentPercent(time){
    const totalMin = 24 * 60;
    const currentMin = (time.format('HH') * 60) + Number(time.format('mm'));
    
    return Math.floor(100 / (totalMin / currentMin));
}

const BatteryWrapper = styled.div`
    &:before {
        margin-right: 3px;
        display: inline-block;
        content: '${props => Math.floor(props.percent)}%';
        color: ${props => props.themecolor};
    }

    .gauge {
        display: inline-block;
        width: 28px;
        height: 12px;
        border: 1px solid ${props => props.themecolor};
        border-radius:3px;
    }

    .gauge:before {
        display: block;
        content: '';
        width: ${props => props.percent}%;
        height: 100%;
        background-color: ${props => props.themecolor};
    }
`;

const Battery = ({ themecolor }) => {
    const [time, setTime] = useState(dayjs());
    const [percent, setPercent] = useState(null);
    let timerInterval = null;

    useEffect(() => {
        timerInterval = setInterval(() => {
            setTime(dayjs());
        }, 1000);

        return () => {
            clearInterval(timerInterval);
        };
    }, []);

    useEffect(() => {
        const currentPer = getCurrentPercent(time);
        
        if(percent === currentPer){
            return;
        }

        setPercent(Math.floor(currentPer));
    }, [time]);

    return(
        <BatteryWrapper themecolor={themecolor} percent={percent}>
            <span className="gauge"></span>
        </BatteryWrapper>
    );
};

Battery.propTypes = {
    themecolor: PropTypes.string,
};

Battery.defaultProps = {
    themecolor: '#333',
};

export default Battery;

 

나중에 할 일인데, 언어를 ko만 가져와서 사용할 수 있도록 설정할 수 있습니다.

(그때쯤 기억나면 추가하도록 할게요)

 

완성 이미지!

배터리 완성 이미지

 

+ Recent posts