개발 뜯기/JavaScript

[JavaScript / Nomad Coder] JavaScript로 그림판 만들기(canvas)

디자인 지지(ZII) 2021. 10. 26. 17:10
반응형

 

 

강의에서 배운 기능에 새로운 기능인 eraser와 clear 버튼을 추가 했다.

그리고 컬러는 슬라이드로 구현을 해놨다.

아예 처음부터 만들기도 했고 공부한지 시간이 꽤 지났어서 오래 걸렸다. ㅠㅠ 

클론코딩은 역시 본인이 직접 다시 만들어봐야 하는 것 같다.

 

 

HTML

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="styles.css">
  <title>Paint JS</title>
</head>
<body>
  <section class="canvas-wrap">
    <canvas class="jsCanvas canvas" width="500" height="700"></canvas>
    <nav class="menu">
      <button class="menu__icon" type="menu" value="open" onclick="activeMenu()">
        <span></span>
        <span></span>
        <span></span>
      </button>
      <ul class="controls-colors">
        <li class="controls__color jsColor" style="background-color: #333;"></li>
        <li class="controls__color jsColor" style="background-color: #fff;"></li>
        <li class="controls__color jsColor" style="background-color: #fe3629;"></li>
        <li class="controls__color jsColor" style="background-color: #FE8B00;"></li>
        <li class="controls__color jsColor" style="background-color: #FEC600;"></li>
        <li class="controls__color jsColor" style="background-color: #44D558;"></li>
        <li class="controls__color jsColor" style="background-color: #52C1FB;"></li>
        <li class="controls__color jsColor" style="background-color: #006BF9"></li>
        <li class="controls__color jsColor" style="background-color: #8040d4"></li>
      </ul>
    </nav>
  </section>

  <div class="controls-functions">
    <div class="controls-btns">
      <button class="btns__clear jsClear">CLEAR</button>
      <button class="btns__save jsSave">SAVE</button>      
    </div>
    <div class="controls-tools">
      <button class="tools__pen jsPen">PEN</button>
      <button class="tools__eraser jsEraser">ERASER</button>
      <button class="tools__paint jsPaint">PAINT</button>
      <input type="range" class="tools__range jsRange" min="0.1" max="10" value="5" step="0.1">
    </div>
  </div>

  <script src="canvas.js"></script>
  <script src="menu.js"></script>
</body>
</html>

 

 

CSS

@import "reset.css";

body {
  padding: 50px 0px;

  background-color: #f6f9fc;
}

.canvas-wrap {
  display: flex;
  justify-content: center;
  align-items: center;

  height: 70vh;
  margin-bottom: 2em;
}
.canvas {
  width: 500px;
  height: 700px;

  background-color: #fff;
  border-radius: 15px;
  box-shadow: 0px 4px 5px rgb(233, 233, 233), 0 1px 3px rgb(160, 160, 160);
}

/* ==================== colors menu */
.menu {
  position: relative;
  height: 100%;
  margin-left: 60px;
}
.menu__icon {
  position: absolute;
  top: 6%;
  left: .5em;
  width: 42px;
  height: 32px;
  z-index: 10;

  cursor: pointer;
  background-color: unset;
  border: none;

  transition: transform 1s;
}
.menu__icon span {
  position: absolute;
  left: 0;
  width: 100%;
  height: 2px;

  background-color: #444;
} 
.menu__icon span:nth-of-type(1) {top: 0;}
.menu__icon span:nth-of-type(2) {top: 15px; height: 3px;}
.menu__icon span:nth-of-type(3) {bottom: 0;}

.controls-colors { 
  display: flex;
  flex-direction: column;

  position: absolute;
  top: -40%;
  left: 0;
  opacity: 0;

  transform: translateY(-40%);
  transition: 1s;
}
.controls__color {
  width: 50px;
  height: 50px;
  margin-top: 15px;

  border-radius: 30px 40px 5px;
  cursor: pointer;
  box-shadow: 0px 4px 5px rgb(233, 233, 233), 0 1px 3px rgb(160, 160, 160);
}
.controls-color:active {
  transform: scale(0.98);
}

