init
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
#draw {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
display: none;
|
||||
|
||||
backdrop-filter: blur(1rem);
|
||||
}
|
||||
|
||||
#draw:before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0.75;
|
||||
background-color: rgb(var(--movim-background));
|
||||
}
|
||||
|
||||
#draw.open {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#draw .canvas {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
#draw ul.list.controls li {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#draw #draw-background {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
#draw #screen-canvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#draw #draw-canvas {
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
/* Controls */
|
||||
|
||||
#draw .draw-top-nav {
|
||||
transition: transform 0.2s;
|
||||
pointer-events: none;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
#draw .draw-top-nav span {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
#draw .draw-top-nav span.control {
|
||||
margin-right: 1.5rem;
|
||||
box-shadow: var(--elevation-1);
|
||||
}
|
||||
|
||||
#draw .draw-actions,
|
||||
#draw .draw-control {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
right: 1rem;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
#draw .draw-actions {
|
||||
left: -1rem;
|
||||
right: initial;
|
||||
}
|
||||
|
||||
#draw .draw-top-nav.drawing {
|
||||
transform: translateY(-130%);
|
||||
}
|
||||
|
||||
#draw .draw-control.drawing {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
#draw .draw-actions.drawing {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
#draw .draw-control span.icon.bubble i,
|
||||
#draw .draw-actions span.icon.bubble i {
|
||||
line-height: 5rem;
|
||||
height: 5rem;
|
||||
}
|
||||
|
||||
#draw .draw-actions li span.primary,
|
||||
#draw .draw-control li span.primary {
|
||||
height: 5rem;
|
||||
flex: 0 0 5rem;
|
||||
margin: 0.5rem 1rem;
|
||||
transition: box-shadow .3s cubic-bezier(.4,0,.2,1), transform .3s cubic-bezier(.4,0,.2,1);
|
||||
box-shadow: var(--elevation-1);
|
||||
}
|
||||
|
||||
#draw .draw-actions li span.primary:hover,
|
||||
#draw .draw-control li span.primary:hover {
|
||||
box-shadow: var(--elevation-2);
|
||||
}
|
||||
|
||||
#draw .draw-actions li.selected span.primary,
|
||||
#draw .draw-control li.selected span.primary {
|
||||
box-shadow: var(--elevation-4);
|
||||
transform: scale(1.3);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#draw [data-width=small] i {
|
||||
font-size: 2rem;
|
||||
}
|
||||
#draw [data-width=medium] i {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
var Draw = {
|
||||
initCanvas: null,
|
||||
|
||||
SMALL: 4,
|
||||
MEDIUM: 6,
|
||||
BIG: 8,
|
||||
|
||||
CROP_PADDING: 0.15,
|
||||
|
||||
draw: null, // widget wrapper
|
||||
|
||||
canvas: null, // drawable
|
||||
ctx: null,
|
||||
bg: null, // non drawable, only used for background
|
||||
|
||||
drawing: false,
|
||||
mousePos: null,
|
||||
lastPos: null,
|
||||
|
||||
drawingData: null, // data structure for saving drawn points
|
||||
|
||||
control: null,
|
||||
actions: null,
|
||||
topNav: null,
|
||||
save: null, // button
|
||||
|
||||
backgroundCanvas: null, // background coming from another widget as a canvas
|
||||
bgHeight: null, // natural height of background
|
||||
bgWidth: null, // natural width of background
|
||||
ratio: 1, // upscale ratio
|
||||
|
||||
init: function () {
|
||||
MovimTpl.pushAnchorState('draw', function () {
|
||||
Draw.draw.classList.remove('bound', 'open');
|
||||
});
|
||||
|
||||
Draw.drawingData = [];
|
||||
|
||||
if (Draw.initCanvas) {
|
||||
Draw.backgroundCanvas = Draw.initCanvas;
|
||||
Draw.initCanvas = null;
|
||||
} else {
|
||||
Draw.backgroundCanvas = null;
|
||||
}
|
||||
|
||||
Draw.topNav = document.querySelector('.draw-top-nav');
|
||||
Draw.control = document.querySelector('.draw-control');
|
||||
Draw.actions = document.querySelector('.draw-actions');
|
||||
|
||||
Draw.draw = document.getElementById('draw');
|
||||
const canvasWrapper = document.querySelector('#draw .canvas');
|
||||
const colors = document.querySelectorAll('.draw-colors li');
|
||||
const widths = document.querySelectorAll('.draw-widths li');
|
||||
const eraser = document.querySelector('.draw-eraser');
|
||||
|
||||
Draw.draw.classList.add('open');
|
||||
|
||||
let height, width;
|
||||
if (Draw.backgroundCanvas) {
|
||||
Draw.bgHeight = Draw.backgroundCanvas.height;
|
||||
Draw.bgWidth = Draw.backgroundCanvas.width;
|
||||
const dheight = document.body.clientHeight;
|
||||
const dwidth = document.body.clientWidth;
|
||||
if (Draw.bgHeight <= dheight && Draw.bgWidth <= dwidth) {
|
||||
height = Draw.bgHeight;
|
||||
width = Draw.bgWidth;
|
||||
} else {
|
||||
const bgTaller = Draw.bgHeight / Draw.bgWidth > dheight / dwidth;
|
||||
if (Draw.bgHeight <= dheight || !bgTaller) {
|
||||
width = dwidth;
|
||||
height = dwidth * Draw.bgHeight / Draw.bgWidth;
|
||||
Draw.ratio = Draw.bgWidth / dwidth;
|
||||
} else if (Draw.bgWidth <= dwidth || bgTaller) {
|
||||
height = dheight;
|
||||
width = dheight * Draw.bgWidth / Draw.bgHeight;
|
||||
Draw.ratio = Draw.bgHeight / dheight;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
height = document.body.clientHeight;
|
||||
width = document.body.clientWidth;
|
||||
}
|
||||
|
||||
canvasWrapper.style.height = `${height}px`;
|
||||
canvasWrapper.style.width = `${width}px`;
|
||||
|
||||
Draw.canvas = document.getElementById('draw-canvas');
|
||||
Draw.canvas.width = width;
|
||||
Draw.canvas.height = height;
|
||||
Draw.ctx = Draw.canvas.getContext('2d');
|
||||
Draw.ctx.lineCap = 'round';
|
||||
|
||||
Draw.screen = document.getElementById('screen-canvas');
|
||||
Draw.screen.width = width;
|
||||
Draw.screen.height = height;
|
||||
Draw.sctx = Draw.screen.getContext('2d');
|
||||
Draw.sctx.lineCap = Draw.ctx.lineCap;
|
||||
|
||||
Draw.bg = document.getElementById('draw-background');
|
||||
Draw.bg.width = width;
|
||||
Draw.bg.height = height;
|
||||
bgctx = Draw.bg.getContext('2d');
|
||||
|
||||
if (Draw.backgroundCanvas) {
|
||||
// copy over background image
|
||||
bgctx.drawImage(Draw.backgroundCanvas, 0, 0, width, height);
|
||||
} else {
|
||||
// fill canvas with white
|
||||
bgctx.fillStyle = 'white';
|
||||
bgctx.fillRect(0, 0, width, height);
|
||||
}
|
||||
|
||||
// init controls
|
||||
Draw.ctx.strokeStyle = '#000';
|
||||
Draw.sctx.strokeStyle = '#000';
|
||||
colors.forEach(item => item.classList.remove('selected'));
|
||||
eraser.classList.remove('selected');
|
||||
document.querySelector('[data-color=black]').classList.add('selected');
|
||||
Draw.ctx.lineWidth = Draw.MEDIUM;
|
||||
Draw.sctx.lineWidth = Draw.MEDIUM;
|
||||
widths.forEach(item => item.classList.remove('selected'));
|
||||
document.querySelector('[data-width=medium]').classList.add('selected');
|
||||
|
||||
if (Draw.draw.classList.contains('bound')) return;
|
||||
|
||||
// Get a regular interval for drawing to the screen
|
||||
window.requestAnimFrame = (function (callback) {
|
||||
return window.requestAnimationFrame ||
|
||||
window.webkitRequestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.oRequestAnimationFrame ||
|
||||
window.msRequestAnimaitonFrame ||
|
||||
function (callback) {
|
||||
window.setTimeout(callback, 1000 / 60);
|
||||
};
|
||||
})();
|
||||
// Allow for animation
|
||||
(function drawLoop() {
|
||||
requestAnimFrame(drawLoop);
|
||||
Draw.renderCanvas();
|
||||
})();
|
||||
|
||||
Draw.screen.addEventListener('mousedown', Draw.startDrawing, true);
|
||||
Draw.screen.addEventListener('mouseenter', Draw.startDrawing, false);
|
||||
Draw.screen.addEventListener('mouseup', Draw.stopDrawing, false);
|
||||
Draw.screen.addEventListener('mouseleave', Draw.stopDrawing, false);
|
||||
Draw.screen.addEventListener('mousemove', function (e) {
|
||||
Draw.mousePos = Draw.getPos(Draw.screen, e);
|
||||
}, false);
|
||||
|
||||
// Set up touch events for mobile, etc
|
||||
Draw.screen.addEventListener('touchstart', function (e) {
|
||||
Draw.mousePos = Draw.getPos(Draw.screen, e);
|
||||
var touch = e.touches[0];
|
||||
var mouseEvent = new MouseEvent('mousedown', {
|
||||
clientX: touch.clientX,
|
||||
clientY: touch.clientY,
|
||||
buttons: 1
|
||||
});
|
||||
Draw.screen.dispatchEvent(mouseEvent);
|
||||
}, false);
|
||||
Draw.screen.addEventListener('touchend', Draw.stopDrawing, false);
|
||||
Draw.screen.addEventListener('touchmove', function (e) {
|
||||
var touch = e.touches[0];
|
||||
var mouseEvent = new MouseEvent('mousemove', {
|
||||
clientX: touch.clientX,
|
||||
clientY: touch.clientY
|
||||
});
|
||||
Draw.screen.dispatchEvent(mouseEvent);
|
||||
}, false);
|
||||
|
||||
// Clear canvas
|
||||
const clear = document.getElementById('draw-clear');
|
||||
clear.addEventListener('click', (e) => {
|
||||
const rect = Draw.screen.getBoundingClientRect();
|
||||
Draw.ctx.clearRect(0, 0, rect.width, rect.height);
|
||||
Draw.drawingData = [];
|
||||
}, false);
|
||||
|
||||
// Save (background +) drawing
|
||||
Draw.save = document.querySelector('#draw-save');
|
||||
Draw.save.onclick = (e) => {
|
||||
const finalCanvas = document.createElement('canvas');
|
||||
let crop, height, width, bg;
|
||||
|
||||
if (Draw.backgroundCanvas) {
|
||||
width = Draw.bgWidth;
|
||||
height = Draw.bgHeight;
|
||||
bg = Draw.backgroundCanvas;
|
||||
} else {
|
||||
crop = Draw.autoCropDrawing();
|
||||
width = crop.width;
|
||||
height = crop.height;
|
||||
bg = Draw.bg;
|
||||
}
|
||||
finalCanvas.setAttribute('width', width);
|
||||
finalCanvas.setAttribute('height', height);
|
||||
|
||||
const finalctx = finalCanvas.getContext('2d');
|
||||
finalctx.lineCap = 'round';
|
||||
|
||||
if (Draw.backgroundCanvas) {
|
||||
Draw.upscaleDrawing(finalctx);
|
||||
} else {
|
||||
finalctx.putImageData(crop, 0, 0);
|
||||
}
|
||||
|
||||
// add background
|
||||
finalctx.globalCompositeOperation = 'destination-over';
|
||||
finalctx.drawImage(
|
||||
bg,
|
||||
0, 0,
|
||||
width,
|
||||
height
|
||||
);
|
||||
|
||||
// Disable button
|
||||
Draw.save.classList.add('disabled');
|
||||
|
||||
finalCanvas.toBlob(
|
||||
function (blob) {
|
||||
Upload.prepare(blob);
|
||||
Upload.name = 'drawing.jpg';
|
||||
Upload.init();
|
||||
},
|
||||
'image/jpeg',
|
||||
0.85
|
||||
);
|
||||
};
|
||||
|
||||
// Use the eraser
|
||||
eraser.addEventListener('click', function (e) {
|
||||
colors.forEach(item => item.classList.remove('selected'));
|
||||
this.classList.add('selected');
|
||||
|
||||
Draw.ctx.globalCompositeOperation = 'destination-out';
|
||||
}, false);
|
||||
|
||||
// Change pencil color
|
||||
for (let i = 0; i < colors.length; i++) {
|
||||
colors[i].addEventListener('click', function (e) {
|
||||
colors.forEach(item => item.classList.remove('selected'));
|
||||
eraser.classList.remove('selected');
|
||||
this.classList.add('selected');
|
||||
|
||||
Draw.ctx.globalCompositeOperation = 'source-over';
|
||||
Draw.ctx.strokeStyle = window.getComputedStyle(colors[i].querySelector('span.primary')).backgroundColor;
|
||||
Draw.sctx.strokeStyle = window.getComputedStyle(colors[i].querySelector('span.primary')).backgroundColor;
|
||||
});
|
||||
}
|
||||
|
||||
// Change pencil thickness
|
||||
for (let i = 0; i < widths.length; i++) {
|
||||
widths[i].addEventListener('click', function (e) {
|
||||
widths.forEach(item => item.classList.remove('selected'));
|
||||
this.classList.add('selected');
|
||||
|
||||
let width;
|
||||
switch (this.dataset.width) {
|
||||
case 'small':
|
||||
width = Draw.SMALL;
|
||||
break;
|
||||
case 'medium':
|
||||
width = Draw.MEDIUM;
|
||||
break;
|
||||
case 'big':
|
||||
width = Draw.BIG;
|
||||
break;
|
||||
default:
|
||||
width = Draw.SMALL;
|
||||
}
|
||||
Draw.ctx.lineWidth = width;
|
||||
Draw.sctx.lineWidth = width;
|
||||
});
|
||||
}
|
||||
|
||||
const drawback = document.querySelector('#draw #drawback');
|
||||
drawback.addEventListener('click', () => {
|
||||
history.back();
|
||||
});
|
||||
|
||||
// Add a flag to not re-bind event listeners
|
||||
Draw.draw.classList.add('bound');
|
||||
},
|
||||
|
||||
stopDrawing: function (e) {
|
||||
// print to actual canvas
|
||||
if (Draw.drawingData.length)
|
||||
Draw.smoothenLine(Draw.drawingData[Draw.drawingData.length - 1], Draw.ctx, 1)
|
||||
// cleanup screen
|
||||
const rect = Draw.screen.getBoundingClientRect();
|
||||
Draw.sctx.clearRect(0, 0, rect.width, rect.height);
|
||||
|
||||
Draw.drawing = false;
|
||||
Draw.lastPos = null;
|
||||
Draw.mousePos = null;
|
||||
|
||||
// show coontrols
|
||||
Draw.topNav.classList.remove('drawing');
|
||||
Draw.actions.classList.remove('drawing');
|
||||
Draw.control.classList.remove('drawing');
|
||||
|
||||
Draw.ctx.beginPath();
|
||||
Draw.sctx.beginPath();
|
||||
},
|
||||
|
||||
startDrawing: function (e) {
|
||||
if (e.buttons == 1) {
|
||||
Draw.drawing = true;
|
||||
|
||||
// hide controls
|
||||
Draw.topNav.classList.add('drawing');
|
||||
Draw.actions.classList.add('drawing');
|
||||
Draw.control.classList.add('drawing');
|
||||
|
||||
// save drawing data
|
||||
const data = {
|
||||
gco: Draw.sctx.globalCompositeOperation,
|
||||
width: Draw.sctx.lineWidth,
|
||||
color: Draw.sctx.strokeStyle,
|
||||
points: []
|
||||
}
|
||||
Draw.drawingData.push(data);
|
||||
|
||||
Draw.lastPos = Draw.getPos(Draw.screen, e);
|
||||
}
|
||||
},
|
||||
|
||||
upscaleDrawing: function (finalctx) {
|
||||
// re-draw upscaled
|
||||
for (let i = 0; i < Draw.drawingData.length; i++) {
|
||||
finalctx.globalCompositeOperation = Draw.drawingData[i].gco;
|
||||
finalctx.lineWidth = Draw.drawingData[i].width * Draw.ratio;
|
||||
finalctx.strokeStyle = Draw.drawingData[i].color;
|
||||
|
||||
Draw.smoothenLine(Draw.drawingData[i], finalctx, Draw.ratio);
|
||||
finalctx.beginPath();
|
||||
}
|
||||
},
|
||||
|
||||
smoothenLine(line, ctx, ratio) {
|
||||
let j = 0;
|
||||
// keep only half of the points
|
||||
const sample = line.points.filter((point, index) => index % 2 == 0)
|
||||
|
||||
while (sample.length < 4) {
|
||||
sample.push(sample[sample.length - 1]);
|
||||
}
|
||||
|
||||
if (sample.length >= 4) {
|
||||
// move to 1st point of the line
|
||||
ctx.moveTo(
|
||||
sample[j].x * ratio,
|
||||
sample[j].y * ratio
|
||||
);
|
||||
for (j = 1; j < sample.length - 2; j++) {
|
||||
// define controle point as the mean point between current point and next point
|
||||
const c = (sample[j].x + sample[j + 1].x) / 2;
|
||||
const d = (sample[j].y + sample[j + 1].y) / 2;
|
||||
|
||||
// draw a curve between
|
||||
ctx.quadraticCurveTo(
|
||||
sample[j].x * ratio,
|
||||
sample[j].y * ratio,
|
||||
c * ratio,
|
||||
d * ratio
|
||||
);
|
||||
}
|
||||
ctx.quadraticCurveTo(
|
||||
sample[j].x * ratio,
|
||||
sample[j].y * ratio,
|
||||
sample[j + 1].x * ratio,
|
||||
sample[j + 1].y * ratio
|
||||
);
|
||||
}
|
||||
ctx.stroke();
|
||||
},
|
||||
|
||||
autoCropDrawing: function () {
|
||||
const rect = Draw.screen.getBoundingClientRect();
|
||||
let height = rect.height;
|
||||
let width = rect.width;
|
||||
|
||||
const imgData = Draw.ctx.getImageData(0, 0, width, height);
|
||||
let maxx = 0, minx = width, maxy = 0, miny = height,
|
||||
index;
|
||||
|
||||
// list all non transparent pixels
|
||||
for (let y = 0; y < height; y++) {
|
||||
for (let x = 0; x < width; x++) {
|
||||
index = (y * width + x) * 4;
|
||||
if (imgData.data[index + 3] > 0) {
|
||||
minx = Math.min(x, minx);
|
||||
miny = Math.min(y, miny);
|
||||
maxx = Math.max(x, maxx);
|
||||
maxy = Math.max(y, maxy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
width = maxx - minx + 1;
|
||||
height = maxy - miny + 1;
|
||||
|
||||
return Draw.ctx.getImageData(
|
||||
minx - (width * Draw.CROP_PADDING),
|
||||
miny - (height * Draw.CROP_PADDING),
|
||||
width * (1 + 2 * Draw.CROP_PADDING),
|
||||
height * (1 + 2 * Draw.CROP_PADDING)
|
||||
)
|
||||
},
|
||||
|
||||
// Get the position of the mouse/touch relative to the canvas
|
||||
getPos: function (canvasDom, event) {
|
||||
var rect = canvasDom.getBoundingClientRect();
|
||||
let x, y;
|
||||
if (event.touches) {
|
||||
x = event.touches[0].clientX - rect.left;
|
||||
y = event.touches[0].clientY - rect.top;
|
||||
} else {
|
||||
x = event.clientX - rect.left;
|
||||
y = event.clientY - rect.top;
|
||||
}
|
||||
|
||||
if (Draw.drawing) {
|
||||
let points = Draw.drawingData[Draw.drawingData.length - 1].points;
|
||||
points.push({ x, y });
|
||||
}
|
||||
|
||||
return { x, y };
|
||||
},
|
||||
|
||||
// Draw to the canvas
|
||||
renderCanvas: function () {
|
||||
if (Draw.drawing && Draw.lastPos && Draw.mousePos) {
|
||||
Draw.sctx.moveTo(Draw.lastPos.x, Draw.lastPos.y);
|
||||
Draw.sctx.lineTo(Draw.mousePos.x, Draw.mousePos.y);
|
||||
Draw.sctx.stroke();
|
||||
Draw.lastPos = Draw.mousePos;
|
||||
}
|
||||
},
|
||||
|
||||
disableForCanvas: function (e) {
|
||||
if (e.target.tagName == 'canvas') {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
};
|
||||
MovimEvents.registerWindow('loaded', 'draw', () => {
|
||||
Upload.attach((file) => {
|
||||
if (Draw.draw) Draw.draw.classList = '';
|
||||
if (Draw.save) {
|
||||
Draw.save.classList.remove('disabled');
|
||||
Draw.save.style.backgroundImage = '';
|
||||
}
|
||||
});
|
||||
|
||||
Upload.fail(() => {
|
||||
if (Draw.draw) Draw.draw.classList = 'upload';
|
||||
if (Draw.save) {
|
||||
Draw.save.classList.remove('disabled');
|
||||
Draw.save.style.backgroundImage = '';
|
||||
}
|
||||
});
|
||||
|
||||
Upload.progress((percent) => {
|
||||
if (Draw.save) {
|
||||
Draw.save.style.backgroundImage
|
||||
= 'linear-gradient(to top, rgba(0, 0, 0, 0.5) ' + percent + '%, transparent ' + percent + '%)';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
MovimEvents.registerBody('touchstart', 'draw', (e) => Draw.disableForCanvas(e));
|
||||
MovimEvents.registerBody('touchend', 'draw', (e) => Draw.disableForCanvas(e));
|
||||
MovimEvents.registerBody('touchmove', 'draw', (e) => Draw.disableForCanvas(e));
|
||||
@@ -0,0 +1,2 @@
|
||||
[draw]
|
||||
title = Draw
|
||||
Reference in New Issue
Block a user