velog 배너 생성기를 만들어 봅시다

GODORI·2019년 3월 3일
296
post-thumbnail

velog를 사용하는 여러분은 썸네일용 배너를 어떻게 만들고 계신가요?
저는 매번 배너 만들기가 귀찮아서(...) 간단한 배너 생성기를 만들었습니다.

바로 사용해보고 싶어서 필수적인 기능만 빠르게 구현했는데요, React로 스태틱 배너 생성기를 만들면서 삽질했던 내용을 기록해 보았습니다.

create-react-app

create-react-app v2로 새로운 프로젝트를 생성했습니다.

create-react-app banner-maker

State

주요하게 필요한 정보는 배너의 배경 색상, 입력한 텍스트, 다운로드할 이미지의 데이터 링크입니다.
간단한 내용이므로 Redux등을 사용하지 않고 App.js에서만 상태 관리를 하겠습니다.

  // App.js
  state = {
    color: "#ccc",
    text: "Sample Text",
    href: ""
  };

Color Picker

배너 색을 고르기 위한 컬러 피커는 react-color 라이브러리를 사용했습니다.

깔끔해 보이는 Circle을 기본으로 정하고, 좀 더 자유롭게 색상을 선택할 수 있도록 아래에 HuePicker도 추가합니다. 배경 색상만 먼저 지정할 수 있게 하고 나중에 상태를 추가해서 글자 색 변경과, hex값을 입력할 수 있게 할 것입니다.

    // App.js
    state = {
    	color: "#ccc"
    }
    handleChange = color => {
        this.setState({ color: color.hex });
      };
    
    ...
    
    <Palette color={color} onChange={this.handleChange} />
    // Palette.js
    import { SketchPicker } from 'react-color';
    
    ...
    
    <div className="paletteWrapper">
    	<div className="circlePicker">
    		<CirclePicker color={color} onChange={onChange} />
    	</div>
    	<div className="huePicker">
    		<HuePicker color={color} onChange={onChange} />
    	</div>
    </div>

hex 값과 현재 선택한 색상을 보여주는 작은 박스 하나도 만들어서 다음과 같이 배치했습니다.

Text Input

글자 입력을 위해 Input을 처리하는 컴포넌트를 하나 만들어주고, text 라는 state로 관리합니다.

// App.js
handleTextChange = e => {
    this.setState({ text: e.target.value });
  };
...
<TextInput onChange={this.handleTextChange} />
// TextInput.js
 <input className="textInput"
		onChange={this.props.onChange}
		type="text"
		size="40"
		placeholder="Type text here!" />

Canvas

color와 text 값을 얻었으니 이제 이를 canvas에 그려보겠습니다.

React로 canvas를 다루는 것은 DOM을 건드려야 해서 살짝 까다롭게 느껴집니다. 하지만 ref를 이용해 canvas의 context를 얻어 직접 조작할 수 있습니다.

이것이 이미 잘 구현된 라이브러리도 있지만, 간단한 기능만 추가할 것이므로 다른 라이브러리를 사용하지 않고 만들어 봅니다.

먼저 Preview.js 컴포넌트를 생성한 다음 React.createRef() 함수로 canvas에 접근할 수 있는 ref를 얻습니다.

// Preview.js
class Preview extends Component {
  constructor(props) {
    super(props);
    this.canvasRef = React.createRef();
  }
  
  ...
  
// render()
 <canvas ref={this.canvasRef} className="previewCanvas" width="700" height="350"/>
}

canvas에 텍스트나 색상을 그리는 과정은 다음과 같이 componentDidUpdate에서 처리했습니다. 나중에 폰트와 글씨 크기도 바꿀 예정이니 setFont라는 함수로 미리 만들어 두었습니다.

// Preview.js
componentDidUpdate() {
    const canvas = this.canvasRef.current;
    const ctx = canvas.getContext("2d");
    const { color, text } = this.props;

    ctx.fillStyle = color;
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    this.setFont(canvas, text, {
      color: "white",
      size: "40",
      font: "Arial"
    });
  }

setFont = (canvas, text, args) => {
    const ctx = canvas.getContext("2d");
    const { color, size, font } = args;
    ctx.font = `${size}px ${font}`;
    ctx.fillStyle = color;
    ctx.fillText(text, canvas.width / 2, canvas.height / 2);
  };
// App.js
// render()
<Preview color={color} text={text} href={href} />

이제 글자를 입력하거나 색상이 변경되면 componentDidUpdate에서 canvas가 변경됩니다.

참고
Techniques for Canvas in React
https://reactjs.org/docs/refs-and-the-dom.html

글자 가운데 정렬

글자 사이즈 + 글자 크기에 따라 가운데 위치를 조절 해줘야 합니다.
텍스트 위치를 캔바스 가운데 좌표로 계산하면 (canvas.width/2, canvas.height/2) 글자 시작점은 한 가운데이긴 하지만 그림처럼 약간 어긋나게 됩니다.

텍스트 크기와 길이로 offset을 계산해야 하나 했는데 context 속성이 있어서 간단히 해결했습니다.

    context.textAlign = "center";    // 가로 가운데 정렬
    context.textBaseline = "middle"; // 세로 가운데 정렬


여기까지 구현이 되었습니다!

초기 배너 색상을 랜덤하게

초기 배경 색상을 랜덤하게 주고 싶어졌습니다. (갑자기?) 임의의 hex 색상을 뽑고 컴포넌트 마운트 후에 state의 color값을 변경해줍니다. 이제 새로고침할 때마다 배너의 색이 다르게 보입니다.

그런데 가끔 배경 색이 흰색으로 초기화 되는 것 같습니다...기억해 두었다가 고치도록 합시다. 🐒

참고: Random Hex Color Code Generator in JavaScript

    // App.js
    componentDidMount() {
        this.setState({ color: this.getRandomColor() });
      }
    
    getRandomColor = () => {
        return "#" + Math.floor(Math.random() * 16777215).toString(16);
      };

이미지 다운로드

이미지를 PNG형식으로 다운받는 버튼을 만듭니다.

canvas로부터 받은 데이터의 URL 정보와, 파일 이름을 a태그의 Attribute로 설정하면 하면 다운로드 링크를 클릭해서 이미지로 다운받을 수 있습니다.

참고: 개비스콘, 내팔자야 짤 생성기


	// href에는 이런 캔버스 데이터 URL이 들어갈 거예요.
    // const href = canvas.toDataURL();
    
	// App.js
    // render()
    <a href={href} className="downbutton" download="sample.png">Download</a>

캔바스 데이터를 다운로드 링크로 만들기

canvas에는 onChange 등의 이벤트가 없습니다. 따라서 componentDidUpdate 함수에서 캔버스에 변화가 일어나게 되면 캔버스 데이터 url로 href를 업데이트하면 됩니다.

그런데 여기서 새로운 url을 얻을 때마다 setState를 해버리면 부모인 App.js에서 일어난 업데이트 때문에 컴포넌트인 Preview.js 가 다시 업데이트 되면서 무한 반복에 빠지게 됩니다.

애초에 canvas 접근 방법이 잘못되었던 건 아닌지 한참 삽질했는데, 간단히 href !== url 조건을 추가하면 무한 업데이트를 방지할 수 있습니다. (Thanks to DoonDoony님)

이제 이미지를 다운로드 받을 데이터 링크 생성이 되었습니다. 다운로드 버튼을 클릭하면 sample.png 라는 이름으로 파일을 저장할 수 있습니다.

// App.js
handleCanvasChange = href => {
    this.setState({ href });
};
// render()
<Preview color={color}
         text={text}
         href={href}
         updateCanvas={this.handleCanvasChange} />
// Preview.js
componentDidUpdate() {
  const canvas = this.canvasRef.current;
  const ctx = canvas.getContext("2d");
  const { color, text, updateCanvas, href } = this.props;

  ctx.fillStyle = color;
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  this.setFont(canvas, text, {
    color: "white",
    size: "40",
    font: "Arial"
  });

  const url = canvas.toDataURL();
  href !== url && updateCanvas(url);
}

SASS 설정

react-create-app v2에서는 sass가 지원된다고 하니 이참에 sass도 써보기로 했습니다. sass를 사용하기 위해 App.css 파일을 App.scss로 변경하면 node-sass 모듈을 먼저 설치해야 한다고 나옵니다.

npm install node-sass

sass는 다음에 좀 더 깊이 파기로 하고 필요한 문법 몇개만 적용해보고 넘어갑니다.
font-face로 글꼴을 하나 추가해주고, 다운로드 버튼에 hover할 때 shade를 주는 등...

$primaryColor: salmon;

@font-face {
  font-family: "SF Pro";
  font-weight: 200;
  src: url("fonts/SF-Pro-Display-Light.otf");
}

@function shade($color) {
  @return mix(black, $color, 30%);
}

.downbutton {
  width: 200px;
  height: 50px;
  color: #fff;
  background-color: $primaryColor;
  margin: 5px;
  font-size: 1.3em;
  font-weight: 200;
  text-decoration: none;
  line-height: 50px;
  border-radius: 10px;
  transition: 0.3s;
  &:hover,
  &:visited,
  &:active {
    background-color: shade($primaryColor);
    color: #ccc;
    text-decoration: none;
  }
}
...

