Skip to content

Instantly share code, notes, and snippets.

@dzhang123
Forked from anonymous/Painting.markdown
Created March 7, 2016 02:28
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 dzhang123/0d676a5c9f0a3d06d908 to your computer and use it in GitHub Desktop.
Save dzhang123/0d676a5c9f0a3d06d908 to your computer and use it in GitHub Desktop.
Painting
<div id="message">Drag mouse to paint.</div>
<canvas id='c'></canvas>
/**
* requestAnimationFrame
*/
window.requestAnimationFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
/**
* Brush
*/
var Brush = (function() {
/**
* @constructor
* @public
*/
function Brush(x, y, color, size, inkAmount) {
this.x = x || 0;
this.y = y || 0;
if (color !== undefined) this.color = color;
if (size !== undefined) this.size = size;
if (inkAmount !== undefined) this.inkAmount = inkAmount;
this._drops = [];
this._resetTip();
}
Brush.prototype = {
_SPLASHING_BRUSH_SPEED: 75,
x: 0,
y: 0,
color: '#000',
size: 35,
inkAmount: 7,
splashing: true,
dripping: true,
_latestPos: null,
_strokeId: null,
_drops: null,
isStroke: function() {
return Boolean(this._strokeId);
},
startStroke: function() {
if (this.isStroke()) return;
this._resetTip();
// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
this._strokeId = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r, v;
r = Math.random() * 16 | 0;
v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
},
endStroke: function() {
this._strokeId = this._latestPos = null;
},
render: function(ctx, x, y) {
var isStroke = this.isStroke(),
dx, dy,
i, len;
if (!this._latestPos) this._latestPos = {};
this._latestPos.x = this.x;
this._latestPos.y = this.y;
this.x = x;
this.y = y;
if (this._drops.length) {
var drops = this._drops,
drop,
sizeSq = this.size * this.size;
for (i = 0, len = drops.length; i < len; i++) {
drop = drops[i];
dx = this.x - drop.x;
dy = this.y - drop.y;
if (
(isStroke && sizeSq > dx * dx + dy * dy && this._strokeId !== drop.strokeId) ||
drop.life <= 0
) {
drops.splice(i, 1);
len--;
i--;
continue;
}
drop.render(ctx);
}
}
if (isStroke) {
var tip = this._tip,
strokeId = this._strokeId,
dist;
dx = this.x - this._latestPos.x;
dy = this.y - this._latestPos.y;
dist = Math.sqrt(dx * dx + dy * dy);
if (this.splashing && dist > this._SPLASHING_BRUSH_SPEED) {
var maxNum = (dist - this._SPLASHING_BRUSH_SPEED) * 0.5 | 0,
r, a, sr, sx, sy;
ctx.save();
ctx.fillStyle = this.color;
ctx.beginPath();
for (i = 0, len = maxNum * Math.random() | 0; i < len; i++) {
r = (dist - 1) * Math.random() + 1;
a = Math.PI * 2 * Math.random();
sr = 5 * Math.random();
sx = this.x + r * Math.sin(a);
sy = this.y + r * Math.cos(a);
ctx.moveTo(sx + sr, sy);
ctx.arc(sx, sy, sr, 0, Math.PI * 2, false);
}
ctx.fill();
ctx.restore();
} else if (this.dripping && dist < this.inkAmount * 2 && Math.random() < 0.05) {
this._drops.push(new Drop(
this.x,
this.y,
(this.size + this.inkAmount) * 0.5 * ((0.25 - 0.1) * Math.random() + 0.1),
this.color,
this._strokeId
));
}
for (i = 0, len = tip.length; i < len; i++) {
tip[i].render(ctx, dx, dy, dist);
}
}
},
dispose: function() {
this._tip.length = this._drops.length = 0;
},
_resetTip: function() {
var tip = this._tip = [],
rad = this.size * 0.5,
x0, y0, a0, x1, y1, a1, cv, sv,
i, len;
a1 = Math.PI * 2 * Math.random();
len = rad * rad * Math.PI / this.inkAmount | 0;
if (len < 1) len = 1;
for (i = 0; i < len; i++) {
x0 = rad * Math.random();
y0 = x0 * 0.5;
a0 = Math.PI * 2 * Math.random();
x1 = x0 * Math.sin(a0);
y1 = y0 * Math.cos(a0);
cv = Math.cos(a1);
sv = Math.sin(a1);
tip.push(new Hair(
this.x + x1 * cv - y1 * sv,
this.y + x1 * sv + y1 * cv,
this.inkAmount,
this.color
));
}
}
};
/**
* Hair
* @private
*/
function Hair(x, y, inkAmount, color) {
this.x = x || 0;
this.y = y || 0;
this.inkAmount = inkAmount;
this.color = color;
this._latestPos = { x: this.x, y: this.y };
}
Hair.prototype = {
x: 0,
y: 0,
inkAmount: 7,
color: '#000',
_latestPos: null,
render: function(ctx, offsetX, offsetY, offsetLength) {
this._latestPos.x = this.x;
this._latestPos.y = this.y;
this.x += offsetX;
this.y += offsetY;
var per = offsetLength ? this.inkAmount / offsetLength : 0;
if (per > 1) per = 1;
else if (per < 0) per = 0;
ctx.save();
ctx.lineCap = ctx.lineJoin = 'round';
ctx.strokeStyle = this.color;
ctx.lineWidth = this.inkAmount * per;
ctx.beginPath();
ctx.moveTo(this._latestPos.x, this._latestPos.y);
ctx.lineTo(this.x, this.y);
ctx.stroke();
ctx.restore();
}
};
/**
* Drop
* @private
*/
function Drop(x, y, size, color, strokeId) {
this.x = x || 0;
this.y = y || 0;
this.size = size;
this.color = color;
this.strokeId = strokeId;
this.life = this.size * 1.5;
this._latestPos = { x: this.x, y: this.y };
}
Drop.prototype = {
x: 0,
y: 0,
size: 7,
color: '#000',
strokeId: null,
life: 0,
_latestPos: null,
_xOffRatio: 0,
render: function(ctx) {
if (Math.random() < 0.03) {
this._xOffRatio += 0.06 * Math.random() - 0.03;
} else if (Math.random() < 0.1) {
this._xOffRatio *= 0.003;
}
this._latestPos.x = this.x;
this._latestPos.y = this.y;
this.x += this.life * this._xOffRatio;
this.y += (this.life * 0.5) * Math.random();
this.life -= (0.05 - 0.01) * Math.random() + 0.01;
ctx.save();
ctx.lineCap = ctx.lineJoin = 'round';
ctx.strokeStyle = this.color;
ctx.lineWidth = this.size + this.life * 0.3;
ctx.beginPath();
ctx.moveTo(this._latestPos.x, this._latestPos.y);
ctx.lineTo(this.x, this.y);
ctx.stroke();
ctx.restore();
ctx.restore();
}
};
return Brush;
})();
// Initialize
(function() {
// Vars
var canvas, context,
centerX, centerY,
mouseX = 0, mouseY = 0, isMouseDown = false,
brush,
gui, control, guiColorCtr, guiSizeCtr, guiIsRandColorCtr;
// Event Listeners
function resize(e) {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
centerX = canvas.width * 0.5;
centerY = canvas.height * 0.5;
context = canvas.getContext('2d');
control.clear();
}
function mouseMove(e) {
mouseX = e.clientX;
mouseY = e.clientY;
}
function mouseDown(e) {
mouseX = e.clientX;
mouseY = e.clientY;
if (control.isRandomColor) {
brush.color = randomColor();
guiColorCtr.updateDisplay();
}
if (control.isRandomSize) {
brush.size = random(51, 5) | 0;
guiSizeCtr.updateDisplay();
}
brush.startStroke(mouseX, mouseY);
}
function mouseUp(e) {
brush.endStroke();
}
var touched = false;
function touchMove(e) {
var t = e.touches[0];
mouseX = t.clientX;
mouseY = t.clientY;
}
function touchStart(e) {
if (touched) return;
touched = true;
var t = e.touches[0];
mouseX = t.clientX;
mouseY = t.clientY;
if (control.isRandomColor) {
brush.color = randomColor();
guiColorCtr.updateDisplay();
}
if (control.isRandomSize) {
brush.size = random(51, 5) | 0;
guiSizeCtr.updateDisplay();
}
brush.startStroke(mouseX, mouseY);
}
function touchEnd(e) {
touched = false;
brush.endStroke();
}
// Helpers
function randomColor() {
var r = random(256) | 0,
g = random(256) | 0,
b = random(256) | 0;
return 'rgb(' + r + ',' + g + ',' + b + ')';
}
function random(max, min) {
if (typeof max !== 'number') {
return Math.random();
} else if (typeof min !== 'number') {
min = 0;
}
return Math.random() * (max - min) + min;
}
// GUI Control
control = {
isRandomColor: true,
isRandomSize: false,
clear: function(e) {
context.clearRect(0, 0, canvas.width, canvas.height);
brush.dispose();
}
};
// Init
canvas = document.getElementById('c');
brush = new Brush(centerX, centerY, randomColor());
window.addEventListener('resize', resize, false);
resize(null);
canvas.addEventListener('mousemove', mouseMove, false);
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseout', mouseUp, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('touchmove', touchMove, false);
canvas.addEventListener('touchstart', touchStart, false);
canvas.addEventListener('touchcancel', touchEnd, false);
canvas.addEventListener('touchend', touchEnd, false);
// GUI
gui = new dat.GUI();
guiColorCtr = gui.addColor(brush, 'color').name('Color').onChange(function() {
control.isRandomColor = false;
guiIsRandColorCtr.updateDisplay();
});
guiSizeCtr = gui.add(brush, 'size', 5, 50).name('Size');
gui.add(brush, 'inkAmount', 1, 30).name('Ink Amount');
gui.add(brush, 'splashing').name('Splashing');
gui.add(brush, 'dripping').name('Dripping');
guiIsRandColorCtr = gui.add(control, 'isRandomColor').name('Random Color');
gui.add(control, 'isRandomSize').name('Random Size');
gui.add(control, 'clear').name('Clear');
gui.close();
// Start Update
var loop = function() {
brush.render(context, mouseX, mouseY);
requestAnimationFrame(loop);
};
loop();
})();
<script src="http://dat-gui.googlecode.com/git/build/dat.gui.min.js"></script>
body {
font-family: 'Anaheim', sans-serif;
padding: 0;
margin: 0;
background: #f7f6f2;
overflow: hidden;
-webkit-user-select: none;
-moz-user-select: none;
-o-user-select: none;
-ms-user-select: none;
user-select: none;
}
canvas {
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
#message {
color: #e2e2de;
font-size: 32px;
text-align: center;
letter-spacing: 2px;
position: fixed;
top: 50%;
z-index: 0;
width: 100%;
margin-top: -0.5em;
text-shadow: 0 -1px 0 #d3d3d1;
}
.dg.ac {
z-index: 2;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment