Skip to content

Instantly share code, notes, and snippets.

@rista404
Created December 21, 2016 22:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rista404/2b5b35a4f17cf1e58f5a8afdaca448df to your computer and use it in GitHub Desktop.
Save rista404/2b5b35a4f17cf1e58f5a8afdaca448df to your computer and use it in GitHub Desktop.
Funky paint app
<!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