๐Ÿ”ฎ ์†Œ๋งˆ๋ฒ• ํ”„๋กœ์ ํŠธ -6 (jjal)

์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๊ณ  ์ˆ˜์ •ํ•˜๊ณ  ๋‹ค์šด๋กœ๋“œํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๋งŒ๋“ค์–ด๋ณด๊ณ  ์‹ถ์–ด์„œ

์งค๋ฐฉ์ƒ์„ฑ๊ธฐ๋ฅผ ํ•œ๋ฒˆ ๋งŒ๋“ค์–ด๋ณด์•˜๋‹ค.


#. Project Map


์ œ์ž‘๋…ธํŠธ ํ•œ๋ˆˆ์—๋ณด๊ธฐ[์ ‘๊ธฐ/ํŽผ์น˜๊ธฐ]

1. ๋ ˆ์ด์•„์›ƒ

1-1. ๋ฉ”์ธํ™”๋ฉด

jjal1

  1. ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ค๋ช…
  1. ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๋ฒ„ํŠผ๊ณผ ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ
  1. ์ด๋ฏธ์ง€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ฐฝ (canvas ์ด์šฉ)
  1. ์ด๋ฏธ์ง€์— ์‚ฝ์ž…ํ•  ํ…์ŠคํŠธ ์ฐฝ (input ์ด์šฉ)
  1. ํ…์ŠคํŠธ ํฌ๊ธฐ ์กฐ์ ˆ ์…€๋ ‰ํŠธ๋ฐ•์Šค (select ์ด์šฉ)

1-2. ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ํ™”๋ฉด

jjal2

์น˜๋ช…์ ..

1-3. ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€

jjalbang

โ€œHello world!โ€

2. ์ƒํƒœ๊ด€๋ฆฌ

const data = useSelector(state => state.jjal)

const [text, setText] = useState(data.text || 'welcome to my page')
const [imageOn, setImageOn] = useState(data.imageOn || '')
const [imageOnWidth, setImageOnWidth] = useState(data.imageOnWidth || '')
const [imageOnHeight, setImageOnHeight] = useState(data.imageOnHeight || '')
const [downloadHref, setDownloadHref] = useState(data.downloadHref || '')
const [textFontSize, setTextFontSize] = useState('20')

2-1. ๋ฆฌ์•กํŠธ state

๋ฆฌ์•กํŠธ hooks๋ฅผ ์ด์šฉํ•ด์„œ ์ปดํฌ๋„ŒํŠธ ๋‚ด์—์„œ์˜ ์ƒํƒœ๊ด€๋ฆฌ๋ฅผ ํ•˜์˜€๋‹ค.

์ƒํƒœ๋ณ€์ˆ˜๋Š”

  1. text : ์ด๋ฏธ์ง€์— ์‚ฝ์ž…๋  ํ…์ŠคํŠธ
  1. imageOn : ์—…๋กœ๋“œ๋œ ์ด๋ฏธ์ง€
  1. imageOnWidth : ์—…๋กœ๋“œ๋œ ์ด๋ฏธ์ง€ width
  1. imageOnHeight : ์—…๋กœ๋“œ๋œ ์ด๋ฏธ์ง€ height
  1. downloadHref : ๋‹ค์šด๋กœ๋“œ๋งํฌ
  1. textFontSize : ์ด๋ฏธ์ง€์— ์‚ฝ์ž…๋  ํ…์ŠคํŠธ ํฌ๊ธฐ

2-2. ๋ฆฌ๋•์Šค store

๋ฆฌ๋•์Šค hooks๋ฅผ ์ด์šฉํ–ˆ๋‹ค.

contentsMenuBar์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋Š” ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด useDispatch๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ณ 

useSelector๋กœ ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์™”๋‹ค.

3. ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ

3-1. View

<label htmlFor="imageLoader">์ด๋ฏธ์ง€ ์—…๋กœ๋“œ</label>
<input type="file" id="imageLoader" name="imageLoader" hidden onChange={handleImage} />

file ํƒ€์ž…์˜ input ํƒœ๊ทธ๋ฅผ hidden ์œผ๋กœ ๊ฐ€๋ฆฌ๊ณ  label์— ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ๋ผ๋Š” ๊ธ€์ž์™€ css๋ฅผ ๋„ฃ์–ด์ฃผ์—ˆ๋‹ค.

3-2. ๐ŸŽ FileReader()

const handleImage = e => {
  var reader = new FileReader()
  reader.onload = event => {
    // โญ reader๊ฐ€ load๋˜๋ฉด ์‹คํ–‰
  }
  reader.readAsDataURL(e.target.files[0])
}

ํŒŒ์ผ์„ ์ฝ์„ ๋•Œ, FileReader ๊ฐ์ฒด๋ฅผ ์ด์šฉํ–ˆ๋‹ค.

