Created
December 21, 2016 22:06
-
-
Save rista404/2b5b35a4f17cf1e58f5a8afdaca448df to your computer and use it in GitHub Desktop.
Funky paint app
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>HTML5 Canvas</title> | |
<style> | |
* { | |
box-sizing: border-box; | |
} | |
html, body { | |
margin:0; | |
} | |
canvas { | |
cursor: url(http://cur.cursors-4u.net/others/oth-2/oth198.cur), progress !important; | |
} | |
.hide { | |
opacity: 0.1; | |
pointer-events: none; | |
} | |
.options, .tools { | |
position: fixed; | |
bottom: 25px; | |
left: 25px; | |
color: white; | |
font-family: "Inconsolata", Monospace; | |
display: flex; | |
flex-direction: column; | |
font-size: 14px; | |
transition: all 0.3s; | |
} | |
.options { | |
background-color: black; | |
padding: 15px 25px; | |
border-radius: 5px; | |
cursor: normal; | |
user-select: none; | |
-webkit-user-select: none; | |
} | |
.options > * { | |
margin-bottom: 10px; | |
} | |
.options > *:last-child { | |
margin-bottom: 0; | |
} | |
.tools { | |
left: auto; | |
right: 25px; | |
} | |
.eraser { | |
right: 25px; | |
bottom: 25px; | |
position: fixed; | |
background-color: black; | |
padding: 15px; | |
border-radius: 5px; | |
transition: all 0.3s; | |
cursor: pointer; | |
box-sizing: initial; | |
} | |
.eraser.active { | |
background-color: #3EE178; | |
} | |
label { | |
display: flex; | |
align-items: center; | |
} | |
label input, label select { | |
margin-left: 12px; | |
} | |
input[type="text"], input[type="number"], select { | |
background-color: transparent !important; | |
border: none; | |
border-radius: 1px; | |
border-bottom: 2px solid white; | |
padding: 3px 2px; | |
color: white !important; | |
font-size: 16px; | |
transition: all 0.3s; | |
-webkit-appearance: none; | |
appearance: none; | |
} | |
input[type="text"]:focus, input[type="number"]:focus, select { | |
outline: none; | |
background-color: white; | |
color: black; | |
} | |
</style> | |
</head> | |
<body> | |
<canvas id="draw" width="800" height="800"></canvas> | |
<section class="options"> | |
<label for="minLineWidth">Minimal Line Width: <input type="number" class="option" id="minLineWidth"></label> | |
<label for="maxLineWidth">Maximal Line Width: <input type="number" class="option" id="maxLineWidth"></label> | |
<label for="lineWidthChangeRation">Brush change ratio: <input type="range" min="0.1" step="0.1" max="2" class="option" id="lineWidthChangeRation"></label> | |
<label for="colorChangeRation">Color change ratio: <input type="range" min="0.1" step="0.1" max="2" class="option" id="colorChangeRation"></label> | |
<label for="globalCompositeOperation">Blending Style: | |
<select class="option" id="globalCompositeOperation" data-canvas-attr="true"> | |
<!-- <option value="source-over">source-over</option> | |
<option value="source-in">source-in</option> | |
<option value="source-out">source-out</option> | |
<option value="source-atom">source-atom</option> | |
<option value="destination-over">destination-over</option> | |
<option value="destination-in">destination-in</option> | |
<option value="destination-out">destination-out</option> | |
<option value="destination-atom">destination-atom</option> | |
<option value="lighter">lighter</option> | |
<option value="copy">copy</option> --> | |
<option value="xor">xor</option> | |
<option value="multiply">multiply</option> | |
<option value="screen">screen</option> | |
<option value="overlay">overlay</option> | |
<option value="darken">darken</option> | |
<option value="lighten">lighten</option> | |
<option value="color-burn">color-burn</option> | |
<option value="hard-light">hard-light</option> | |
<option value="soft-light">soft-light</option> | |
<option value="difference">difference</option> | |
<option value="exclusion">exclusion</option> | |
<option value="saturation">saturation</option> | |
<option value="color">color</option> | |
<option value="luminosity">luminosity</option> | |
</select> | |
</label> | |
</section> | |
<svg viewBox="0 0 39.359 56.40125" height="40px" class="eraser"> | |
<path xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" fill="white" stroke="white" d="M29.436,7.759c2.144,2.829,0.596,7.26,2.395,10.261 c-1.068-0.157-1.094,0.73-1.027,1.709c-0.468,11.051-0.044,21.819-1.709,31.807c-5.327,2.279-14.534,4.147-19.836,0 C7.314,40.122,8.358,19.92,10.968,9.811C15.438,5.363,24.085,4.138,29.436,7.759z M11.652,14.258 c0.054-0.403-0.06-0.971,0.342-1.026c0.401,1.894-1.202,1.987-0.342,3.42c6.23,0.255,10.641-0.861,17.1-1.026 C32.864,4.135,9.094,6.869,11.652,14.258z M10.968,19.388c0.143,8.28-1.274,14.39-1.026,22.914 c5.789-0.182,11.761-0.385,18.467-1.025c0.407-8.363,0.895-14.747,0.685-22.916C23.844,17.478,15.33,19.77,10.968,19.388z M9.942,45.037c-0.317,9.209,10.312,5.976,17.1,5.814c1.007-1.729,0.954-4.518,1.367-6.841 C21.467,44.302,16.003,44.631,9.942,45.037z"/> | |
</svg> | |
<script> | |
// | |
// Canvas | |
// | |
const canvas = document.querySelector('#draw') | |
const ctx = canvas.getContext('2d') | |
// | |
// Config | |
// | |
const config = { | |
lineCap: 'round', | |
lineJoin: 'round', | |
minLineWidth: 5, | |
maxLineWidth: 100, | |
initialHSLValue: 266, | |
lineWidthChangeRation: 0.2, | |
colorChangeRation: 1, | |
globalCompositeOperation: 'color' | |
} | |
const optionsContainer = document.querySelector('.options') | |
const options = document.querySelectorAll('.option') | |
options.forEach(option => { | |
option.value = config[option.id] | |
option.addEventListener('change', (e) => { | |
config[e.target.id] = e.target.value | |
if(e.target.dataset.canvasAttr) ctx[e.target.id] = e.target.value | |
}) | |
}) | |
const eraser = document.querySelector('.eraser') | |
eraser.addEventListener('click', (e) => { | |
e.stopPropagation() | |
const el = e.target | |
el.classList.toggle('active') | |
ctx.globalCompositeOperation = ctx.globalCompositeOperation === 'destination-out' | |
? config.globalCompositeOperation | |
: 'destination-out' | |
}) | |
// | |
// Utils | |
// | |
const setCanvasSize = (canvas, width = window.innerWidth, height = window.innerHeight) => [canvas.width, canvas.height] = [width, height] | |
const hslColor = (n) => `hsl(${n}, 100%, 50%)` | |
const getPos = (e) => ({ offsetX: x, offsetY: y } = e) | |
const updateLastPos = (x, y) => [lastX, lastY] = [x, y] | |
// | |
// Initial config | |
// | |
let isDrawing = false | |
let lastX = 0, lastY = 0 | |
let lineWidth = config.minLineWidth | |
let hsl = config.initialHSLValue | |
let lineWidthDirection = config.lineWidthChangeRation | |
setCanvasSize(canvas) | |
ctx.lineWidth = lineWidth | |
ctx.strokeStyle = hslColor(hsl) | |
ctx.lineCap = 'round' | |
ctx.lineJoin = 'round' | |
ctx.globalCompositeOperation = config.globalCompositeOperation | |
// | |
// Functionality | |
// | |
const setColor = (c) => ctx.strokeStyle = c | |
const setLineWidth = (w) => ctx.lineWidth = w | |
const updateLineWidthDirection = (lineWidth) => { | |
// Direction of line width | |
if(lineWidth >= config.maxLineWidth) lineWidthDirection = -config.lineWidthChangeRation | |
if(lineWidth <= config.minLineWidth) lineWidthDirection = +config.lineWidthChangeRation | |
} | |
const updateLineWidth = (direction) => lineWidth = lineWidth + lineWidthDirection | |
function draw(e) { | |
// Get X and Y position | |
const { x, y } = getPos(e) | |
// Draw Line | |
ctx.beginPath() | |
ctx.moveTo(lastX, lastY) | |
ctx.lineTo(x, y) | |
ctx.stroke() | |
// Increment or reset HSL value | |
hsl = hsl >= 360 ? 1 : hsl + Number(config.colorChangeRation) | |
setColor(hslColor(hsl)) | |
// Increment or decrement line width | |
updateLineWidthDirection(lineWidth) | |
updateLineWidth(lineWidthDirection) | |
setLineWidth(lineWidth) | |
updateLastPos(x, y) | |
} | |
// Events | |
canvas.addEventListener('mousedown', (e) => { | |
updateLastPos(e.offsetX, e.offsetY) | |
optionsContainer.classList.add('hide') | |
eraser.classList.add('hide') | |
isDrawing = true | |
}) | |
window.addEventListener('mouseup', () => { | |
isDrawing = false | |
optionsContainer.classList.remove('hide') | |
eraser.classList.remove('hide') | |
}) | |
window.addEventListener('mouseout', () => { | |
isDrawing = false | |
optionsContainer.classList.remove('hide') | |
eraser.classList.remove('hide') | |
}) | |
window.addEventListener('mousemove', (e) => isDrawing && draw(e) ) | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment