본문 바로가기

프론트엔드/자바스크립트

프로젝트 - 그림판

반응형

그림판 애플리케이션

 

index.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">
  <title>Document</title>

  <script src="https://kit.fontawesome.com/833be080c6.js" crossorigin="anonymous"></script>
  <link rel="stylesheet" href="./main.css">
  <script defer src="./index.js"></script>
</head>

<body>
  <canvas></canvas>

  <div class="controls">
    <select class="size control" title="line size">
      <option value="5">5</option>
      <option value="10">10</option>
      <option value="15">15</option>
      <option value="20">20</option>
      <option value="25">25</option>
      <option value="30">30</option>
      <option value="35">35</option>
      <option value="40">40</option>
      <option value="45">45</option>
      <option value="50">50</option>
    </select>

    <input type="color" class="line-color control" title="font color">

    <input type="color" class="background-color control" title="background color">

    <button class="control" title="save image">
      <i class="fas fa-save"></i>
    </button>

    <button class="control">
      <i class="fas fa-redo" title="clear"></i>
    </button>

    <button class="control">
      <i class="fas fa-paste" title="copy"></i>
    </button>

    <button class="control">
      <i class="fas fa-upload" title="load"></i>
    </button>

    <button class="control">
      <i class="fas fa-trash-alt" title="delete"></i>
    </button>

    <button class="control">
      <i class="fas fa-brush" title="draw"></i>
    </button>

    <button class="control">
      <i class="fas fa-eraser" title="eraser"></i>
    </button>

    <button class="control">
      <i class="fas fa-arrow-left" title="rollback"></i>
    </button>
  </div>

  <div class="modal hide">
    <div class="shadow"></div>
    <div class="card">
      <input type="text" class="download-title" title="title" placeholder="Title">
      <select name="mime" id="" class="mime">
        <option value="png">png</option>
        <option value="jpg">jpg</option>
        <option value="jpeg">jpeg</option>
      </select>
      <a id="download">
        <button class="btn-download-confirm">Save</button>
      </a>
    </div>
  </div>
</body>

</html>

 

main.css

@import url('https:fonts.googleapis.com/css?family=Poppins&display=swap');

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  display: flex;
  flex-direction: column;  
  height: 100vh;  
  overflow: hidden;
}

canvas {      
  background-color: aliceblue;
}

.controls {
  height: 50px;
  background-color: rebeccapurple;
  display: flex;
  align-items: center;
  justify-content: space-around;
}

.control {
  background-color: #f0f8ff;
  border-radius: 5px;
  height: 40px;
  width: 100px;
  font-size: 1.9rem;
  padding: 0 10px;
  cursor: pointer;
  transition: .4 ease;
  border: none;
  box-shadow: 1px 1px 2px 0 rgba(0, 0, 0, .3);
  margin: 5px;
}

button.control:hover {
  filter: brightness(110%);
}

button.control:active {
  transform: scale(.98);
}