FileReader ๊ฐ์ฒด์˜ readAsDataURL() ๋ฉ”์†Œ๋“œ๋ฅผ ์ด์šฉํ•ด ํŒŒ์ผ์„ ์ฝ์€ ํ›„, ํ•ด๋‹น ํŒŒ์ผ์˜ ๋ฐ์ดํ„ฐ URI๋ฅผ result ์†์„ฑ์— ์ €์žฅํ•˜์˜€๋‹ค.

๊ทธ๋ฆฌ๊ณ  FileReader ๊ฐ์ฒด๋Š” ์ด๋ฒคํŠธํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ,

event : load(์ฝ๊ธฐ ์™„๋ฃŒ), progress(์ฝ๋Š” ์ค‘), error(์—๋Ÿฌ)

onload ์ด๋ฒคํŠธํ•ธ๋“ค๋Ÿฌ๋กœ ํŒŒ์ผ์ด ์ฝ๊ธฐ ์™„๋ฃŒ๋˜๋ฉด ๋‹ค์Œ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜๋„๋ก ํ•˜์˜€๋‹ค.

FileReader์˜ ๋น„๋™๊ธฐ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ๋ณผ ์ˆ˜ ์žˆ์—ˆ๋‹ค.

3-3. ๐ŸŽ Image()

// ์œ„์˜ ์ฃผ์„๋ถ€๋ถ„

reader.onload = event => {
  var img = new Image()
  img.onload = () => {
    // โญ img๊ฐ€ load๋˜๋ฉด ์‹คํ–‰
  }
  img.src = event.target.result
}

FileReader ๋กœ๋“œ๊ฐ€ ์™„๋ฃŒ๋œ ํ›„, ํŒŒ์ผ์ด uri ํ˜•ํƒœ๋กœ ๋“ค์–ด์žˆ๋Š” result ์†์„ฑ์„ Image ๊ฐ์ฒด ์†์„ฑ src์— ๋„ฃ์–ด์ฃผ์—ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ onload ์ด๋ฒคํŠธํ•ธ๋“ค๋Ÿฌ๋กœ ๋‹ค์Œ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜๋„๋ก ํ•˜์˜€๋‹ค.

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋น„๋™๊ธฐ ๋งค์ปค๋‹ˆ์ฆ˜

3-4. ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง•

// ์œ„์˜ ์ฃผ์„๋ถ€๋ถ„

img.onload = () => {
  let max_size = 1280,
    width = img.width,
    height = img.height

  if (width > height) {
    if (width > max_size) {
      height *= max_size / width
      width = max_size
    }
  } else {
    if (height > max_size) {
      width *= max_size / height
      height = max_size
    }
  }
  canvas.width = width
  canvas.height = height
  ctx.drawImage(img, 0, 0, width, height)
  setImageOnWidth(width)
  setImageOnHeight(height)
}

์ด๋ฏธ์ง€์˜ onload ์ด๋ฒคํŠธํ•ธ๋“ค๋Ÿฌ์—๋Š” ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง• ํ•จ์ˆ˜์™€, canvas ์— ํ•ด๋‹น ์ด๋ฏธ์ง€๋ฅผ ๊ทธ๋ฆฌ๊ณ  ํ•ด๋‹น ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋„ฃ์–ด์ฃผ์—ˆ๋‹ค.

๋ฆฌ์‚ฌ์ด์ง•์€ ๋จผ์ € max_size๋ฅผ ๋‘๊ณ , ํ•ด๋‹น ์ด๋ฏธ์ง€์˜ width ํ˜น์€ height๊ฐ€ max_size๋ฅผ ์ดˆ๊ณผํ•˜๋ฉด ์ดˆ๊ณผํ•œ width, height๋ฅผ max_size๋กœ ๋งž์ถ”๊ณ  ๋‚˜๋จธ์ง€ width, height๋ฅผ ๋น„์œจ์— ๋งž๊ฒŒ ์กฐ์ •ํ•˜๋Š” ์‹์œผ๋กœ ํ•˜์˜€๋‹ค.

width : height = x : max_size (height๊ฐ€ ์ดˆ๊ณผํ•œ๊ฒฝ์šฐ)

4. ์ด๋ฏธ์ง€์— ํ…์ŠคํŠธ ์‚ฝ์ž…

4-1. ํ…์ŠคํŠธ ์ž…๋ ฅ์ฐฝ

<input
  type="file"
  id="imageLoader"
  name="imageLoader"
  hidden
  onChange={e => {
    // โญ
    setText(e.target.value)
  }}
/>

4-2. ํ…์ŠคํŠธ ํฌ๊ธฐ ๋ณ€๊ฒฝ์ฐฝ

