🍠 바닐라JS로 그림 앱 만들기 : 노마드 코더 클론 코딩 🍠

2023. 7. 27. 00:32개발/🍠 바닐라JS 노마드 코더 클론코딩

 

 

 

수료증!!

 

 

 

 

그림앱 만든 거!

 

 

 

const saveBtn = document.getElementById("save");	//id:save인 버튼 불러오기
const textInput = document.getElementById("text");	//id:text인 요소(text input) 불러오기
const fileInput = document.getElementById("file");	//id:file인 요소(file input) 불러오기
const eraseBtn = document.getElementById("eraser-btn"); //id:eraser-btn인 버튼 불러오기
const destroyBtn = document.getElementById("destroy-btn");id:destroy-btn인 버튼 불러오기
const modeBtn = document.getElementById("mode-btn");id:mode-btn인 버튼 불러오기

//색 옵션(class : color-option) 배열로 받기
const colorOptions = Array.from(
    document.getElementsByClassName("color-option")
);

const color = document.getElementById("color");    //id:color인 요소(색 input) 불러오기
const lineWidth = document.getElementById("line-width");    //id:line-width인 요소(선 굵기 input) 불러오기
const canvas = document.querySelector("canvas");    //html에서 canvas 불러오기
const ctx = canvas.getContext("2d");    //canvas상의 드로잉 컨텍스트(type:2d로 지정) 불러오기

//canvas 크기 상수
const CANVAS_WIDTH = 800;
const CANVAS_HEIGHT = 800;

//canvas 크기 지정
canvas.width = CANVAS_WIDTH;
canvas.height = CANVAS_HEIGHT;

ctx.lineWidth = lineWidth.value;    //ctx의 디폴트 선 굵기 지정
ctx.lineCap = "round";		//ctx의 선 끝 둥글게 만들기
let isPainting = false;     //그림을 그리고 있는지
let isFilling = false;		//색 채우기 모드인지

//마우스 따라서 선 그리기
function onMove(event){
    #지금 그림을 그리고 있다면
    if(isPainting){
        ctx.lineTo(event.offsetX, event.offsetY);	//마우스의 위치 따라서 line 따라가기
        ctx.stroke();	//따라간 line의 색 채우기
        return;
    }
    #지금 그림을 그리고 있지 않다면 -> 붓의 위치만 이동시키기
    ctx.moveTo(event.offsetX, event.offsetY);
}

#그림 그린다!
function startPainting(event){
    isPainting = true;
}

#그림 안 그린다!
function cancelPainting(event){
    isPainting = false;
    ctx.beginPath();
}

#선 굵기 변경
function onLineWidthChange(event){
    ctx.lineWidth = event.target.value;	 #굵기 변경 슬라이더의 값 따라서 선 굵기 지정
}

#선 색깔 바꾸기
function onColorChange(event){
	#선택한 값 따라서 선 색깔 지정
    ctx.strokeStyle = event.target.value;	#선 색 
    ctx.fillStyle = event.target.value;		#배경 색	
}

#색 옵션 바꾸기
function onColorClick(event){
    const colorValue = event.target.dataset.color;	#클릭한 옵션의 data값(색 넘버) 저장
    ctx.strokeStyle = colorValue;	#선 색
    ctx.fillStyle = colorValue;	    #배경 색
}

#배경 채우기 / 선 그리기 모드 선택
function onModeClick(event){
#버튼 눌렀을 때
	#현재 isFilling : true 상태면
    if(isFilling){
        isFilling = false;	#isFilling : false로 바꾸고
        modeBtn.innerText = "Fill";	  #버튼 텍스트 Fill로 바꾸기
    }
    #현재 isFilling : false 상태면
    else{
        isFilling = true;	#isFilling : true로 바꾸고
        modeBtn.innerText = "Draw";	  #버튼 텍스트 Draw로 바꾸기
    }
}

#배경 색 바꾸기
function onCanvasClick(event){
    if(isFilling){
        ctx.fillRect(0,0,CANVAS_WIDTH,CANVAS_HEIGHT);	#배경 크기 맞춰서 색 바꾸기
    }
}

#초기화
function onDestroyClick(event){
    ctx.fillStyle = "white";	#배경 색 white로 지정 
    ctx.fillRect(0,0,CANVAS_WIDTH,CANVAS_HEIGHT);	#지정된 색(white)으로 배경 채우기
}

#지우개
function onEraserClick(event){
    ctx.strokeStyle = "white";	#선 색 white로 지정
    isFilling = false;	#isFilling : false
    modeBtn.innerText = "Fill";		#모드 변경 버튼 텍스트 : Fill
}

#사진 파일 삽입
function onFileChange(event){
    const file = event.target.files[0];	   #파일 상수에 저장
    const url = URL.createObjectURL(file);	#파일 url(브라우저에만 존재) 상수에 저장 
    const image = new Image();   #image 생성
    image.src = url;    #image 출처 <= 파일 url
    #image onload 하면 함수 발동
    image.onload = function(){
        ctx.drawImage(image, 0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); #배경에 사진 삽입
        fileInput.value = null;
    };
}

#더블클릭으로 텍스트 삽입
function onDoubleClick(event){
    const text = textInput.value;  #사용자가 input한 텍스트 상수에 저장
    if (text !==""){
        ctx.save();	#기존 ctx(그림 선)의 정보 저장
        ctx.lineWidth = 1;  #ctx(현재는 텍스트)의 선 굵기 변경
        ctx.font = "68px, serif";  #ctx(현재는 텍스트)의 폰트 변경
        ctx.strokeText(text, event.offsetX, event.offsetY);	#더블클릭한 위치에 텍스트 삽입
        ctx.restore();	#ctx.save() 때 저장한 정보 다시 불러오기
    }
    
}

