如何用box-shadow绘制一张图片?

146 阅读2分钟

前言

前几天刷视频,看到了一个用box-shadow绘制图片的效果,心血来潮,想来看看自己能不能实现。自己尝试了一下如何去实现,下面就看看主要的流程是什么样吧

思考过程

  1. 上传图片拿到文件对象
  2. 读取文件内容
  3. 加载图片内容,将图片转换成base64
  4. 绘制到canvas中
  5. 获取canvas中的图片的像素点
  6. 绘制阴影字符串,并渲染dom节点

效果图

代码开始

这里我用的是vue3的代码实现的,不过逻辑都是类似的,可以用原生实现也很方便

基本结构

  <div>
    <div class="img-box" :style="{ width: imgboxWidth + 'px' }">
      <img :src="imgUrl" alt="" />
    </div>
    <div>
      <input type="file" name="file" @change="handleFileChange" />
    </div>
    <div class="boxShadow" ref="boxShadow"> </div>
    <div><span>box-shadow</span></div>
  </div>
<style lang="less">
  .img-box {
    width: 300px;
    img {
      width: 100%;
      height: 100%;
    }
  }
  .boxShadow {
    width: 1px;
    height: 1px;
  }
</style>

逻辑代码

<script setup lang="ts">
  import { ref } from 'vue'
  const imgUrl = ref()
  const boxShadow = ref()
  // 默认上传图片容器高度
  const imgboxWidth = ref(300)
  // 等比例图片高度
  const imgHeight = ref(0)
  // 流程
  // 文件对象 -> 读取文件内容 -> 显示图片 -> 绘制到canvas -> 获取像素点 -> 绘制阴影
  const handleFileChange = (e: any) => {
    const file = e.target.files[0]
    // 读取文件内容
    const reader = new FileReader()
    reader.readAsDataURL(file)
    reader.onload = () => {
      // 显示图片
      imgUrl.value = reader.result as string
      const img = new Image()
      img.src = imgUrl.value
      img.onload = () => {
        // 获取图片高度
        imgHeight.value = Math.floor(img.height * (imgboxWidth.value / img.width))
        // 显示图片
        createCanvas(img)
      }
    }
  }
  // 绘制canvas
  const createCanvas = (img: any) => {
    const canvas = document.createElement('canvas')
    const ctx = canvas.getContext('2d')
    // 图片容器的宽度
    canvas.width = imgboxWidth.value
    // 等比例图片高度
    canvas.height = imgHeight.value
    // 绘制图片到canvas
    ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
    // 获取像素点
    const canvasData = ctx.getImageData(0, 0, canvas.width, canvas.height)
    let boxShadowStr = ''
    // 阴影像素点
    let x = 0
    let y = 0
    const maxLen = canvasData.data.length
    for (let i = 0; i < maxLen; i += 4) {
      boxShadowStr += `${x}px ${y}px rgb(${canvasData.data[i]}, ${canvasData.data[i + 1]}, ${canvasData.data[i + 2]}),`
      x++
      // 换行
      if (x >= canvas.width) {
        x = 0
        y++
      }
    }
    // 绘制阴影
    boxShadow.value.style.boxShadow = boxShadowStr.slice(0, boxShadowStr.length - 1)
  }
</script>

总结

  1. 因为box-shadow需要依托一个div,所以我是把这个bos-shadow放在了boxShadow的div上,并设置了宽高为1px
  2. x,y的大小也会改变绘制出来的阴影大小,感兴趣可以尝试一下

总的来说,这个绘制不是很难,主要是挺好玩的,不过对浏览器的性能消耗很大,我在查看元素的时候经常会被卡死,轻易别尝试了。