Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
В этой статье я собираюсь показать создание небольшого приложения с помощью которого можно будет создавать пиксельные картинки, также будет возможность разместить эти картинки в галерее, проголосовать за понравившуюся. Вообщем будет блог, галерея и мастерская. Мне показалось, что это может быть интересно новичкам, которые уже немного научились Angular и пробуют делать различные приложения для того, чтобы выработать свой стиль и набить руку.
Это моя первая статья подобного рода и возможно я бы никогда ее не разместил, но моё лузерство и привычка быть обыкновенным, меня настолько раскрепостили, что я воспринимаю поражения как должное. Амбиций нет, есть только кайф от того, что я делаю, люблю созерцать, люблю что то создавать. Рад буду если это кому то принесет пользу. Надеюсь при просмотре опытными разработчиками все не окажется очень плохо и я найду в себе силы выложить следующие части.
А может кому то просто понравится рисовать, ссылка на демо ниже.
В этой части я буду использовать Angular, CSS фреймворк от w3schools
Итак, в первой части будет описан процесс создания вот такой мастерской.
Здесь можно посмотреть Demo, здесь находится код.
Идем устанавливаем Ангулар. Создаем новый проект.
Подразумевается, что вы умеете это делать по этому я не буду это описывать.
Наша мастерская будет создана в виде отдельного модуля.
Создаем его с помощью следующих команд:
ng g m canvas
ng g c canvas/components/main-canvas --module canvas.module
ng g c canvas/components/left-panel --module canvas.module
ng g c canvas/components/canvas --module canvas.module
ng g c canvas/components/form --module canvas.module
Где main-canvas у нас является родителем для компонентов left-panel, canvas, form.
Создаем component main-canvas
code main-canvas.component.html
<!DOCTYPE html>
<html>
<title>W3.CSS Template</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Roboto'>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<style>
html,
body,
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: "Roboto", sans-serif
}
</style>
<div class="w3-bar w3-light-grey">
<h1 style="padding-left: 2rem;">ARTpixel</h1>
</div>
<body class="w3-light-grey">
<!-- Page Container -->
<div class="w3-content w3-margin-top" style="max-width:1400px;">
<!-- The Grid -->
<div class="w3-row-padding">
<!-- Right Column -->
<!-- Left Column -->
<app-left-panel (pagesEvent)="getDataPanel($event)"></app-left-panel>
<!-- End Left Column -->
<div class="w3-twothird">
<div class="w3-container w3-card w3-white w3-margin-bottom">
<h2 class="w3-text-grey w3-padding-16"><i
class="fa fa-paint-brush fa-fw w3-margin-right w3-xxlarge w3-text-teal"></i>Поле рисования
</h2>
<div class="w3-container">
<app-canvas [uploadSuccess]="uploadSuccess"></app-canvas>
<hr>
</div>
</div>
</div>
<!-- End Right Column -->
</div>
<!-- End Grid -->
</div>
<!-- End Page Container -->
<footer class="w3-container w3-teal w3-center w3-margin-top">
<p>Find me on social media.</p>
<i class="fa fa-facebook-official w3-hover-opacity"></i>
<i class="fa fa-instagram w3-hover-opacity"></i>
<i class="fa fa-snapchat w3-hover-opacity"></i>
<i class="fa fa-pinterest-p w3-hover-opacity"></i>
<i class="fa fa-twitter w3-hover-opacity"></i>
<i class="fa fa-linkedin w3-hover-opacity"></i>
<p>Powered by <a href="https://www.w3schools.com/w3css/default.asp" target="_blank">w3.css</a></p>
</footer>
</body>
</html>
code main-canvas.component.ts
import { Component, EventEmitter } from '@angular/core';
import { FormGroup } from '@angular/forms';
@Component({
selector: 'app-main-canvas',
templateUrl: './main-canvas.component.html',
styleUrls: ['./main-canvas.component.scss']
})
export class MainCanvasComponent {
uploadSuccess: EventEmitter<any> = new EventEmitter();//объявили
constructor() { }
getDataPanel($event: FormGroup) {
const data = {
widthRect: Number($event.value.widthCanvas),
heightRect: Number($event.value.heightCanvas),
numberOf: Number($event.value.hwPixel),
borderRow: $event.value.meshThickness,
numberRow: Number($event.value.hwPixel) + Number($event.value.meshThickness),
innerWidth: Number($event.value.heightCanvas) * (Number($event.value.hwPixel) + Number($event.value.meshThickness)),
innerHeight: Number($event.value.widthCanvas) * (Number($event.value.hwPixel) + Number($event.value.meshThickness)),
colorfillStyle: $event.value.colorFone
}
this.uploadSuccess.emit(data);//передали
}
}
Пока в этом компоненте у нас находится единственный метод, который получает данные из компонента left-panel
<app-left-panel (pagesEvent)="getDataPanel($event)"></app-left-panel>
который в свою очередь получает их из компонента form и передает их в компонент canvas
<app-canvas [uploadSuccess]="uploadSuccess"></app-canvas>
где на основании отправленных данных происходит создание рисунка.
Создаем компонент left-panel
left-panel.cpmponent.html
<div class="w3-third">
<div class="w3-white w3-text-grey w3-card-4">
<div class="w3-container">
<p class="w3-large"><b><i class="fa fa-asterisk fa-fw w3-margin-right w3-text-teal"></i>Форма создания холста</b></p>
<div class="w3-light-grey w3-round-xlarge w3-small">
<app-form
(pagesEvent)="getData($event)"
></app-form>
</div>
<hr>
</div>
</div><br>
</div>
Здесь все просто мы получаем данные из компонента форм
<app-form(pagesEvent)="getData($event)"></app-form>
left-panel.component.ts
import { Component, EventEmitter, Output } from '@angular/core';
import { FormGroup } from '@angular/forms';
@Component({
selector: 'app-left-panel',
templateUrl: './left-panel.component.html',
styleUrls: ['./left-panel.component.scss']
})
export class LeftPanelComponent {
@Output() pagesEvent: EventEmitter<FormGroup > =
new EventEmitter< FormGroup >();
constructor() { }
getData($event: FormGroup) {
this.pagesEvent.emit($event);
}
}
Сейчас у нас здесь единственный метод getData() который передает полученный из формы данные в родительский компонент и дальше в канвас компонент
Создаем компонент form.
form.component.html
<form [formGroup]="form" class="w3-container">
<p>
Холст
</p>
<label>Количество пикселей по горизонтали</label>
<input class="w3-input" type="number" name="widthCanvas" formControlName="widthCanvas">
<label>Количество пикселей по вертикали</label>
<input class="w3-input" type="number" name="heightCanvas" formControlName="heightCanvas">
<p>
Pixel
</p>
<label>Размер пикселя</label>
<input class="w3-input" type="number" name="hwPixel" formControlName="hwPixel">
<label>Толщина сетки</label>
<input class="w3-input" type="number" name="meshThickness" formControlName="meshThickness">
<label>Цвет заливки холста</label>
<input class="w3-input w3-border 0 input-color" type="color" name="colorFone" formControlName="colorFone">
<hr>
<div class="form-group">
<button class="w3-button w3-pale-red" (click)="onCreate()">
Создать
</button>
</div>
<div>
<hr>
</div>
</form>
Создаем form.component.ts
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.scss']
})
export class FormComponent implements OnInit {
@Output() pagesEvent: EventEmitter<FormGroup> = new EventEmitter<FormGroup>();
constructor() { }
form: FormGroup = new FormGroup(
{
"heightCanvas": new FormControl("24", Validators.required),
"widthCanvas": new FormControl("24", Validators.pattern("[0-9]{10}")),
"hwPixel": new FormControl("10", Validators.required),
"meshThickness": new FormControl("1", Validators.pattern("[0-9]{10}")),
"colorFone": new FormControl("green"),
}
);
onCreate(): void {
this.pagesEvent.emit(this.form);
}
}
и наконец создаем компонент нашего ядра canvas.component
canvas.component.html
<div class="w3-card-4">
<div class="w3-container w3-center">
<label for="colorWell"></label>
<input list="" id="colorWell" type="color" name="colorRect" [(ngModel)]="colorRect">
<button (click)="create()">Востановить рисунок</button>
<button (click)="clearPixel()">Задать пикселю цвет фона</button>
</div>
</div>
<div class="w3-card-4 layer">
<div class="w3-container w3-center">
<canvas id="canvas" #canvas></canvas>
</div>
</div>
canvas.component.ts
import { AfterViewInit, Component, ElementRef, EventEmitter, OnInit, Renderer2, ViewChild } from '@angular/core';
import { FormGroup } from '@angular/forms';
@Component({
selector: 'app-canvas',
templateUrl: './canvas.component.html',
styleUrls: ['./canvas.component.scss']
})
export class CanvasComponent implements AfterViewInit, OnInit {
canvas: HTMLCanvasElement;
innerWidth: number;
innerHeight: number;
rendererRef: any;
numberRow: number;
numberOf = 10;
borderRow = 1;
widthRect = 50;
heightRect = 50;
x = 0;
y = 0
canvasX: number // X click cordinates
canvasY: number // Y click cordinates
colorRect = '#242323';//цвет пикселя рисовалки
colorfillStyle = '#19a923';//цвет пикселя холста
matr = { numberOf: 10, backgroundColor: '#19a923', data: [{ x: 0, y: 0, color: '' }] }
private ctx: CanvasRenderingContext2D | null;
@ViewChild('canvas') canvasRef: ElementRef;
constructor(private el: ElementRef,
private renderer: Renderer2,
) {
this.numberRow = this.numberOf + this.borderRow;
this.innerWidth = this.heightRect * this.numberRow;
this.innerHeight = this.widthRect * this.numberRow;
}
onResize(data: any) {
this.innerWidth = data.innerWidth;
this.innerHeight = data.innerHeight;
this.widthRect = data.widthRect;
this.heightRect = data.heightRect;
this.numberOf = data.numberOf;
this.borderRow = data.borderRow;
this.numberRow = data.numberRow;
this.canvas.width = this.innerWidth;
this.canvas.height = this.innerHeight
this.colorfillStyle = data.colorfillStyle;
this.cleardraw()
this.draw()
}
@Input() uploadSuccess: EventEmitter<FormGroup>;
ngOnInit(): void {
this.uploadSuccess.subscribe(data => {
this.onResize(data);
});
}
ngAfterViewInit(): void {
this.canvas = this.canvasRef.nativeElement;
this.canvas.width = this.innerWidth;
this.canvas.height = this.innerHeight;
let image = document.getElementById('source');
this.cleardraw()
this.draw();
const data = this.canvas?.toDataURL();
if (this.rendererRef != null) {
this.rendererRef();
}
this.rendererRef = this.renderer.listen(this.canvasRef.nativeElement, 'click', (event) => {
let cX = event.layerX;
let cY = event.layerY;
const offsetLeft = this.canvasRef.nativeElement.offsetLeft;
const offsetTop = this.canvasRef.nativeElement.offsetTop;
this.canvasX = cX - offsetLeft;
this.canvasY = cY - offsetTop;
this.matr.data.map(data => {
if (cX >= data.x && cX < data.x + this.numberOf && cY >= data.y && cY < data.y + this.numberOf) {
this.ctx.fillStyle = this.colorRect;
this.ctx.fillRect(data.x, data.y, this.numberOf, this.numberOf);
data.color = this.colorRect;
}
})
localStorage.setItem('matr', JSON.stringify(this.matr));
const data = this.canvas?.toDataURL();
})
}
draw(): void {
this.matr.backgroundColor = this.colorfillStyle;
this.matr.numberOf = this.numberOf;
for (let i = 0; i < this.heightRect; i++) {
for (let j = 0; j < this.widthRect; j++) {
this.ctx.fillStyle = this.colorfillStyle;
this.ctx.fillRect(j * this.numberRow, i * this.numberRow, this.numberOf, this.numberOf);
this.matr.data.push({ x: j * this.numberRow, y: i * this.numberRow, color: this.colorfillStyle })
}
}
}
cleardraw(): void {
this.ctx = this.canvas.getContext('2d');
this.ctx.clearRect(0, 0, this.widthRect, this.heightRect);
this.matr.data = [];
}
create(): void {
const retrievedObject = localStorage.getItem('matr');
this.matr = JSON.parse(retrievedObject);
this.matr.data.map(data => {
this.ctx.fillStyle = data.color;
this.ctx.fillRect(data.x, data.y, this.matr.numberOf, this.matr.numberOf);
})
}
clearPixel(): void {
this.colorRect = this.matr.backgroundColor
}
}
А теперь давайте разбираться что здесь происходит. Компонент состоит из методов:
onResize(data: any), draw(), cleardraw(), create(), clearPixel()
onResize(data: any) - вызывается из метода ngOnInit, когда приходит событие о том, что пользователь отправил форму создания холста.
cleardraw(): подготавливает поле к перерисовке
draw(): отрисовывает холст
create(): восстанавливает рисунок из localStorage
clearPixel(): устанавливает цвет кисти равным цвету холста
ngOnInit: вызывается фреймворком Angular один раз после установки свойств компонента, которые участвуют в привязке. Выполняет инициализацию компонента. И здесь мы подписываемся на события из родительского компонента
ngAfterViewInit: вызывается фреймворком Angular после инициализации представления компонента, а также представлений дочерних компонентов. Вызывается только один раз, здесь мы подписываемся на пользовательское событие клика и собственно здесь и происходит рисование
На сегодня это все, иду готовить продолжение.