기타...

헤더 타이틀과 Google Analytics같은 것도 넣어보고 싶어졌습니다.
<head>에 들어갈 내용을 쉽게 추가하기 위해 Helmet 라이브러리를 설치합니다.

구글 콘솔에 가서 새로운 GA속성을 추가하고 GA추적 코드(gtag.js)를 복사해 둡니다. 타이틀과 gtag.js를 추가하다 보면, 추적 코드에 즉시 실행 함수와 변수가 여러 줄로 포함되어 있으므로 {``}로 감싸줍니다. head 다음에 추가하라고 되어 있는 스크립트는 <Helmet> 다음에 넣어주었습니다.

  <Helmet>
    <meta charSet="utf-8" />
    <title>Banner Maker</title>
    <script> <!-- [ gtag.js ] --> </script>
  </Helmet>

  <script>
    {` 긴 스크립트 태그는 이런 식으로 넣어줍니다.`}
  </script>

필수 사항은 아니라 생략했지만, 글꼴과 글자 크기를 선택할 수 있도록 Ant Design UI를 설치하고 각각 Select 박스를 넣어 추가로 작업해 주었습니다.

끝! 이제 배포만 하면 됩니다.

배포하기

요즘 Github pages 대신에 Netlify를 사용해보고 있습니다. Github 계정으로 로그인 한 후에 저장소와 연결만 하면 바로 스태틱 사이트를 배포할 수 있습니다. 마침 .dev 도메인도 샀겠다, 커스텀 도메인 연결도 해주었습니다.

와우!


최소한의 기능을 하는 앱 하나를 완성했습니다. 만들다보니 배경 이미지나 로고 업로드도 하고 싶고, App에 때려넣었던 코드도 점점 길고 복잡해져서 많은 수정과 개선이 필요할 것 같습니다...🙊

profile
잡식개발

56개의 댓글

comment-user-thumbnail
2019년 3월 3일

글 잘 봤습니다!!! 굉장히 멋진 어플리케이션입니다!

1개의 답글
comment-user-thumbnail
2019년 3월 7일

안녕하세요! 포스팅 잘 봤습니다
제일 마지막 데모 이미지는 어떤 프로그램으로 만드신건지 궁금해요

1개의 답글
comment-user-thumbnail
2019년 3월 7일

훌륭합니다. dev 도메인도 멋있다고 생각하고 있었는데 역시나 멋있네요!

1개의 답글
comment-user-thumbnail
2019년 3월 8일

Netlify 신세계네요! 좋은 정보 감사해요!

1개의 답글
comment-user-thumbnail
2019년 3월 10일

오우... 멋져용!!

답글 달기
comment-user-thumbnail
2019년 3월 14일

와 필요에 의해서 이런거 바로 바로 만드는거 너무 멋집니당 ~~

답글 달기
comment-user-thumbnail
2019년 4월 5일

와우 GOOD 입니다. 짱짱짱👍👍👍
다른 사이트에 이 서비스를 소개해도 될까요?

1개의 답글
comment-user-thumbnail
2019년 4월 12일

잘 사용하고 있습니다. 감사합니다!

1개의 답글
comment-user-thumbnail
2019년 8월 27일

잘쓸게요 ㅎㅎ

1개의 답글
comment-user-thumbnail
2019년 12월 23일

엄청 좋아요!!
잘쓸께요~ 감사해요!

1개의 답글
comment-user-thumbnail
2020년 2월 5일

컬러를 핵사 값으로 입력가능하면 더 좋을거 같아요~!ㅎ

답글 달기
comment-user-thumbnail
2020년 2월 15일

감사합니다. 잘쓸게요!!

답글 달기
comment-user-thumbnail
2020년 3월 17일

안녕하세요! 만들어주신 배너 생성기 잘 이용하고 있습니다! ㅎㅎㅎㅎ 아래 분 말씀하신것처럼 컬러를 헥사값으로 입력할 수 있는 기능 만들어주신다면 더 좋겠습니다.! 한 시리즈는 같은 컬러로 배너를 통일하고있는데, 값을 입력을 못해서 그나마 비슷한 컬러 계속 어림잡아 매칭하고 있어서 그게 조금 어려워서요.. 암튼 잘쓰고있어요 감사합니다! ㅎㅎ

답글 달기
comment-user-thumbnail
2020년 3월 22일

썸네일 적용이 어려워서 고민하다 발견했습니다 ㅠㅠ
잘 쓰겠습니다 감사합니당!!

답글 달기
comment-user-thumbnail
2020년 4월 6일

쵝오...👍

답글 달기
comment-user-thumbnail
2020년 7월 12일

