Создаем приложение Art-pixel на Angular и Nest.js. Часть 1

от автора

В этой статье я собираюсь показать создание небольшого приложения с помощью которого можно будет создавать пиксельные картинки, также будет возможность разместить эти картинки в галерее, проголосовать за понравившуюся. Вообщем будет блог, галерея и мастерская. Мне показалось, что это может быть интересно новичкам, которые уже немного научились 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 после инициализации представления компонента, а также представлений дочерних компонентов. Вызывается только один раз, здесь мы подписываемся на пользовательское событие клика и собственно здесь и происходит рисование

На сегодня это все, иду готовить продолжение.


ссылка на оригинал статьи https://habr.com/ru/post/647411/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *