Schreiben von Pixel Art Maker in JavaScript



Guten Tag, Freunde!

Vorwort


Sobald Web - Surfen fĂŒhrte mich zu diesem .

SpĂ€ter entdeckte ich einen Artikel darĂŒber, wie das funktioniert.

Es scheint nichts Besonderes zu sein - Pikachu, gezeichnet mit CSS. Diese Technik nennt man Pixel Art (Pixel Art?). Was mich beeindruckt hat, war die KomplexitĂ€t des Prozesses. Jede Zelle wird von Hand bemalt (na ja, fast; da es PrĂ€prozessoren gibt; in diesem Fall Sass). Schönheit erfordert natĂŒrlich Opfer. Der Entwickler ist jedoch eine faule Kreatur. Deshalb habe ich ĂŒber Automatisierung nachgedacht. Also erschien das, was ich Pixel Art Maker nannte.

Bedingungen


Was wollen wir bekommen?

Wir brauchen ein Programm, das eine bestimmte Anzahl von Zellen mit der Möglichkeit erzeugt, sie mit beliebigen Farben zu fÀrben.

Hier einige Beispiele aus dem Internet:


ZusÀtzliche Funktionen:

  • Zellform - Quadrat oder Kreis
  • Zellenbreite in Pixel
  • Anzahl der Zellen
  • Hintergrundfarbe
  • Farbe zum FĂ€rben
  • Canvas-Erstellungsfunktion
  • Funktion zur Anzeige der Zellennummer
  • Bildspeicher- / Löschfunktion
  • Leinwandreinigungsfunktion
  • Leinwandentfernungsfunktion

Wir werden kleinere Details im Codierungsprozess diskutieren.

So lass uns gehen.

Markup


Um die erforderliche FunktionalitĂ€t zu implementieren, sollte unser HTML ungefĂ€hr so ​​aussehen:

<!--     -->
<div class="tools">

    <!--      () -->
    <div>
        <p>Shape Form</p>
        <select>
            <!--  -->
            <option value="squares">Square</option>

            <!--  -->
            <option value="circles">Circle</option>
        </select>
    </div>

    <!--        -->
    <div class="numbers">
        <!--  -->
        <div>
            <!--    10  50 ( ) -->
            <p>Shape Width <br> <span>(from 10 to 50)</span></p>
            <input type="number" value="20" class="shapeWidth">
        </div>

        <!--  -->
        <div>
            <!--    -->
            <p>Shape Number <br> <span>(from 10 to 50)</span></p>
            <input type="number" value="30" class="shapeNumber">
        </div>
    </div>

    <!--     -->
    <div class="colors">
        <!--   -->
        <div>
            <p>Background Color</p>
            <input type="color" value="#ffff00" required class="backColor">
        </div>

        <!--   ( ) -->
        <div>
            <p>Shape Color</p>
            <input type="color" value="#0000ff" class="shapeColor">
        </div>
    </div>

    <!--     -->
    <div class="buttons">
        <!--     -->
        <input type="button" value="Generate Canvas" class="generate">

        <!--   /   () -->
        <input type="button" value="Show/Hide Numbers" class="show">

        <!--  /  () -->
        <input type="button" value="Save/Delete Image" class="save">

        <!--           -->
        <input type="button" value="Clear Canvas" class="clear">

        <!--      -->
        <input type="button" value="Delete Canvas" class="delete">
    </div>
</div>

<!--  -->
<canvas></canvas>

Der Wertebereich (Grenzwert) fĂŒr die Breite und Anzahl der Zellen wurde empirisch bestimmt. Die Experimente zeigten, dass kleinere / grĂ¶ĂŸere Werte aus GrĂŒnden ĂŒbermĂ€ĂŸiger Detailgenauigkeit (fĂŒr Werte <10 fĂŒr die Breite), verminderter Leistung (fĂŒr Werte> 50 fĂŒr die Menge) usw. unpraktisch sind.

Stile


Wir haben nichts Besonderes in Stilen.

CSS:
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    margin: 0;
    min-height: 100vh;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    align-content: flex-start;
}

h1 {
    width: 100%;
    text-align: center;
    font-size: 2.4em;
    color: #222;
}

.tools {
    height: 100%;
    display: inherit;
    flex-direction: column;
    margin: 0;
    font-size: 1.1em;
}

.buttons {
    display: inherit;
    flex-direction: column;
    align-items: center;
}

div {
    margin: .25em;
    text-align: center;
}

p {
    margin: .25em 0;
    user-select: none;
}