잘쓰겠습니다. 감사해요!

답글 달기
comment-user-thumbnail
2020년 8월 3일

개발자가 되고싶어서 공부중인 개발자 지망생입니다. 썸네일 만들기가 너무 힘들었는데 감사합니다 :)

답글 달기
comment-user-thumbnail
2020년 12월 5일

벨로그 작성하면서 요런 건 없을까 했는데 역시나 있었네요 ㅎㅎ 감사합니다~ 잘 이용하겠습니다.

답글 달기
comment-user-thumbnail
2020년 12월 17일

짱이에요 ... WOW

답글 달기
comment-user-thumbnail
2020년 12월 21일

감사해요~ 잘 쓰겠습니다!!!

답글 달기
comment-user-thumbnail
2021년 1월 10일

감사히 잘 쓰겠습니다!!

답글 달기
comment-user-thumbnail
2021년 1월 13일

감사합니다! 너무 잘쓰고있어요ㅠㅠ

답글 달기
comment-user-thumbnail
2021년 1월 26일

감사합니다 잘쓸게요 😀

답글 달기
comment-user-thumbnail
2021년 3월 4일

벨로그 시작하면서 썸네일 어떡할까 고민했는데 이렇게 좋은 프로그램을!ㅎㅎ
감사합니다 잘 쓰겠습니다!

답글 달기
comment-user-thumbnail
2021년 3월 21일

진심으로 너무너무 감사합니다!!! 정말 천사셔요!!!
글에도 아주 소박하지만.... 추천글을 올려두었습니다 감사히 잘 사용하겠습니다!!!

답글 달기
comment-user-thumbnail
2021년 4월 11일

와! 감사합니다! 잘 사용하겠습니다!

답글 달기
comment-user-thumbnail
2021년 5월 1일

배너에 글 이미지가 나왔는데 덕분에 해결됐습니다!!
감사합니다 ㅎㅎ

답글 달기

우와 너무 대단하십니다 잘 이용하도록 하겠습니다! 감사합니다!

답글 달기
comment-user-thumbnail
2021년 6월 26일

감사히 잘쓰겠습니다!!!

답글 달기
comment-user-thumbnail
2021년 8월 4일

감사합니다 잘 쓰겠습니다!

답글 달기
comment-user-thumbnail
2021년 9월 3일

다른 썸네일 메이커도 많이 봤는데 이게 제일 심플하고 깔끔하네요.
또 폰트 설정을 따로 할 수 있어서 좋습니다!

답글 달기
comment-user-thumbnail
2021년 9월 24일

감사히 잘 쓰겠습니다! 너무 편하고 좋네요!! ㅎㅎ

답글 달기
comment-user-thumbnail
2021년 11월 18일

그동안 귀찮아서 썸네일 안하다가 검색해봤는데,, 너무 감사해요!!

답글 달기
comment-user-thumbnail
2021년 12월 23일

잘 사용할게요! 감사합니다~

답글 달기
comment-user-thumbnail
2022년 1월 16일

잘 사용하겠습니다! 감사합니다

답글 달기
comment-user-thumbnail
2022년 2월 7일

This is something useful and I'm going to try. Thank you for sharing.

답글 달기
comment-user-thumbnail
2022년 2월 9일

간결하고 멋진 어플리케이션 감사합니다!
너무 멋있네요...!
감사히 잘 사용하겠습니다

답글 달기
comment-user-thumbnail
2022년 4월 6일

잘 쓰고 있습니다!! 너무 멋있어요 : )

답글 달기
comment-user-thumbnail
2022년 6월 3일

와 진짜 미쳤네요 ㅎㅎㅎ 잘 쓰겠습니다 !

답글 달기
comment-user-thumbnail
2022년 10월 3일

감사합니다!

답글 달기
comment-user-thumbnail
2022년 11월 15일

우와 최고에요~!! 잘 쓰겠습니다!!

답글 달기
comment-user-thumbnail
2023년 3월 8일

하트 댓글남기고갑니다 ㅎㅎ 대박이에요..

답글 달기
comment-user-thumbnail
2023년 7월 11일

감사합니다

답글 달기
comment-user-thumbnail
2023년 9월 15일

심플해서 간단하게 쓰기 너무 좋네요! 감사합니다

답글 달기
comment-user-thumbnail
2023년 10월 18일

감사합니다!!

답글 달기
comment-user-thumbnail
2024년 1월 11일

감사합니다!

답글 달기
comment-user-thumbnail
2024년 2월 11일

멋집니다! 저는 이 서비스를 이용해서 제 벨로그 썸네일 만들어서 가져다 써도 될까요? ㅎㅎ!

답글 달기