March 11, 2020
์ด๋ฏธ์ง๋ฅผ ์ ๋ก๋ํ๊ณ ์์ ํ๊ณ ๋ค์ด๋ก๋ํ๋ ๊ธฐ๋ฅ์ ๋ง๋ค์ด๋ณด๊ณ ์ถ์ด์
์งค๋ฐฉ์์ฑ๊ธฐ๋ฅผ ํ๋ฒ ๋ง๋ค์ด๋ณด์๋ค.
- ์ดํ๋ฆฌ์ผ์ด์ ์ค๋ช
- ์ด๋ฏธ์ง ์ ๋ก๋ ๋ฒํผ๊ณผ ๋ค์ด๋ก๋ ๋ฒํผ
- ์ด๋ฏธ์ง ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ฐฝ (canvas ์ด์ฉ)
- ์ด๋ฏธ์ง์ ์ฝ์ ํ ํ ์คํธ ์ฐฝ (input ์ด์ฉ)
- ํ ์คํธ ํฌ๊ธฐ ์กฐ์ ์ ๋ ํธ๋ฐ์ค (select ์ด์ฉ)
์น๋ช ์ ..
โHello world!โ
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')
๋ฆฌ์กํธ hooks๋ฅผ ์ด์ฉํด์ ์ปดํฌ๋ํธ ๋ด์์์ ์ํ๊ด๋ฆฌ๋ฅผ ํ์๋ค.
์ํ๋ณ์๋
text
: ์ด๋ฏธ์ง์ ์ฝ์ ๋ ํ ์คํธ
imageOn
: ์ ๋ก๋๋ ์ด๋ฏธ์ง
imageOnWidth
: ์ ๋ก๋๋ ์ด๋ฏธ์ง width
imageOnHeight
: ์ ๋ก๋๋ ์ด๋ฏธ์ง height
downloadHref
: ๋ค์ด๋ก๋๋งํฌ
textFontSize
: ์ด๋ฏธ์ง์ ์ฝ์ ๋ ํ ์คํธ ํฌ๊ธฐ
๋ฆฌ๋์ค hooks๋ฅผ ์ด์ฉํ๋ค.
contentsMenuBar์์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋ ๋ฒํผ์ ๋๋ฅด๋ฉด useDispatch
๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ณ
useSelector
๋ก ์ ์ฅ๋ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์๋ค.
<label htmlFor="imageLoader">์ด๋ฏธ์ง ์
๋ก๋</label>
<input type="file" id="imageLoader" name="imageLoader" hidden onChange={handleImage} />
file ํ์ ์ input ํ๊ทธ๋ฅผ hidden ์ผ๋ก ๊ฐ๋ฆฌ๊ณ label์ ์ด๋ฏธ์ง ์ ๋ก๋๋ผ๋ ๊ธ์์ css๋ฅผ ๋ฃ์ด์ฃผ์๋ค.
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์ ๋น๋๊ธฐ ๋ฉ์ปค๋์ฆ์ ๋ณผ ์ ์์๋ค.
// ์์ ์ฃผ์๋ถ๋ถ
reader.onload = event => {
var img = new Image()
img.onload = () => {
// โญ img๊ฐ load๋๋ฉด ์คํ
}
img.src = event.target.result
}
FileReader ๋ก๋๊ฐ ์๋ฃ๋ ํ, ํ์ผ์ด uri ํํ๋ก ๋ค์ด์๋ result ์์ฑ์ Image
๊ฐ์ฒด ์์ฑ src์ ๋ฃ์ด์ฃผ์๋ค.
๊ทธ๋ฆฌ๊ณ ๋ง์ฐฌ๊ฐ์ง๋ก onload
์ด๋ฒคํธํธ๋ค๋ฌ๋ก ๋ค์ ํจ์๋ฅผ ์คํํ๋๋ก ํ์๋ค.
๋ง์ฐฌ๊ฐ์ง๋ก ๋น๋๊ธฐ ๋งค์ปค๋์ฆ
// ์์ ์ฃผ์๋ถ๋ถ
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๊ฐ ์ด๊ณผํ๊ฒฝ์ฐ)
<input
type="file"
id="imageLoader"
name="imageLoader"
hidden
onChange={e => {
// โญ
setText(e.target.value)
}}
/>
<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 ์ด๋ฒคํธํธ๋ค๋ฌ ์ฝ๋ฐฑํจ์๋ฅผ ๋ฐ๋ก ์ ์ธํ์ง ์๊ณ ์์ฑ๊ฐ ๋ด๋ถ์์ ๋ฐ๋ก ์คํํ๊ฒ๋ ํ์๋ค.
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
ํจ์๋ฅผ ์ฌ์ฉํ์๋ค.
์ฒ์ ์ฝ๋๋ฅผ ์์ฑํ ๋, ํ ์คํธ๊ฐ ๊ฒน์น๋ ํ์์ด ์ผ์ด๋ฌ์๋ค.
hello๋ฅผ ์น๋ฉด canvas ์์ h ๊ฐ ๊ทธ๋ ค์ง๊ณ , ๋ค์ he ๊ฐ ๋ค์ ๊ทธ๋ ค์ง๊ณ , hel โฆ ์ด๋ฐ์์ผ๋ก ์์ฌ๋ง ๊ฐ๋ค.
๊ทธ๋์ clearRect()
๋ฉ์๋๋ฅผ ํตํด canvas๋ฅผ ๋ค์ ๋ฆฌ์
์ํค๊ณ
image๊ฐ ๋ฑ๋ก๋ ์ํ๋ฉด ๋ค์ image๋ฅผ ๊ทธ๋ฆฌ๊ณ
image๊ฐ ์์ผ๋ฉด ๊ธฐ์กด์ ๋ฐฐ๊ฒฝ์ ์ ์งํ๋๋ก ์ค์ ํด์ฃผ์๋ค.
๊ทธ๋ฆฌ๊ณ ํ
์คํธ ๋ํ ๊ฒน์น์ง ์๊ฒ ์๋ก์ด ๋ฐฐ๊ฒฝ์ ํ์ฌ text
state๊ฐ์ ๊ทธ๋ฆฌ๋๋ก ์ค์ ํด์ฃผ์๋๋ ๊ฒน์นจํ์์ ํด๊ฒฐํ ์ ์์๋ค.
useEffect(() => {
const href = canvas.toDataURL()
setDownloadHref(href)
})
์ด๋ฏธ์ง ๋ค์ด๋ก๋๋ canvas์ toDataURL()
๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ค.
canvas๋ฅผ ๊ทธ๋ฆด ๋๋ง๋ค setDownloadHref() hooks๋ฅผ ํตํด ์ด๋ฏธ์ง๋ฐ์ดํฐ๊ฐ์ ์ด๊ธฐํํด์ฃผ์๋ค.
<a href={downloadHref} download="jjalbang.png">
์งค๋ฐฉ ๋ค์ด๋ก๋
</a>
๊ทธ๋ฆฌ๊ณ a ํ๊ทธ์ download ์์ฑ
์ ํตํด ํด๋น ์ด๋ฏธ์ง๋ฅผ ๋ค์ด๋ก๋ ๋ฐ์ ์ ์๋๋ก ํ์๋ค.
์ด๋ฏธ์ง์ ํ ์คํธ๊ฐ ์ฝ์ ์ ๋์ง๋ง ๋๋ฌด ๊นจ์ ธ์๋ค๋ ๋๋์ ๋ง์ด ๋ฐ์๋ค.
ํ
์คํธ๋ฅผ Anti-Aliasing
ํ ์ ์๋ ๋ฐฉ๋ฒ์ด๋, ํน์ ๋ฒกํฐ
๋ก ๊ทธ๋ฆฌ๋ ๊ฒ์ด ๊ฐ๋ฅํ์ง ํ๋ฒ ์์๋ด์ผ๊ฒ ๋ค.
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์ ์ง์ ํ ๋๋ถํฐ ์ ๊ฒฝ์ ์ข ์จ์ผ๊ฒ ๋ค๊ณ ๋๊ผ๋ค.
์์ ๊ฐ์ธ ๋น ์ด๋ฏธ์ง๋ฅผ ์ ๋ก๋ํ๊ณ Docker์ ์ดํ๋ฆฌ์ผ์ด์ ์ ์ ์ฅํ๋ค ๋ค์ ๋ถ๋ฌ์์ ๋ ์ํฉ..
์ดํ๋ฆฌ์ผ์ด์ ์ Docker์ ๋ด๊ณ ๋ค์ ์ด์์ ๋ ์ด๋ฏธ์ง๊ฐ ์๋ฆฌ๋ ํ์์ด ์๋ค.
Redux Dev Tools ๋ก ์ฐจ๊ทผ์ฐจ๊ทผ ๋ค์ ๋ด๋ณผ๊ฒ.
ํ๋ก์ ํธ๋ฅผ ์งํํ๋ค๊ฐ Blob
๊ฐ์ฒด์ ๊ฐ๋
๋ ์๊ฒ๋์๋๋ฐ ๋ค์ ํ์ตํ ๊ฒ!