Création de Pixel Art Maker en JavaScript



Bonjour mes amis!

Préface


Une fois que la navigation sur le Web m'a conduit à cela .

Plus tard, j'ai découvert un article sur la façon dont cela fonctionne.

Cela ne semble rien de spécial - Pikachu, dessiné avec CSS. Cette technique est appelée Pixel Art (pixel art?). Ce qui m'a frappé, c'est la complexité du processus. Chaque cellule est peinte manuellement (enfin, presque; car il y a des préprocesseurs; Sass dans ce cas). Bien sûr, la beauté nécessite des sacrifices. Cependant, le développeur est une créature paresseuse. J'ai donc pensé à l'automatisation. Donc, ce que j'ai appelé Pixel Art Maker est apparu.

Conditions


Que voulons-nous obtenir?

Nous avons besoin d'un programme qui génère un nombre donné de cellules avec la possibilité de les colorer avec des couleurs arbitraires.

Voici quelques exemples tirés du Web:


Fonctions supplémentaires:

  • forme de cellule - carré ou cercle
  • largeur de cellule en pixels
  • nombre de cellules
  • Couleur de l'arrière plan
  • couleur à colorier
  • fonction de création de canevas
  • fonction d'affichage du numéro de cellule
  • fonction d'enregistrement / suppression d'image
  • fonction de nettoyage de la toile
  • fonction de suppression de la toile

Nous discuterons de petits détails dans le processus de codage.

Alors allons-y.

Marquage


Pour implémenter les fonctionnalités nécessaires, notre code HTML devrait ressembler à ceci:

<!--     -->
<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>

La plage (limite) de valeurs pour la largeur et le nombre de cellules a été déterminée empiriquement. Les expériences ont montré que des valeurs plus petites / plus grandes ne sont pas pratiques pour des raisons de détails excessifs (pour des valeurs <10 pour la largeur), de performances réduites (pour des valeurs> 50 pour la quantité), etc.

modes


Nous n'avons rien de spécial dans les styles.

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


Définissez le canevas et son contexte (contexte de dessin 2D):

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

Nous trouvons le bouton pour créer le canevas et y accrochons le gestionnaire d'événements click:

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

Tout le code supplémentaire sera dans la fonction generateCanvas:

function generateCanvas(){
    ...
}

Nous déterminons la forme, la largeur, la quantité horizontale et la quantité totale (la toile représente le même nombre de cellules horizontalement et verticalement), ainsi que la couleur de fond:

// 
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

Nous déterminons la taille du canevas et définissons les attributs appropriés pour cela (rappelez-vous que la taille de canevas correcte est définie via les attributs):

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

Quelques paramètres supplémentaires:

//  
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()
}

Voici à quoi ressemble la fonction carrés:

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()
    }
}

La fonction cercles est très similaire à la fonction carrés.

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()
    }
}

Nous trouvons le bouton pour enregistrer / supprimer le résultat (image) et y accrocher le gestionnaire d'événements click:

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

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

On retrouve le bouton pour nettoyer la toile et ...:

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

Trouvez le bouton pour supprimer le canevas et ...:

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

Le résultat ressemble à ceci:



Codepen (ajouté quelques cas d'utilisation)

Github

Merci pour votre attention.

All Articles