#그림 파일 컴퓨터에 저장
function onSaveClick(event){
    const url = canvas.toDataURL();
    const a = document.createElement("a");
    a.href = url;
    a.download = "myDrawing.png";
    a.click();
}


canvas.addEventListener("dblclick", onDoubleClick)
canvas.addEventListener("mousemove", onMove);
canvas.addEventListener("mousedown",startPainting);
canvas.addEventListener("mouseup",cancelPainting);
canvas.addEventListener("mouseleave", cancelPainting);
canvas.addEventListener("click", onCanvasClick)

lineWidth.addEventListener("change", onLineWidthChange);
color.addEventListener("change", onColorChange);
colorOptions.forEach((color) => color.addEventListener("click", onColorClick));

modeBtn.addEventListener("click", onModeClick);
destroyBtn.addEventListener("click", onDestroyClick);
eraseBtn.addEventListener("click", onEraserClick);
saveBtn.addEventListener("click", onSaveClick);

fileInput.addEventListener("change", onFileChange);

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=  , initial-scale=1.0">
    <title>Meme Maker</title>
    <link rel = "stylesheet" href="styles.css" />
</head>
<body>
    <div class="color-options">
        <input type="color" id="color" />
        <div
        class="color-option" 
        style="background-color: #1abc9c;" 
        data-color="#1abc9c"></div>
        <div 
        class="color-option" 
        style="background-color: #3498db;" 
        data-color="#3498db"></div>
        <div 
        class="color-option" 
        style="background-color: #34495e;" 
        data-color="#34495e"></div>
        <div 
        class="color-option" 
        style="background-color: #27ae60;" 
        data-color="#27ae60"></div>
        <div 
        class="color-option" 
        style="background-color: #8e44ad;" 
        data-color="#8e44ad"></div>
        <div 
        class="color-option" 
        style="background-color: #f1c40f;" 
        data-color="#f1c40f"></div>
        <div 
        class="color-option" 
        style="background-color: #e74c3c;" 
        data-color="#e74c3c"></div>
        <div 
        class="color-option" 
        style="background-color: #95a5a6;" 
        data-color="#95a5a6"></div>
        <div 
        class="color-option" 
        style="background-color: #d35400;" 
        data-color="#d35400"></div>
        <div 
        class="color-option" 
        style="background-color: #bdc3c7;" 
        data-color="#bdc3c7"></div>
        <div 
        class="color-option" 
        style="background-color: #2ecc71;" 
        data-color="#2ecc71"></div>
        <div 
        class="color-option" 
        style="background-color: #e67e22;" 
        data-color="#e67e22"></div>
    </div>
    <canvas></canvas>
    <div class = "btns">
        <input
            id="line-width"
            type="range"
            min="1"
            max="10"
            value="5"
            step="0.1"
        />
        <button id ="mode-btn">🩸</button>
        <button id ="destroy-btn">💣</button>
        <button id ="eraser-btn">❌</button>
        <label for="file">

        </label>
            💅🏻 Add Photo
            <input type="file" accept="image/*" id="file"
        /></label>
        <input type="text" id="text" placeholder="Add text here... :)" />
        <button id="save">🖼 Save image</button>
    </div>
    <script src="app.js"></script>
</body>
</html>

 

@import "reset.css";

body {
    display: flex;
    gap:20px;
    justify-content: space-between;
    align-items: flex-start;
    background-color: #f6f9fc;
    padding:20px;
    font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}

canvas{
    width: 800px;
    height: 800px;
    border: 5px solid black;
    border:5px solid black;
    background-color: white;
    border-radius:10px;
    box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
}

body {
    display: flex;
    justify-content: center;
    align-items: center;
}

.btns {
    display: flex;
    flex-direction: column;
    gap:20px;
}

.color-options {
    display: flex;
    flex-direction: column;
    gap:20px;
    align-items: center;
}
.color-option {
    width:50px;
    height: 50px;
    border-radius: 50%;
    cursor: pointer;
    border:2px solid white;
    transition:transform ease-in-out .1s;
    box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);

}
.color-option:hover {
    transform: scale(1.2);
}
input#color {
    background-color: white;
}

button,
label {
    all:unset;
    padding:10px 0px;
    text-align: center;
    background-color:royalblue;
    color:white;
    font-weight: 500;
    cursor: pointer;
    border-radius: 10px;
    transition: opacity linear .1s;
    box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
}

button:hover {
    opacity: 0.85;
}

input#file {
    display: none;
}

input#text {
    all:unset;
    padding:10px 0px;
    border-radius: 10px;
    font-weight: 500;
    text-align: center;
    background-color:white;
    box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
}

 

html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed, 
figure, figcaption, footer, header, hgroup, 
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
	margin: 0;
	padding: 0;
	border: 0;
	font-size: 100%;
	font: inherit;
	vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure, 
footer, header, hgroup, menu, nav, section {
	display: block;
}
body {
	line-height: 1;
}
ol, ul {
	list-style: none;
}
blockquote, q {
	quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
	content: '';
	content: none;
}
table {
	border-collapse: collapse;
	border-spacing: 0;
}

 

 

 

 

 

Code Challenge는 패스....

다음에 해커톤할 때 잘 응용해봐야겠다!!