File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
/*
* Copyright (c) FOX EDU CO., LTD ALL RIGHT RESERVED.
* by flashkid
* 25.OCT.2023(WED)
*/
class colouredPenciles {
private btnInit: HTMLElement;
private ctrlCon: HTMLElement;
private canvasCon: HTMLElement;
private canvas: HTMLCanvasElement;
private context: CanvasRenderingContext2D;
private eraseObj: HTMLInputElement;
private previewObj: HTMLElement;
private paint: boolean;
private points: object[] = [];
private dopoints: object[] = [];
private coloursArr: any;
private coloursSelect: number;
private sizeRange: object;
private size: number;
private erase: boolean = false;
constructor(btnInit, canvasCon, coloursArr, coloursSelect = 1, brushMin = 0.5, brushMax = 10) {
this.btnInit = btnInit;
this.canvasCon = canvasCon;
this.coloursArr = coloursArr.map(c => c.toLowerCase());
this.coloursSelect = coloursSelect - 1;
brushMax = brushMax > 60 ? 60 : brushMax;
this.sizeRange = {
min: brushMin,
max: brushMax
}
this.size = Math.round((brushMin + brushMax) / 2);
this.btnInit.onclick = () => {
if(this.btnInit.classList.contains('is-active')) {
this.clearCanvas();
} else {
const canvas = document.createElement('canvas') as HTMLCanvasElement;
canvas.width = this.canvasCon.offsetWidth;
canvas.height = this.canvasCon.clientHeight;
const context = canvas.getContext("2d");
context.lineCap = 'round';
context.lineJoin = 'round';
this.canvas = canvas;
this.canvas.id = 'painterCanvas';
this.context = context;
this.canvasCon.append(this.canvas);
this.uiUpdate();
this.redraw();
this.createDrawEvents();
}
}
}
private uiUpdate = () => {
this.btnInit.classList.add('is-active');
this.canvasCon.classList.add('is-active');
this.ctrlCon = document.createElement('div');
this.ctrlCon.classList.add('painterbar');
this.canvasCon.append(this.ctrlCon);
const minBtn = document.createElement('button');
minBtn.type = "button";
minBtn.classList.add('btn-painterbar-size');
minBtn.textContent = '색연필 최소화';
this.ctrlCon.append(minBtn);
minBtn.onclick = () => {
this.ctrlCon.classList.toggle('min');
}
const previewCon = document.createElement('div');
previewCon.classList.add('pencil-ctrl-options', 'pencil-preview');
previewCon.style.width = `${this.sizeRange['max'] * 0.1 + 0.2}rem`;
previewCon.style.height = `${this.sizeRange['max'] * 0.1 + 0.2}rem`;
this.ctrlCon.append(previewCon);
this.previewObj = document.createElement('div');
previewCon.append(this.previewObj);
const sizeCon = document.createElement('div');
sizeCon.classList.add('pencil-ctrl-options', 'painterbar-stroke');
this.ctrlCon.append(sizeCon);
const range = document.createElement('input');
range.type = "range";
range.min = this.sizeRange['min'];
range.max = this.sizeRange['max'];
range.step = "0.1";
range.setAttribute("orient", "vertical");
range.value = this.size.toString();
sizeCon.append(range);
range.oninput = () => {
this.changeSize(range.value);
}
const fncCon = document.createElement('div');
fncCon.classList.add('pencil-ctrl-options', 'painterbar-fnc');
this.ctrlCon.append(fncCon);
const undo = document.createElement('button');
undo.type = "button";
undo.classList.add('undo');
undo.textContent = "undo";
fncCon.append(undo);
undo.addEventListener('click', this.undo);
const redo = document.createElement('button');
redo.type = "button";
redo.classList.add('redo');
redo.textContent = "redo";
fncCon.append(redo);
redo.addEventListener('click', this.redo);
this.eraseObj = document.createElement('input');
this.eraseObj.type = "checkbox";
this.eraseObj.id = "colouredPencils_eraser";
this.eraseObj.classList.add('clear');
this.eraseObj.checked = this.erase;
fncCon.append(this.eraseObj);
const eraserLB = document.createElement('label');
eraserLB.setAttribute("for", this.eraseObj.id);
eraserLB.textContent = "지우개";
fncCon.append(eraserLB);
this.eraseObj.onchange = () => {
this.switchEraser();
}
const reset = document.createElement('button');
reset.type = 'button';
reset.classList.add('reset');
reset.textContent = 'reset';
fncCon.append(reset);
reset.onclick = () => this.reset();
const colorDiv = document.createElement('div');
colorDiv.classList.add('pencil-ctrl-options', 'painterbar-color');
colorDiv.setAttribute('data-opt', this.coloursArr[this.coloursSelect]);
this.ctrlCon.append(colorDiv);
this.coloursArr.forEach ((el, i) => {
const radio = document.createElement('input');
radio.type = "radio";
radio.name = "colouredPencils_colour";
radio.id = "colouredPencils_colour_" + i;
radio.value = i.toString();
if (i == this.coloursSelect) radio.checked = true;
colorDiv.append(radio);
const label = document.createElement('label');
label.setAttribute("for", radio.id);
label.textContent = el;
label.style.backgroundColor = el;
if(el == '#ffffff') label.classList.add('white');
colorDiv.append(label);
radio.onchange = (e:Event) => {
this.changeColour(i);
}
});
const close = document.createElement('button');
close.type = "button";
close.classList.add('btn-viewer-painter-close');
close.textContent = "색연필 닫기";
this.ctrlCon.append(close);
close.onclick = () => {
this.clearCanvas();
}
this.updatePreview();
}
private undo = () => {
if (this.points.length == 0) return;
const arrKeys = this.points.map(el => el["mode"]);
const lastIndex = arrKeys.lastIndexOf(false);
const tempPoints = this.points.slice(lastIndex, this.points.length - 1);
this.dopoints = [...this.dopoints, ...tempPoints];
this.points = this.points.slice(0, lastIndex);
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.redraw();
}
private redo = () => {
if (this.dopoints.length == 0) return;
const arrKeys = this.dopoints.map(el => el["mode"]);
const lastIndex = arrKeys.lastIndexOf(false);
const tempPoints = this.dopoints.slice(lastIndex, this.dopoints.length - 1);
this.points = [...this.points, ...tempPoints];
this.dopoints = this.dopoints.slice(0, lastIndex);
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.redraw();
}
private reset = () => {
this.points = [];
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.redraw();
if(this.erase) {
this.switchEraser();
}
}
private changeSize = (size) => {
this.size = +size;
this.updatePreview();
}
private changeColour = (key) => {
this.coloursSelect = key;
if(this.erase) this.switchEraser();
this.updatePreview();
}
private switchEraser = () => {
this.erase = !this.erase;
this.eraseObj.checked = this.erase;
this.updatePreview();
}
private updatePreview = () => {
this.previewObj.style.cssText = `
width: ${this.size * 0.1}rem;
height: ${this.size * 0.1}rem;
`
if (this.coloursArr[this.coloursSelect] == '#ffffff') {
this.previewObj.classList.add('white');
} else {
this.previewObj.classList.remove('white');
}
if (this.erase) {
this.previewObj.classList.add('erase');
} else {
this.previewObj.classList.remove('erase');
this.previewObj.style.backgroundColor = `${this.coloursArr[this.coloursSelect]}`;
}
}
private clearCanvas = () => {
this.ctrlCon.remove();
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.points = [];
this.dopoints = [];
this.canvasCon.removeChild(this.canvas);
this.erase = false;
this.btnInit.classList.remove('is-active');
this.canvasCon.classList.remove('is-active');
}
private createDrawEvents = () => {
const canvas = this.canvas;
canvas.addEventListener("mousedown", this.pressEventHandler);
canvas.addEventListener("mousemove", this.dragEventHandler);
canvas.addEventListener("mouseup", this.releaseEventHandler);
canvas.addEventListener("mouseout", this.cancelEventHandler);
canvas.addEventListener("touchstart", this.pressEventHandler);
canvas.addEventListener("touchmove", this.dragEventHandler);
canvas.addEventListener("touchend", this.releaseEventHandler);
canvas.addEventListener("touchcancel", this.cancelEventHandler);
}
private redraw = () => {
const points = this.points;
if (points.length == 0) return;
const context = this.context;
context.clearRect(0, 0, this.canvas.width, this.canvas.height);
for (let i = 0; i < points.length; ++i) {
if (points[i]["erase"]) {
context.globalCompositeOperation = "destination-out";
} else {
context.globalCompositeOperation = "source-over";
}
context.beginPath();
if (points[i]["mode"] && i) {
context.moveTo(points[i - 1]["x"], points[i - 1]["y"]);
} else {
context.moveTo(points[i]["x"] - 1, points[i]["y"]);
}
context.lineTo(points[i]["x"], points[i]["y"]);
context.strokeStyle = points[i]["color"];
context.lineWidth = points[i]["size"];
context.stroke();
}
context.closePath();
}
private addClick = (x: number, y: number, color: string, size: number, dragging: boolean, erase: boolean) => {
this.points.push({ x: x, y: y, color: color, size: size, mode: dragging, erase: erase })
}
private releaseEventHandler = () => {
if (this.paint) {
this.paint = false;
this.redraw();
}
}
private cancelEventHandler = () => {
this.releaseEventHandler();
}
private pressEventHandler = (e: MouseEvent | TouchEvent) => {
this.dopoints = [];
let mouseX = (e as TouchEvent).changedTouches ?
(e as TouchEvent).changedTouches[0].pageX :
(e as MouseEvent).pageX;
let mouseY = (e as TouchEvent).changedTouches ?
(e as TouchEvent).changedTouches[0].pageY :
(e as MouseEvent).pageY;
mouseX -= (this.canvasCon.getBoundingClientRect().left + window.pageXOffset);
mouseY -= (this.canvasCon.getBoundingClientRect().top + window.pageYOffset);
this.paint = true;
this.addClick(mouseX, mouseY, this.coloursArr[this.coloursSelect], this.size, false, this.erase);
this.redraw();
}
private dragEventHandler = (e: MouseEvent | TouchEvent) => {
let mouseX = (e as TouchEvent).changedTouches ?
(e as TouchEvent).changedTouches[0].pageX :
(e as MouseEvent).pageX;
let mouseY = (e as TouchEvent).changedTouches ?
(e as TouchEvent).changedTouches[0].pageY :
(e as MouseEvent).pageY;
mouseX -= (this.canvasCon.getBoundingClientRect().left + window.pageXOffset);
mouseY -= (this.canvasCon.getBoundingClientRect().top + window.pageYOffset);
if (this.paint) {
this.addClick(mouseX, mouseY, this.coloursArr[this.coloursSelect], this.size, true, this.erase);
this.redraw();
}
e.preventDefault();
}
}
// 환경설정
const colorArray = ["#222222","#fd4418","#ffce00","#00d56a","#2f77ff","#ffffff"];
const defaultColor = 5;
const brushMin = 0.5, brushMax = 40;
// 대상 개체
const btnInit = document.querySelector('.btn-painter-toggle') as HTMLElement;
const targetObj = document.querySelector('.paint-target') as HTMLElement;
// 구동 defaultColor, brushMin, brushMax 은 넘기지 않아도 기본 값에 의해 작동됨
new colouredPenciles(btnInit, targetObj, colorArray, defaultColor, brushMin, brushMax);
// new colouredPenciles(btnInit, targetObj, colorArray);