JSFiddle - React, Tailwind, and code Playground

by Kanatantsin

HTML

<div class="sprite-generator" id="generator">
  <h2 class="sprite-generator__title">Генератор спрайтов</h2>
  <p>
    Добавьте изображения и нажмите на кнопку «Сгенерировать спрайт»
  </p>
  <input type="file" multiple class="sprite-generator__upload">
  <div class="images__added-count">Изображений в спрайте:
    <span class="images__added-count-value">0</span>
  </div>
  <!-- <canvas id="canvas"></canvas> -->
  <button class="sprite-generator__generate">Сгенерировать спрайт</button>
  <div class="sprite-generator__result">
    
    <div class="sprite-generator__preview">
      <img alt="" class="sprite-generator__result-image">
    </div>
    <textarea class="sprite-generator__code" cols="30" rows="10" placeholder="Здесь будет ваш CSS" readonly></textarea>
  </div>
</div>

CSS

body {
  margin: 0;
  font-family: Verdana;
}

.sprite-generator {
  box-sizing: border-box;
  padding: 10px;
}

.sprite-generator__result {
  margin-top: 15px;
  display: flex;
}
.images__added-count {
  margin-top: 10px;
  margin-bottom: 10px;
}
.sprite-generator__preview {
  flex-grow: 1;
  position: relative;
  border: 1px solid #ccc;
}
.sprite-generator__result-image {
  max-width: 100%;
}

.sprite-generator__code {
  flex-grow: 1;
}

JavaScript

const prop = ( data, name ) => data.map( item => item[ name ] ),
	summ = data => data.reduce(( total, value ) => total + value, 0 );

class SpriteGenerator {
	constructor( container ) {
  	this.uploadButton = container.querySelector( '.sprite-generator__upload' );
    this.submitButton = container.querySelector( '.sprite-generator__generate' );
    this.imagesCountContainer = container.querySelector( '.images__added-count-value' );
    this.codeContainer = container.querySelector( '.sprite-generator__code' ); 
    this.imageElement = container.querySelector( '.sprite-generator__result-image' ); 
    this.images = [];
    this.imagesCount = 0;
    this.registerEvents();
  }
  
  registerEvents() {
    this.uploadButton.addEventListener('change', event => this.upload(event));
    this.submitButton.addEventListener('click', () => this.genSprite( {margin: {top: 12, right: 0, bot: 0, left: 0} } ));
  }
  
  upload(e) {
    const files = Array.from(e.currentTarget.files).filter(
      file => /^image\//.test(file.type)
    );

    Promise.all(files.map(this.crtImage))
    .then(images => { 
      images.forEach(image => URL.revokeObjectURL(image.el.src));
      this.images = this.images.concat(images);
    })
    .catch(err => console.error(`${err}`));
  }
  
  crtImage(file) {
    return new Promise((done, fail) => {
      const img = document.createElement('img');
      img.src = URL.createObjectURL(file);
      img.addEventListener('load', event => done({ name: file.name.replace(/\.\w*$/, ''), el: event.currentTarget, width: event.currentTarget.width, height: event.currentTarget.height }));
      img.addEventListener('error', error => fail(error.message)); 
    });
  }
  
  genSprite( {margin} ) {
    const canvas = document.createElement('canvas'),
          ctx = canvas.getContext('2d');     
    canvas.width = Math.max( ...prop(this.images, 'width') ) + margin.left;
    canvas.height = summ( prop(this.images, 'height') ) + (this.images.length - 1) * margin.top;
 ...