纯css竟能直接绘制出图片?

8,463 阅读5分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第2篇文章,点击查看活动详情

作为一位前端开发,我们的日常是肯定离不开css的,各种css属性确实需要花费很大的精力去学习和记忆,但是这个属性,我相信大家一定都不陌生,box-shadow,就是我们常用的阴影属性,简单介绍一下我们今天的主角:

box-shadow: 阴影类型 X轴位移 Y轴位移 阴影大小 阴影扩展 阴影颜色

它有六个属性,可以向四个方向进行扩展

我们日常经常使用它,已经很熟悉了,就不过多的介绍了,今天我们来分析下我们的需求

看看效果

css绘制图片.png

分析实现

首先我们需要知道我们今天的主题就是需要由这个属性来完成,其次,我们想要实现的效果的是,通过css绘制一张图片,那们,我们现在想象一下,需要完成这个需求,我们现在已经掌握了哪些知识了,分析出来就知道我们接下来需要干嘛了:

  1. 我们知道box-shadow属性可以往四个方向随意衍生绘制,
  2. 我们知道这个属性是可以设置多个叠加的,没有上限的
  3. 我们知道这个属性可以绘制自定义大小,颜色方向,
  4. 我们知道一张图片实际是许许多多的像素点组成

知道了上面的知识,我们就知道了,我们通过box-shadow不停的绘制像素点即可,绘制宽高为1的像素点,将这个属性绘制多个即可。

那么我们现在的第一个问题就来了,如何获取图片的所有像素点,其实呢这个知识点我怕们在这篇博客中也有讲到过,产品让开发一个电子签名、这不是伸手就来?,我们在绘制过程中可以通过canvasgetImageData获取当前画布的所有像素信息,所有思路是不是已经有了呢?

我们通过canvas绘制一下我们的图片,然后通过getImageData拿到当前的所有像素信息,然后通过box-shadow将所有元素点进行绘制即可完成【css绘制出图片的酷炫操作】,那么接下来,我们开始手摸手实现

准备工作

在逻辑代码开始前,我们需要一些准备工作,一个inpu文件上传框,用于上传我们需要绘制的图片,一个节点,用于我们拿到所有的像素点后进行box-shadow样式赋值的节点,也是我们最终展示的节点,那么这一步无需多言

<input type="file" id="upload">
<div id="app"></div>

那么接下来,我们开始正式的工作,首先我们就需要拿到上传的图片信息,让后将其转化为流文件,然后我们自己创建一个图片出来,并且拿到最终的图片结果

const upload = document.getElementById('upload')
upload.onchange = async (e) => {
  const file = e.target.files[0]
  if (!file) return
  const filePath = URL.createObjectURL(file)
  const img = await loadImg(filePath)
}

/* 获取上传的图片并创建一张图片出来 */
function loadImg(filePath){
  return new Promise( (resolve, reject) => {
    const img = new Image()
    img.src = filePath
    img.onload = () => resolve(img);
    img.onerror = () => reject(null);
  })
}

获取图片的所有像素点信息

此时我们已经有了图片,那么接下来,就可以通过canvas将其绘制下来,再通过getImageData获取其详细的位置元素信息吧

function getImgPositionInfo(img) {
  const cvs = document.createElement('canvas');
  ({width: cvs.width, height: cvs.height} = img);
  const ctx = cvs.getContext('2d');
  ctx.drawImage(img, 0, 0)
  const imgPositionInfo = ctx.getImageData(0, 0, img.width, img.height).data
  return imgPositionInfo
}

此时,我们就有了详细的像素点的所有信息了

通过像素点转化为shadow样式位置

此时,我们已经拿到了所有像素点的位置,其中包含了详细的坐标颜色信息,我们只需要从中获取出来,我们写一个双层循环,在里面拿到所有的位置加颜色

function createStyle(width, height, imgPositionInfo){
  const cacheShadowCssFragments = []
  for (let r = 0; r < height; r++) {
    for (let c = 0; c < width; c++) {
      const i = r * width + c;
      const R = imgPositionInfo[i * 4 + 0];
      const G = imgPositionInfo[i * 4 + 1];
      const B = imgPositionInfo[i * 4 + 2];
      const A = imgPositionInfo[i * 4 + 3] / 255;
      cacheShadowCssFragments.push(`${c + 1}px ${r}px  rgba(${R},${G},${B},${A})`)
     }
   }
  return cacheShadowCssFragments.join(',')
}

上面就是一些获取位置和颜色的操作了,没什么特别的地方,此时,我们就拿到了box-shadow的所有绘制信息了,将其赋值给dom即可,就完成了此操作

const app = document.getElementById('app')
const shadows = createHtml(img.width, img.height, imgPositionInfo)
app.style.boxShadow = shadows

实验一下

详细完整的代码已经放置在了文末码上掘金,我们可以在线体验,现在我们来自己动手实现一下,就可以看到文章开头的样子了

css绘制图片.png

我们可以看到首先这个图片是个div节点并不是img,我们右键的时候也没有出现保存图片按钮,再看看控制台,发现其样式就是许许多多的box-shadow的样式组合而成,至此,我们已经大功告成了,

用这样的思路绘制出一张纯css的图片是不是很有趣呢?

总结

首先,想说的是,工作中大家是肯定用不上这种方法的,但是不失为一种思路,也有可能这种问题会出现在面试题中,这种东西似乎并没有什么意义,但是却可以为我们开阔思路,其次这种形式非常的耗费渲染性能,想象一下一下需要绘制几万个点,其消耗也是非常大,我在我的这台小windos上测试时,如果传入太大的图片,就会造成渲染直接崩溃,所以,各位如果想测试的话呢,建议使用小一点的图片哟,绘制是需要花费4-10s的时间的,所以上传完毕还需要稍微等待一下呢。

在线体验

好了,掘金的【码上掘金】非常好用,安利大家可以把日常的各种小demo小片段在上面编写留存,实时线上编译查看,非常方便,最后,贴上地址,大家愉快测试玩耍把

使用纯css绘制出图片