<div style={{ display: 'flex', alignItems: 'center' }}>
  <label
    style={{
      marginRight: '10px',
      fontFamily: 'escore7',
      fontSize: '12px',
      color: '#555',
    }}
    htmlFor="textFontSize"
  >
    ๊ธ€์ž ํฌ๊ธฐ
  </label>

  <select
    id="textFontSize"
    onChange={e => {
      // โญ
      setTextFontSize(e.target.value)
    }}
  >
    <option value="20">20</option>
    <option value="28">28</option>
    <option value="36">36</option>
    <option value="44">44</option>
  </select>
</div>

ํ…์ŠคํŠธ ์ž…๋ ฅ์ฐฝ, ํ…์ŠคํŠธ ํฌ๊ธฐ ๋ณ€๊ฒฝ์ฐฝ ๋ชจ๋‘ change ์ด๋ฒคํŠธํ•ธ๋“ค๋Ÿฌ ์ฝœ๋ฐฑํ•จ์ˆ˜๋ฅผ ๋”ฐ๋กœ ์„ ์–ธํ•˜์ง€ ์•Š๊ณ  ์†์„ฑ๊ฐ’ ๋‚ด๋ถ€์—์„œ ๋ฐ”๋กœ ์‹คํ–‰ํ•˜๊ฒŒ๋” ํ•˜์˜€๋‹ค.

5. real-time Canvas

5-1. ๐ŸŽ useEffect

useEffect(() => {
  var canvas = document.getElementById('imageCanvas')
  const ctx = canvas.getContext('2d')
  ctx.textAlign = 'center'
  ctx.textBaseline = 'middle'
  ctx.font = `${textFontSize}px Gulim`
  ctx.clearRect(0, 0, canvas.width, canvas.height)
  if (imageOn) {
    ctx.drawImage(imageOn, 0, 0, imageOnWidth, imageOnHeight)
  } else {
    ctx.fillStyle = 'dodgerblue'
    ctx.fillRect(0, 0, canvas.width, canvas.height)
  }
  ctx.lineWidth = 5
  ctx.strokeStyle = `black`
  ctx.strokeText(text, canvas.width / 2, canvas.height - 40)
  ctx.fillStyle = `white`
  ctx.fillText(text, canvas.width / 2, canvas.height - 40)
})

์•ž์„œ ์—…๋กœ๋“œํ•œ ์ด๋ฏธ์ง€์™€ ํ…์ŠคํŠธ์˜ ๊ฐ’๊ณผ ์Šคํƒ€์ผ์„ canvas์— ๊ทธ๋ฆฌ๋Š” ์—ญํ• ๋กœ useEffect ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์˜€๋‹ค.

5-2. ๐ŸŽ real-time Canvas

์ฒ˜์Œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ, ํ…์ŠคํŠธ๊ฐ€ ๊ฒน์น˜๋Š” ํ˜„์ƒ์ด ์ผ์–ด๋‚ฌ์—ˆ๋‹ค.

hello๋ฅผ ์น˜๋ฉด canvas ์œ„์— h ๊ฐ€ ๊ทธ๋ ค์ง€๊ณ , ๋‹ค์Œ he ๊ฐ€ ๋‹ค์‹œ ๊ทธ๋ ค์ง€๊ณ , hel โ€ฆ ์ด๋Ÿฐ์‹์œผ๋กœ ์Œ“์—ฌ๋งŒ ๊ฐ”๋‹ค.

๊ทธ๋ž˜์„œ clearRect() ๋ฉ”์†Œ๋“œ๋ฅผ ํ†ตํ•ด canvas๋ฅผ ๋‹ค์‹œ ๋ฆฌ์…‹์‹œํ‚ค๊ณ 

image๊ฐ€ ๋“ฑ๋ก๋œ ์ƒํƒœ๋ฉด ๋‹ค์‹œ image๋ฅผ ๊ทธ๋ฆฌ๊ณ 

image๊ฐ€ ์—†์œผ๋ฉด ๊ธฐ์กด์˜ ๋ฐฐ๊ฒฝ์„ ์œ ์ง€ํ•˜๋„๋ก ์„ค์ •ํ•ด์ฃผ์—ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ํ…์ŠคํŠธ ๋˜ํ•œ ๊ฒน์น˜์ง€ ์•Š๊ฒŒ ์ƒˆ๋กœ์šด ๋ฐฐ๊ฒฝ์— ํ˜„์žฌ text state๊ฐ’์„ ๊ทธ๋ฆฌ๋„๋ก ์„ค์ •ํ•ด์ฃผ์—ˆ๋”๋‹ˆ ๊ฒน์นจํ˜„์ƒ์„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

6. ์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋“œ

6-1. ๐ŸŽ toDataURL()

useEffect(() => {
  const href = canvas.toDataURL()
  setDownloadHref(href)
})

์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋“œ๋Š” canvas์˜ toDataURL() ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.