.modal {
  height: 100vh;
  width: 100%;
  position: fixed;
  top: 0;
  left: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.modal.hide {
  display: none;
}

.shadow {
  height: 100vh;
  width: 100%;
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgba(0, 0, 0, .3);  
}

.modal .card {
  z-index: 1; 
  border: 3px solid #efefef; 
}

.modal .card input, select, button {
  height: 30px;
  border: none;
  padding: 0 10px;
  transition: .4 ease;
}

.modal .card input:focus {
  outline: none;
}

.modal .card button:active {
  transform: scale(.98);
}

 

index.js

// elements
const canvasEl = document.querySelector('canvas')
const statusEl = document.querySelector('.status')
const sizeEl = document.querySelector('.size')
const lineColorEl = document.querySelector('.line-color')
const backgroundColorEl = document.querySelector('.background-color')
const btnControlsEl = document.querySelectorAll('button')
const modalEl = document.querySelector('.modal')
const btnSave = document.getElementById('download')

// variables
const cv = canvasEl.getContext('2d')
let x
let x2
let y
let y2
let isMouseDown = false
let lineColor
let backgroundColor =
  '#' +
  getComputedStyle(canvasEl)
    .backgroundColor.replace(/[^\d,]/g, '')
    .split(',')
    .map((n) => parseInt(n).toString(16))
    .join('')
let lineSize
let eraserOn = false

let routes = []
let drawings = []

// event handlers
window.addEventListener('load', (e) => {
  canvasEl.height = window.innerHeight - 50
  canvasEl.width = window.innerWidth
  console.log(canvasEl.height, canvasEl.width)
  backgroundColorEl.value = backgroundColor
})

window.addEventListener('resize', (e) => {
  canvasEl.height = window.innerHeight - 50
  canvasEl.width = window.innerWidth
  console.log('resize: ', canvasEl.height, canvasEl.width)
})

canvasEl.addEventListener('mousedown', (e) => {
  x = e.offsetX
  y = e.offsetY
  isMouseDown = true
})

canvasEl.addEventListener('mousemove', (e) => {
  if (isMouseDown) {
    x2 = e.offsetX
    y2 = e.offsetY
    cv.beginPath()
    drawLine()
    x = x2
    y = y2
  }
})

canvasEl.addEventListener('mouseup', (e) => {
  isMouseDown = false
  drawings.push(routes)
  routes = []
})

lineColorEl.addEventListener('change', (e) => {
  lineColor = e.target.value
})

backgroundColorEl.addEventListener('change', (e) => {
  canvasEl.style.backgroundColor = e.target.value
})

sizeEl.addEventListener('change', (e) => {
  lineSize = e.target.value
})

btnControlsEl.forEach((btn, idx) => {
  btn.addEventListener('click', () => {
    switch (idx) {
      case 0:
        downloadImage()
        break

      case 1:
        cv.clearRect(0, 0, canvasEl.width, canvasEl.height)
        routes = []
        drawings = []
        break

      case 2:
        saveDrawing()
        break

      case 3:
        loadDrawing()
        break

      case 4:
        deleteDrawing()
        break

      case 5:
        eraserOn = false
        break

      case 6:
        eraserOn = true
        break

      case 7:
        removeLastDrawing()
        break
    }
    console.log(idx)
  })
})

btnSave.addEventListener('click', () => {
  const fileName = document.querySelector('.download-title').value
  const ext = document.querySelector('.mime').value

  console.log(fileName + '.' + ext)

  btnSave.href = canvasEl.toDataURL(`image/${ext}`, 1.0)
  btnSave.download = fileName + '.' + ext
  modalEl.classList.add('hide')
})

// functions
function drawLine() {
  cv.moveTo(x, y)
  cv.lineTo(x2, y2)
  cv.strokeStyle = lineColor
  cv.lineWidth = lineSize

  cv.arc(x, y, lineSize, 0, Math.PI * 2)
  cv.fillStyle = lineColor

  routes.push({
    x: x,
    y: y,
    color: lineColor,
    width: lineSize,
  })

  if (eraserOn) {
    cv.globalCompositeOperation = 'destination-out'
  } else {
    cv.globalCompositeOperation = 'source-over'
  }
  cv.stroke()
  cv.fill()
}

function downloadImage() {
  modalEl.classList.remove('hide')
}

function saveDrawing() {
  if (drawings.length > 0) {
    localStorage.setItem('drawing', JSON.stringify(drawings))
  }
}

function loadDrawing() {
  if (localStorage.getItem('drawing')) {
    drawings = JSON.parse(localStorage.getItem('drawing'))
    redrawLoadedDrawing()
  }
}

function deleteDrawing() {
  if (localStorage.getItem('drawing')) {
    localStorage.removeItem('drawing')
  }
}

function redrawLoadedDrawing() {
  drawings.forEach((drawing) => {
    for (let i = 0; i < drawing.length - 1; i++) {
      cv.moveTo(drawing[i].x, drawing[i].y)
      cv.lineTo(drawing[i + 1].x, drawing[i + 1].y)
      cv.strokeStyle = drawing[i].color
      cv.lineWidth = drawing[i].width

      cv.arc(drawing[i].x, drawing[i].y, drawing[i].width, 0, Math.PI * 2)
      cv.fillStyle = drawing[i].color

      cv.stroke()
      cv.fill()
    }
  })
}

function removeLastDrawing() {
  cv.globalCompositeOperation = 'destination-out'
  if (drawings.length > 0) {
    drawings[drawings.length - 1].forEach((route) => {
      cv.lineTo(route.x, route.y)
      cv.strokeStyle = route.color
      cv.lineWidth = route.width

      cv.arc(route.x, route.y, route.width, 0, Math.PI * 2)
      cv.fillStyle = route.color

      cv.stroke()
      cv.fill()
    })
    drawings.pop()
  }
  cv.globalCompositeOperation = 'source-over'
}

 

 

데모

 

Document

 

jin-co-jcg.vercel.app

 

728x90
반응형