<canvas id="canvas"></canvas>
<h1 class="ttl">let's drawing.</h1>
body {
background: #e3e3e3;
overflow: hidden;
}
.ttl {
position: absolute;
top: 50%;
margin-top: -25px;
width: 100%;
color: #d3d3d3;
font: 50px AvenirNext-Heavy;
text-align: center;
text-shadow: 0 0 2px rgba(0, 0, 0, .1);
user-select: none;
pointer-events: none;
}
'use strict';
// NameSpace
((win) => {
win.App = win.App || {};
})(this);
// Util
((win) => {
win.App = win.App || {};
win.App.Util = win.App.Util || {};
function buildGetAve(opt_maxLength) {
const array = [];
const maxLength = opt_maxLength || 10;
let index = 0;
function _ave() {
const length = array.length;
let sum = 0;
for (let i = 0; i < length; ++i) {
sum += array[i];
}
return sum / length;
}
function getAve(val) {
array[index] = val;
index = (index + 1) % maxLength;
return _ave();
}
return getAve;
}
win.App.Util = {
buildGetAve
};
})(this);
// EventDispatcher
((win, doc, ns) => {
function EventDispatcher() {
this._events = {};
}
EventDispatcher.prototype.hasEventListener = function(eventName) {
return !!this._events[eventName];
};
EventDispatcher.prototype.addEventListener = function(eventName, callback) {
console.log(this);
if (this.hasEventListener(eventName)) {
const events = this._events[eventName];
const length = events.length;
for (let i = 0; i < length; i++) {
if (events[i] === callback) {
return;
}
}
events.push(callback);
} else {
this._events[eventName] = [callback];
}
return this;
};
EventDispatcher.prototype.removeEventListener = function(eventName, callback) {
if (!this.hasEventListener(eventName)) {
return;
} else {
const events = this._events[eventName];
let i = events.length;
let index;
while (i--) {
if (events[i] === callback) {
index = i;
}
}
if (typeof index === 'number') {
events.splice(index, 1);
}
}
return this;
};
EventDispatcher.prototype.fireEvent = function(eventName, opt_this, opt_arg) {
if (!this.hasEventListener(eventName)) {
return;
} else {
const events = this._events[eventName];
const copyEvents = [ ...events ];
const arg = [ ...arguments ];
const length = events.length;
// eventNameとopt_thisを削除
arg.splice(0, 2);
for (let i = 0; i < length; i++) {
copyEvents[i].apply(opt_this || this, arg);
}
}
};
ns.EventDispatcher = EventDispatcher;
})(this, document, App);
// Throttle
((win, doc, ns) => {
function Throttle(opt_interval, opt_callback) {
this._timer = null;
this._lastEventTime = 0;
this._interval = opt_interval || 500;
this._callback = opt_callback || function() {};
}
Throttle.prototype.setInterval = function(ms) {
this._interval = ms;
};
Throttle.prototype.addEvent = function(fn) {
this._callback = fn;
};
Throttle.prototype.fireEvent = function(opt_arg) {
const _this = this;
const currentTime = Date.now();
const timerInterval = this._interval / 10;
clearTimeout(this.timer);
if (currentTime - _this._lastEventTime > _this._interval) {
_fire();
} else {
_this.timer = setTimeout(_fire, timerInterval);
}
function _fire() {
_this._callback.call(_this, opt_arg || null);
_this._lastEventTime = currentTime;
}
};
ns.Throttle = Throttle;
})(this, document, App);
// Point
((win, doc, ns) => {
function Point(x, y, opt_size) {
const _this = this;
_init();
function _init() {
ns.EventDispatcher.call(_this);
}
_this.x = x;
_this.y = y;
_this.size = opt_size || 1;
}
Point.prototype = new ns.EventDispatcher();
Point.prototype.constructor = Point;
Point.prototype.setSize = function(size) {
this.size = size;
};
Point.prototype.draw = function(ctx, opt_size) {
const size = opt_size || this.size || 1;
ctx.save();
ctx.beginPath();
ctx.arc(this.x, this.y, size, 0, Math.PI * 2, false);
ctx.fill();
ctx.moveTo(this.x, this.y);
ctx.restore();
return this;
};
Point.getDistance = function(pointA, pointB) {
return Math.sqrt(Math.pow(pointB.x - pointA.x, 2) + Math.pow(pointB.y - pointA.y, 2));
};
ns.Point = Point;
})(this, document, App);
// Line
((win, doc, ns) => {
let Point = ns.Point;
function Line(opt_point) {
const _this = this;
const pointList = [];
_init();
function _init() {
ns.EventDispatcher.call(_this);
_this.pointList = pointList;
if (opt_point) {
_this.push(opt_point);
}
}
}
Line.prototype = new ns.EventDispatcher();
Line.prototype.constructor = Line;
Line.prototype.push = function(pointModel) {
this.pointList.push(pointModel);
};
Line.prototype.drawLine = function(ctx) {
const pointList = this.pointList;
const length = pointList.length;
ctx.save();
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
if (length > 1) {
for (let i = 1; i < length; ++i) {
ctx.beginPath();
ctx.moveTo(pointList[i - 1].x, pointList[i - 1].y);
ctx.lineTo(pointList[i].x, pointList[i].y);
ctx.lineWidth = pointList[i].size;
ctx.stroke();
}
} else {
pointList[0].draw(ctx);
}
ctx.restore();
};
Line.prototype.drawQuadraticCurve = function(ctx) {
const pointList = this.pointList;
const length = pointList.length;
const quadraticPointList = [];
let lastIndex = 0;
ctx.save();
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
if (length > 1) {
quadraticPointList[lastIndex] = pointList[0];
ctx.beginPath();
ctx.moveTo(quadraticPointList[0].x, quadraticPointList[0].y);
for (let i = 1; i < length; ++i) {
quadraticPointList[++lastIndex] = new Point(
(quadraticPointList[lastIndex - 1].x + pointList[i].x) / 2,
(quadraticPointList[lastIndex - 1].y + pointList[i].y) / 2
);
quadraticPointList[++lastIndex] = (pointList[i]);
ctx.quadraticCurveTo(
quadraticPointList[i * 2 - 2].x,
quadraticPointList[i * 2 - 2].y,
quadraticPointList[i * 2 - 1].x,
quadraticPointList[i * 2 - 1].y
);
ctx.lineWidth = pointList[i].size;
ctx.stroke();
ctx.beginPath();
ctx.moveTo(quadraticPointList[i * 2 - 1].x, quadraticPointList[i * 2 - 1].y);
}
ctx.lineTo(quadraticPointList[lastIndex].x, quadraticPointList[lastIndex].y);
ctx.stroke();
} else {
pointList[lastIndex].draw(ctx);
}
ctx.restore();
};
ns.Line = Line;
})(this, document, App);
// LineManager
((win, doc, ns) => {
let instance;
function getInstance() {
if (!instance) {
instance = new LineManager();
}
return instance;
}
function LineManager() {
const _this = this;
const lineList = [];
_init();
function _init() {
ns.EventDispatcher.call(_this);
}
_this.lineList = lineList;
}
LineManager.prototype = new ns.EventDispatcher();
LineManager.prototype.constructor = LineManager;
LineManager.prototype.push = function(lineModel) {
this.lineList.push(lineModel);
};
LineManager.prototype.addPoint = function(pointModel, opt_index) {
const index = opt_index || this.lineList.length - 1;
this.lineList[index].push(pointModel);
};
LineManager.prototype.drawQuadraticCurve = function(ctx) {
const lineList = this.lineList;
const length = lineList.length;
if (length) {
lineList[length - 1].drawQuadraticCurve(ctx);
}
};
LineManager.prototype.clear = function() {
let _this = this;
_this.lineList = [];
};
ns.LineManager = {
getInstance: getInstance
};
})(this, document, App);
// main
((win, doc, ns) => {
const SIZE = 10;
const _getAve = win.App.Util.buildGetAve(10);
function _getSize(l) {
const MAX_DISTANCE = 100;
let width;
if (l > MAX_DISTANCE) {
width = 1;
}
width = (MAX_DISTANCE - l) * SIZE / MAX_DISTANCE;
return _getAve(width);
}
const START_EVENT = 'mousedown';
const MOVE_EVENT = 'mousemove';
const END_EVENT = 'mouseup';
const lineManager = ns.LineManager.getInstance();
const canvas = doc.getElementById('canvas');
const ctx = canvas.getContext('2d');
const sub = doc.createElement('canvas');
const subCtx = sub.getContext('2d');
setup();
async function setup() {
addSketchEventForCanvas(canvas);
canvas.width = sub.width = win.innerWidth;
canvas.height = sub.height = win.innerHeight;
tick();
}
function tick() {
draw();
requestAnimationFrame(tick);
}
function draw() {
canvas.width = win.innerWidth;
canvas.height = win.innerHeight;
ctx.drawImage(sub, 0, 0);
ctx.save();
ctx.fillStyle = ctx.strokeStyle = 'rgba(50, 50, 50, 1)';
lineManager.drawQuadraticCurve(ctx);
ctx.restore();
}
function addSketchEventForCanvas(elm) {
const throttle = new ns.Throttle(10, handleThrottleMoveEvent);
let lastPoint = null;
elm.addEventListener(START_EVENT, handleStartEvent, false);
doc.addEventListener(END_EVENT, handleEndEvent, false);
function handleThrottleMoveEvent(evt) {
let currentPoint = new ns.Point(evt.offsetX, evt.offsetY);
currentPoint.setSize(_getSize(ns.Point.getDistance(lastPoint, currentPoint)));
lastPoint = currentPoint;
lineManager.addPoint(currentPoint);
}
function handleStartEvent(evt) {
lastPoint = new ns.Point(evt.offsetX, evt.offsetY, SIZE / 2);
lineManager.push(new ns.Line(lastPoint));
elm.addEventListener(MOVE_EVENT, handleMoveEvent, false);
}
function handleMoveEvent(evt) {
throttle.fireEvent(evt);
}
function handleEndEvent(evt) {
sub.width = win.innerWidth;
sub.height = win.innerHeight;
subCtx.drawImage(canvas, 0, 0);
elm.removeEventListener(MOVE_EVENT, handleMoveEvent, false);
lastPoint = null;
}
}
})(this, document, App);
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.