canvas๋ฅผ ๊ทธ๋ฆด ๋•Œ๋งˆ๋‹ค setDownloadHref() hooks๋ฅผ ํ†ตํ•ด ์ด๋ฏธ์ง€๋ฐ์ดํ„ฐ๊ฐ’์„ ์ดˆ๊ธฐํ™”ํ•ด์ฃผ์—ˆ๋‹ค.

6-2. View

<a href={downloadHref} download="jjalbang.png">
  ์งค๋ฐฉ ๋‹ค์šด๋กœ๋“œ
</a>

๊ทธ๋ฆฌ๊ณ  a ํƒœ๊ทธ์˜ download ์†์„ฑ์„ ํ†ตํ•ด ํ•ด๋‹น ์ด๋ฏธ์ง€๋ฅผ ๋‹ค์šด๋กœ๋“œ ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก ํ•˜์˜€๋‹ค.

7. ๊ฐœ์ธ์ ์ธ ํ”ผ๋“œ๋ฐฑ

7-1. ํ…์ŠคํŠธ์˜ ํ€„๋ฆฌํ‹ฐ

์ด๋ฏธ์ง€์— ํ…์ŠคํŠธ๊ฐ€ ์‚ฝ์ž…์€ ๋˜์ง€๋งŒ ๋„ˆ๋ฌด ๊นจ์ ธ์žˆ๋‹ค๋Š” ๋Š๋‚Œ์„ ๋งŽ์ด ๋ฐ›์•˜๋‹ค.

ํ…์ŠคํŠธ๋ฅผ Anti-Aliasing ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด๋‚˜, ํ˜น์€ ๋ฒกํ„ฐ๋กœ ๊ทธ๋ฆฌ๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•œ์ง€ ํ•œ๋ฒˆ ์•Œ์•„๋ด์•ผ๊ฒ ๋‹ค.

7-2. ์ง€์ €๋ถ„ํ•œ styled Component

const JJalForm = styled.form`
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;

  & > p {
    font-size: 12px;
    font-family: escore7;
    color: #666;
    margin-bottom: 20px;
  }

  & > div {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    margin-bottom: 10px;

    & > label {
      font-size: 12px;

      ์ดํ•˜์ƒ๋žต...
`

์•„์ง์€ ๋‚˜์—๊ฒŒ ์ƒ์†Œํ•œ ํƒœ๊ทธ๋“ค(select, option, label, canvas)์„ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋‹ค๋ณด๋‹ˆ styled component๊ฐ€ ์ง€์ €๋ถ„ํ•ด์กŒ๋‹ค.

๊ทธ๋ž˜๋„ ํƒœ๊ทธ๋“ค์ด ๋งŽ์ง€ ์•Š์•„์„œ ๊ทธ๋ ‡๊ฒŒ ํ—ท๊ฐˆ๋ฆฌ์ง€๋Š” ์•Š์ง€๋งŒ ์ฒ˜์Œ style์„ ์ง€์ •ํ•  ๋•Œ๋ถ€ํ„ฐ ์‹ ๊ฒฝ์„ ์ข€ ์จ์•ผ๊ฒ ๋‹ค๊ณ  ๋Š๊ผˆ๋‹ค.

7-3. ๋ฆฌ๋“€์„œ ๊ฑฐ์ณค์„ ๋•Œ, ์ด๋ฏธ์ง€ ์ž˜๋ฆผํ˜„์ƒ

uing

์œ„์˜ ๊ฐœ์ธ ๋น„ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๊ณ  Docker์— ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ €์žฅํ–ˆ๋‹ค ๋‹ค์‹œ ๋ถˆ๋Ÿฌ์™”์„ ๋•Œ ์ƒํ™ฉ..

์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ Docker์— ๋‹ด๊ณ  ๋‹ค์‹œ ์—ด์—ˆ์„ ๋•Œ ์ด๋ฏธ์ง€๊ฐ€ ์ž˜๋ฆฌ๋Š” ํ˜„์ƒ์ด ์žˆ๋‹ค.

Redux Dev Tools ๋กœ ์ฐจ๊ทผ์ฐจ๊ทผ ๋‹ค์‹œ ๋ด๋ณผ๊ฒƒ.

7-4. Blob ๊ฐ์ฒด

ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋‹ค๊ฐ€ Blob ๊ฐ์ฒด์˜ ๊ฐœ๋…๋„ ์•Œ๊ฒŒ๋˜์—ˆ๋Š”๋ฐ ๋‹ค์‹œ ํ•™์Šตํ•  ๊ฒƒ!

8. ์ฐธ๊ณ  ์‚ฌ์ดํŠธ


Written by@taenyKim
๋ฐฐ์šฐ๋ฉฐ ์„ฑ์žฅํ•˜๊ณ  ๊ธฐ๋กํ•˜๊ธฐ #FE #UI #๊ฐœ๋ฐœ #life

GitHubFacebook