教大家用dom绘制兔子

1,649 阅读3分钟

我正在参加「兔了个兔」创意投稿大赛,详情请看:「兔了个兔」创意投稿大赛

本来想着兔年给各位大佬用css画个兔子助助兴,于是在网上找了一张爱宠大机密的那只痞帅痞帅的兔子的图片,准备临摹一下
于是我凭着感觉来实现了兔子,心里想着一步步来画,先从兔耳朵,于是照着心里的样子,实现成这样了。

image.png

看了下,这是啥?灵魂画手?于是乎产生了个想法,可不可以用dom去渲染,获取图片上每个点的颜色,然后拼起来?

获取图片上对应坐标点的颜色

难点:怎么获取图片上每个点的颜色?

    1. 创建PNG的画布
    1. 通过 getImageData() 方法获取对应像素点的信息
// 获取图片dom对象
const img = document.getElementById('my-image');
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height);
const pixelData = canvas.getContext('2d').getImageData(x, y, 1, 1).data; // x,y对应坐标位置

ImageData.data返回类型为Uint8ClampedArray的一维数组,每四个数组元素代表了一个像素点的RGBA信息,每个元素数值介于0~255;
前面的三个参数代表了RGB颜色,最后一个参数1代表了透明度A,范围为(0, 1)。但是ImageData.data的元素数值范围都在(0, 255)。因此这里需要按照0 => 0,1 => 255的方式对应下。
根据以上代码我们先来实现一个取色器,无非就是获取点击时的event对象里的offsetXoffsetY

dom渲染图片

取色器实现了,我们只需要获取每个点的坐标,将其当作每一个小块状div然后排列在一起

思路

    1. 获取图片宽高
    1. 通过双层循环遍历获取每个点的坐标
    1. 创建 fragment 将每个点坐标dom元素添加进去
    1. 最后添加到视图里

代码如下

// 绘制
function draw () {
  styleSheets.insertRule('.item {display: inline-block;width: 1px; height:1px;}');
    const fragment = new DocumentFragment();
    for (let i = 1; i <= this.imgHeight; i++) {
      for (let j = 1; j <= this.imgWidth;j++) {
        const item = document.createElement('div');
        // 设置item样式
        item.classList.add('item')
        // 获取图片对应坐标信息
        const pixelData = this.canvas.getContext('2d').getImageData(j, i, 1, 1).data;
        let bgColor = `rgba(${pixelData[0]},${pixelData[1]},${pixelData[2]},${pixelData[3] === 0 ? 0 : (pixelData[3] / 255)})`
        // 设置背景色
        item.style.backgroundColor = bgColor
        fragment.appendChild(item);
      }
    }
  // 添加进视图
  content.appendChild(wrap);
}

优化 如果以1px为维度,当图片太大的时候渲染的dom数量会很多,肯定会很卡,我来优化一下,让其可以动态调整颗粒度

// 绘制
function draw (size) {
  styleSheets.insertRule(`.item {display: inline-block;width: ${size}px; height:${size}px;}`);  // 代码变更
    const fragment = new DocumentFragment();
    for (let i = 1; i <= this.imgHeight; i += size) {  // 代码变更
      for (let j = 1; j <= this.imgWidth;j += size) {  // 代码变更
        const item = document.createElement('div');
        // 设置item样式
        item.classList.add('item')
        // 获取图片对应坐标信息
        const pixelData = this.canvas.getContext('2d').getImageData(j, i, 1, 1).data;
        let bgColor = `rgba(${pixelData[0]},${pixelData[1]},${pixelData[2]},${pixelData[3] === 0 ? 0 : (pixelData[3] / 255)})`
        // 设置背景色
        item.style.backgroundColor = bgColor
        fragment.appendChild(item);
      }
    }
  // 添加进视图
  content.appendChild(wrap);
}

最后我们看一下效果,以下分别是颗粒度为1/2/5时的效果图

image.png

可以发现颗粒度越大,看起来就像马赛克了。(以下代码没有考虑性能和边界情况,仅供娱乐哈)