实现像素作品生成器

236 阅读3分钟

工作原理

文章所述,我们使用阴影来充当屏幕上的像素。我们可以放大或缩小这些阴影,使像素变大或变小。

由于每个阴影的大小为1×1,我们可以创建一个像素艺术作品。如果我们希望每个像素为20×20,我们只需使用transform将其放大20倍:

transform: scale(20);

Javascript

第一步是使用一个简单的循环生成像素网格:

let config = {
  width: 40,
  height: 40,
  color: 'white',
  drawing: true,
  eraser: false
}

let events = {
  mousedown: false
}

document.getElementById('pixel-art-area').style.width = `calc(${(0.825 * config.width)}rem + ${(config.height * 2)}px)`;
document.getElementById('pixel-art-area').style.height = `calc(${(0.825 * config.height)}rem + ${(config.width * 2)}px)`;

for(let i = 0; i < config.width; ++i) {
  for(let j = 0; j < config.height; ++j) {
    let createEl = document.createElement('div');
    createEl.classList.add('pixel');
    createEl.setAttribute('data-x-coordinate', j);
    createEl.setAttribute('data-y-coordinate', i);
    document.getElementById('pixel-art-area').appendChild(createEl);
  }
}

最终会创建一个40×40像素的网格,即1600个元素。

跟踪用户的鼠标移动

然后,我们可以使用三个事件来跟踪用户的鼠标移动:pointerdownpointermovepointerup由于我们需要将这些事件应用于所有像素点,我们使用一个循环来遍历每个像素并添加事件。

然后,如果用户按下鼠标,我们可以使用e.target来确定哪个像素点被点击。

document.querySelectorAll('.pixel').forEach(function(item) {
  item.addEventListener('pointerdown', function(e) {
    if(config.eraser === true) {
      item.setAttribute('data-color', null);
      item.style.background = `#191f2b`;
    } else {
      item.setAttribute('data-color', config.color);
      item.style.background = `${config.color}`;
    }
    events.mousedown = true;
  });
});

document.getElementById('pixel-art-area').addEventListener('pointermove', function(e) {
  if(config.drawing === true && events.mousedown === true || config.eraser === true && events.mousedown === true) {
    if(e.target.matches('.pixel')) {
      if(config.eraser === true) {
        e.target.setAttribute('data-color', null);
        e.target.style.background = `#101532`;
      } else {
        e.target.setAttribute('data-color', config.color);
        e.target.style.background = `${config.color}`;
      }
    }
  }
});

document.body.addEventListener('pointerup', function(e) {
  events.mousedown = false;
});

文件上传

当你将图片上传时,将图片内容绘制到一个画布上。绘制图片的画布是隐藏的,因此你看不到它。

一旦图片被添加到画布上,我们可以使用getImageData()来获取每个像素。我们遍历像素,并获取每个点的颜色。如果图片的像素超过40×40,它们将被忽略 。

在下面的代码中,像素数据以R、G、B、A格式返回。我们将像素数据存储在pixelData变量中。pixelData[3]是“A”,pixelData[0][2]分别是R、G和B。利用这些信息,我们可以确定每个像素的RGB颜色,并忽略透明度为0的像素,即pixelData[3]等于0的像素。

// 将事件附加到输入框
document.querySelector('.select-file').addEventListener('change', function(e) {
  let files = e.target.files;
  // 从输入框中获取已上传的文件
  let f = files[0];
  let reader = new FileReader();
  // 隐藏任何错误
  document.querySelector('.error').classList.remove('active');

  reader.onload = (async function(file) {
    // 我们只接受图片...因此检查图片类型
    if(file.type == "image/png" || file.type == "image/jpg" || file.type == "image/gif" || file.type == "image/jpeg") {
      // 从文件中创建图片
      const bitmap = await createImageBitmap(file);
      const canvas = document.querySelector("canvas");
      canvas.width = bitmap.width;
      canvas.height = bitmap.height;
      const ctx = canvas.getContext("2d");
      // 清除任何之前的图片...以防万一
      ctx.clearRect(0, 0, 9999, 9999);
      // 并将新元素绘制到画布上
      ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
      let constructPixelData = []

      // 然后遍历每个像素,获取每个点的颜色
      for(let i = 0; i < config.width; ++i) {
        for(let j = 0; j < config.height; ++j) {
          let pixelData = canvas.getContext('2d').getImageData(i, j, 1, 1).data;
          // 像素数据以R、G、B、A格式返回。pixelData[3]是“A”,pixelData[0]到[2]分别是R、G和B。
          // 如果pixelData[3]为0,则该像素的透明度为0。因此我们不会显示它。
          if(pixelData[3] !== 0) {
            // 将其放入一个包含x、y和颜色的数组中
            constructPixelData.push({ x: i, y: j, color: `rgb(${pixelData[0]} ${pixelData[1]} ${pixelData[2]})`});
          }
        }
      }
      // 然后更新像素艺术生成器以显示这些信息。
      constructPixelData.forEach(function(i) {
        let getPixel = document.querySelector(`.pixel[data-x-coordinate="${i.x}"][data-y-coordinate="${i.y}"]`);
        if(getPixel !== null) {
          getPixel.setAttribute('data-color', i.color);
          getPixel.style.background = i.color;
        }
      });
    }
    else {
      document.querySelector('.error').textContent = '请选择png、jpg或gif格式的文件进行上传。';
      document.querySelector('.error').classList.add('active');
    }

  })(f);
});

最后,我们在颜色和橡皮擦上设置了一些事件,以便我们可以追踪正在选择的工具和颜色:

[ 'click', 'input' ].forEach(function(item) {
  document.querySelector('.color-picker').addEventListener(item, function() {
    config.color = this.value;
    document.querySelectorAll('.colors > div').forEach(function(i) {
      i.classList.remove('current');
    });
    this.classList.add('current');
    config.eraser = false;
    document.querySelector('.eraser-container').classList.remove('current');
  });
});

document.querySelectorAll('.colors > div').forEach(function(item) {
  item.addEventListener('click', function(e) {
    document.querySelector('.color-picker').classList.remove('current');
    document.querySelectorAll('.colors > div').forEach(function(i) {
      i.classList.remove('current');
    })
    item.classList.add('current');
    config.eraser = false;
    config.color = `${item.getAttribute('data-color')}`;
    document.querySelector('.eraser-container').classList.remove('current');
  })
});

原文:fjolt.com/article/css…