select {
    padding: .25em .5em;
    font-size: .8em;
}

input,
select {
    outline: none;
    cursor: pointer;
}

input[type="number"] {
    width: 30%;
    padding: .25em 0;
    text-align: center;
    font-size: .8em;
}

input[type="color"] {
    width: 30px;
    height: 30px;
}

.buttons input {
    width: 80%;
    padding: .5em;
    margin-bottom: .5em;
    font-size: .8em;
}

.examples {
    position: absolute;
    top: 0;
    right: 0;
}

a {
    display: block;
}

span {
    font-size: .8em;
}

canvas {
    display: none;
    margin: 1em;
    cursor: pointer;
    box-shadow: 0 0 1px #222;
}

Javascript


Definieren Sie die Leinwand und ihren Kontext (2D-Zeichnungskontext):

let c = document.querySelector('canvas'),
    $ = c.getContext('2d')

Wir finden die SchaltflÀche zum Erstellen der ZeichenflÀche und hÀngen den Click-Event-Handler daran:

document.querySelector('.generate').onclick = generateCanvas

Der gesamte weitere Code befindet sich in der Funktion generateCanvas:

function generateCanvas(){
    ...
}

Wir bestimmen die Form, Breite, horizontale Menge und Gesamtmenge (die Leinwand reprÀsentiert die gleiche Anzahl von Zellen horizontal und vertikal) sowie die Hintergrundfarbe:

// 
let shapeForm = document.querySelector('select').value
//  (  )
let shapeWidth = parseInt(document.querySelector('.shapeWidth').value)
//    (  )
let shapeNumber = parseInt(document.querySelector('.shapeNumber').value)
//   (   )
let shapeAmount = Math.pow(shapeNumber, 2)
//  
let backColor = document.querySelector('.backColor').value

Wir bestimmen die GrĂ¶ĂŸe der Leinwand und legen die entsprechenden Attribute dafĂŒr fest (denken Sie daran, dass die richtige LeinwandgrĂ¶ĂŸe ĂŒber die Attribute festgelegt wird):

//  =  =   *    
let W = H = shapeWidth * shapeNumber
c.setAttribute('width', W)
c.setAttribute('height', H)

Einige zusÀtzliche Einstellungen:

//  
let border = 1
//  
let borderColor = 'rgba(0,0,0,.4)'
//      
let isShown = false

//    
//    
//  
//         
if (shapeWidth < 10 || shapeWidth > 50 || shapeNumber < 10 || shapeNumber > 50 || isNaN(shapeWidth) || isNaN(shapeNumber)) {
    throw new Error(alert('wrong number'))
} else if (shapeForm == 'squares') {
    c.style.display = 'block'
    squares()
} else {
    c.style.display = 'block'
    circles()
}

So sieht die Quadratfunktion aus:

function squares() {
    //   
    let x = y = 0

    //  
    let squares = []

    //     ()
    let w = h = shapeWidth

    //    
    addSquares()

    // -
    function Square(x, y) {
        //   
        this.x = x
        //  y 
        this.y = y
        //   =  
        this.color = backColor
        //     
        this.isSelected = false
    }

    //   
    function addSquares() {
        //     
        for (let i = 0; i < shapeAmount; i++) {
            //  
            let square = new Square(x, y)

            //    
            //        
            x += w

            //       
            //   y   
            //      
            //   
            if (x == W) {
                y += h
                x = 0
            }

            //    
            squares.push(square)
        }
        //    
        drawSquares()
    }

    //   
    function drawSquares() {
        //  
        $.clearRect(0, 0, W, H)

        //    
        for (let i = 0; i < squares.length; i++) {
            //    
            let square = squares[i]
            //  
            $.beginPath()
            //  ,   
            $.rect(square.x, square.y, w, h)
            //  
            $.fillStyle = square.color
            //  
            $.lineWidth = border
            //  
            $.strokeStyle = borderColor
            //  
            $.fill()
            //  
            $.stroke()

            //       
            if (isShown) {
                $.beginPath()
                //  
                $.font = '8pt Calibri'
                //  
                $.fillStyle = 'rgba(0,0,0,.6)'
                //  ,    
                $.fillText(i + 1, square.x, (square.y + 8))
            }
        }
    }

    //      ""
    c.onclick = select
    //   
    function select(e) {
        //   
        let clickX = e.pageX - c.offsetLeft,
            clickY = e.pageY - c.offsetTop

        //    
        for (let i = 0; i < squares.length; i++) {
            let square = squares[i]

            //  ,   
            //  
            // ,    
            if (clickX > square.x && clickX < (square.x + w) && clickY > square.y && clickY < (square.y + h)) {
                //  ,   ,  
                //        ( )
                if (square.isSelected == false) {
                    square.isSelected = true
                    square.color = document.querySelector('.shapeColor').value
                } else {
                    square.isSelected = false
                    square.color = backColor
                }
                //  
                //  ,     ,   
                //  ,     ,  
                // ,      
                drawSquares()
            }
        }
    }

    //             ""
    document.querySelector('.show').onclick = showNumbers
    //    
    function showNumbers() {
        if (!isShown) {
            isShown = true
            //     
            for (let i = 0; i < squares.length; i++) {
                let square = squares[i]
                $.beginPath()
                //   
                $.font = '8pt Calibri'
                //   
                $.fillStyle = 'rgba(0,0,0,.6)'
                //  ,    
                $.fillText(i + 1, square.x, (square.y + 8))
            }
        } else {
            isShown = false
        }
        //  
        drawSquares()
    }
}

Die Kreisfunktion ist der Quadratfunktion sehr Àhnlich.

JavaScript:
function circles() {
    //  
    let r = shapeWidth / 2

    let x = y = r

    let circles = []

    addCircles()

    function Circle(x, y) {
        this.x = x
        this.y = y
        this.color = backColor
        this.isSelected = false
    }

    function addCircles() {
        for (let i = 0; i < shapeAmount; i++) {
            let circle = new Circle(x, y)
            //      
            x += shapeWidth
            //           
            //      
            //      
            if (x == W + r) {
                y += shapeWidth
                x = r
            }
            circles.push(circle)
        }
        drawCircles()
    }

    function drawCircles() {
        $.clearRect(0, 0, W, H)

        for (let i = 0; i < circles.length; i++) {
            let circle = circles[i]
            $.beginPath()
            //  
            $.arc(circle.x, circle.y, r, 0, Math.PI * 2)
            $.fillStyle = circle.color
            $.strokeStyle = borderColor
            $.lineWidth = border
            $.fill()
            $.stroke()
            if (isShown) {
                $.beginPath()
                $.font = '8pt Calibri'
                $.fillStyle = 'rgba(0,0,0,.6)'
                $.fillText(i + 1, (circle.x - 8), circle.y)
            }
        }
    }

    c.onclick = select
    function select(e) {
        let clickX = e.pageX - c.offsetLeft,
            clickY = e.pageY - c.offsetTop

        for (let i = 0; i < circles.length; i++) {
            let circle = circles[i]

            //  ,   
            let distanceFromCenter = Math.sqrt(Math.pow(circle.x - clickX, 2) + Math.pow(circle.y - clickY, 2))

            if (distanceFromCenter <= r) {
                if (circle.isSelected == false) {
                    circle.isSelected = true
                    circle.color = document.querySelector('.shapeColor').value
                } else {
                    circle.isSelected = false
                    circle.color = backColor
                }
                drawCircles()
            }
        }
    }

    document.querySelector('.show').onclick = showNumbers
    function showNumbers() {
        if (!isShown) {
            isShown = true
            for (let i = 0; i < circles.length; i++) {
                let circle = circles[i]
                $.beginPath()
                $.font = '8pt Calibri'
                $.fillStyle = 'rgba(0,0,0,.6)'
                $.fillText(i + 1, (circle.x - 8), circle.y)
            }
        } else {
            isShown = false
        }
        drawCircles()
    }
}

Wir finden die SchaltflÀche zum Speichern / Löschen des Ergebnisses (Bild) und hÀngen den Click-Event-Handler daran:

document.querySelector('.save').onclick = () => {
    //  
    let img = document.querySelector('img')

    //   , 
    //  , 
    img == null ? document.body.appendChild(document.createElement('img')).src = c.toDataURL() : document.body.removeChild(img)
}

Wir finden den Knopf zum Reinigen der Leinwand und ...:

document.querySelector('.clear').onclick = () => {
    //    
    $.clearRect(0, 0, W, H)
    generateCanvas()
}

Suchen Sie die SchaltflÀche zum Löschen der Leinwand und ...:

document.querySelector('.delete').onclick = () => {
    $.clearRect(0, 0, W, H)
    c.style.display = 'none'
}

Das Ergebnis sieht folgendermaßen aus:



Codepen (einige AnwendungsfĂ€lle hinzugefĂŒgt)

Github

Vielen Dank fĂŒr Ihre Aufmerksamkeit.

All Articles