/* ======================= function wrap */
.controls-functions {
  display: flex;
  flex-flow: column wrap;
  align-items: center;

  margin-right: 8em;
}
.controls-functions button {
  width: 80px;
  height: 40px;
  margin: .5em .1em;
  
  font-size: inherit;
  background-color: #fff;
  border: 1px solid rgb(235, 235, 235);
  border-radius: .3em;
  box-shadow: 0px 0px 3px 1px rgba(0, 0, 0, 0.05);
  cursor: pointer;
}
.tools__range { 
  width: 160px;
  margin-left: 50px; 
}

 

 

JavaScript

저번에 올렸을 때 이웃님이 Array.from을 사용하는 것은 functional 하지 않다고 해주셔서 그냥 forEach()로 돌렸다.

어차피 querySelectorAll로 받으면 배열로 가져올텐데 굳이 새로운 배열로 만들어주지않아도 될 것 같았다.

const canvas = document.querySelector(".jsCanvas");
const penBtn = document.querySelector(".jsPen");
const eraserBtn = document.querySelector(".jsEraser");
const paintBtn = document.querySelector(".jsPaint");
const range = document.querySelector(".jsRange");
const clearBtn = document.querySelector(".jsClear");
const saveBtn = document.querySelector(".jsSave");
const colors = document.querySelectorAll(".jsColor");

const ctx = canvas.getContext('2d');

const CANVAS_WEIGHT = 500;
const CANVAS_HEIGHT = 700;

// canvas setting
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, CANVAS_WEIGHT, CANVAS_HEIGHT);
ctx.lineWidth = 2.5;
ctx.fillStyle = "#2c2c2c";

// default = false
let painting,
    filling,
    erasing = false;


function startPainting() { 
  painting = true;
  erasing = true;
}

function stopPainting() {
  painting = false;
  erasing = false;
}

/* drawing stroke */
function onMouseMove(e) {
  const x = e.offsetX;
  const y = e.offsetY;

  if (painting) {
    ctx.lineTo(x, y);
    ctx.stroke();
  } else {
    ctx.beginPath();
    ctx.moveTo(x, y); // 마우스를 따라간 곳이 시작점
  }
}

// choose color = drawing color
function clickColor(e) {
  const color = e.target.style.backgroundColor;
  ctx.strokeStyle = color;
  ctx.fillStyle = color;
}
/* choose color 
 배열에 담아 forEach() 돌려줌 */
colors.forEach((targetColor) => {
  targetColor.addEventListener("click", clickColor);
})

// handle range
function handleRange(e) {
  const rangeSize = e.target.value;
  ctx.lineWidth = rangeSize;
}

function clickPenBtn() {
  ctx.globalCompositeOperation = 'source-over';
  filling = false;
}

function clickPaintBtn() {
  ctx.globalCompositeOperation = 'source-over';
  filling = true;
}

// fill canvas
function handleCanvasClick(e) {
  if (filling) ctx.fillRect(0, 0, CANVAS_HEIGHT, CANVAS_HEIGHT);
}


function clickEraserBtn() {
  ctx.globalCompositeOperation = 'destination-out';
  ctx.strokeStyle = "rgb(255, 255, 255)";
}

function clickClearBtn() {
  ctx.clearRect(0, 0, CANVAS_WEIGHT, CANVAS_WEIGHT);
  ctx.beginPath();
}

function clickSaveBtn() {
  const img = canvas.toDataURL();
  const link = document.createElement("a"); // a: anchar (href link같은거)
  link.href = img;
  link.download = "canvas image";
  link.click();
}


/* 1. 마우스가 돌아다닐 때
  2. 마우스가 클릭 될 때
  3. 마우스가 떨어질 때
  4. 마우스가 캔버스 밖에 있을 때 */
if (canvas) {
  canvas.addEventListener("mousemove", onMouseMove);
  canvas.addEventListener("mousedown", startPainting);
  canvas.addEventListener("mouseup", stopPainting);
  canvas.addEventListener("mouseleave", stopPainting);
  canvas.addEventListener("click", handleCanvasClick);
}

if (penBtn) penBtn.addEventListener("click", clickPenBtn);
if (paintBtn) paintBtn.addEventListener("click", clickPaintBtn);
if (eraserBtn) eraserBtn.addEventListener("click", clickEraserBtn);
if (clearBtn) clearBtn.addEventListener("click", clickClearBtn);
if (saveBtn) saveBtn.addEventListener("click", clickSaveBtn);
if (range) range.addEventListener("input", handleRange);
반응형