css 的 filter属性竟然如此好玩

业界 作者:SegmentFault 2022-03-21 16:27:41

作者:lulu_up

来源:SegmentFault  思否社区 


状态从何而来?



在此之前我对css里面的filter属性不是很了解, 只知道使用这个属性来改变svg图片的颜色, 最近恰好查了很多相关文档并做了大量实验, 并有了一些启发与想法, 索性就在这里分享出来。


一、filter 滤镜



"滤镜"这个名字很贴切了, 可以理解成为元素添加各种显示效果, 先不用记各种名词咱们直接看效果, 使用方法 & 效果图:


<style>
  #lulu {
     filter: grayscale(1);
  }
</style>

<body>
  <img id="lulu" src="./img/头像.jpeg" />
</body>


     

看上图里的这些效果, 比如第一排第一个, 我们会想到在某些特定的纪念日网站整体会变成灰色的样式, 应该就是用的这个属性, 第二排的第一张就可以用与某些事物被"雷击"?


二、做一个'抖动'特效





看到这张图我第一个想法就是做个抖动的特效, 就是那种很动感的效果:


     

当然配合上一旋转效果也不错:


     

原理就是两个图片层叠在一起, 上面的图片进行放大与旋转动画:


<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
  <style>
    .box {
      position: relative;
      border: 1px solid gray;
      display: flex;
      overflow: hidden;
      width: 110px;
      padding: 0px;
      margin-top: 100px;
      margin-left: 300px;
    }

    .box>img {
      width: 100px;
      height: 100px;
      margin-left: 6px;
      filter: invert(1);
    }

    .mk {
      position: absolute;
      top: 0;
      left: 0;
      opacity: 0.5;
      animation: cc 0.5s linear infinite;
    }

    @keyframes cc {
      from {
        transform: scale(1.2);
      }

      to {
        transform: scale(1);
      }
    }

    .mk2 {
      position: absolute;
      top: 0;
      left: 0;
      opacity: 0.5;
      animation: cc2 0.5s linear infinite alternate;
      border-radius: 50%;
      overflow: hidden;
    }

    @keyframes cc2 {
      from {
        transform: scale(2.2) rotate(30deg);
      }

      to {
        transform: scale(1) rotate(0deg);
      }
    }
  </style>
</head>

<body>
  <div class="box">
    <img src="./img/头像.jpeg" />
    <img class="mk" src="./img/头像.jpeg" />
  </div>

  <div class="box">
    <img src="./img/头像.jpeg" />
    <img class="mk2" src="./img/头像.jpeg" />
  </div>
</body>

</html>


三、drop-shadow 阴影



filter属性通过设置drop-shadow为元素添加阴影, 可是早就有box-shadow属性了呀, 那这两个属性有什么区别了?



<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
  <style>
    #w1 {
      width: 50px;
      height: 50px;
      font-size: 36px;
      font-weight: 900;
      box-shadow: 0 0 2px red;
    }

    #w2 {
      width: 50px;
      height: 50px;
      font-size: 36px;
      font-weight: 900;
      filter: drop-shadow(0px 0px 2px red);
    }
  </style>
</head>

<body>
  <p id="w1">九</p>
  <p id="w2">九</p>
</body>

</html>


上图可知, box-shadow是针对整个dom元素进行阴影的产生, 但是drop-shadow会忽略掉"透明"的部分。


四、drop-shadow 复制 (做一个看图猜人物游戏)



注意: 我这里使用的都是svg图片。

既然与box-shadow都有为元素设置阴影的能力, 那么box-shadow有复制自身样的能力drop-shadow是都也有?

     

所谓box-shadow的复制自身样式如图所示, box-shadow可以制作n个与元素本身形状相同或不同的样式, 下图右侧红色的方块就是左图的阴影:


     

再看一下drop-shadow的表现:


     

看到上面的图我第一反应就是"猜人物"小游戏, 我们把人物的轮廓也就是右图显示出来, 然后在公布答案的时候展示左侧的原图即可。


赋值gif图有bug

    

赋值gif图会有bug, 效果如下:



五、drop-shadow 批量复制



box-shadow属性是可以写多个属性值的, 我一般会利用这个属性进行一个单一样式的dom元素的复制 效果如下图:


     

drop-shadow有点'狠', 他的每一次复制都是基于上次的整体效果进行的阴影投射:


     

上图可以看出, 第一个复制后是出现了横排的2个, 第二次投射是产生了下方的两个, 并且每次投射都是叠加的, 下面我们看一组更夸张的:


     

可想而知这种增长方式有多可怕, 稍微写几遍就可以覆盖满屏幕了。


"找不同"小游戏

     

我们可以做一片阴影, 但是其中某个我们单独做一个样式进行覆盖, 考考大家的眼力, 就如图例所示:


     

这里就是利用drop-shadow产生阴影, 然后再进行一点修改, 正确答案在这里:


     

所以只要再写两段代码, 就可以让这个8x8 变成16x16那么多, 应该还挺好玩的。



六、drop-shadow 与 box-shadow的联合


     

drop-shadow 与 box-shadow 都有投射的能力, 那么他两个属性共同作用于一个元素会是怎样的:


     

box-shadow会基于drop-shadow属性产生的全部投影进行透射阴影, 第一排是drop-shadow的投影, 第二排是box-shadow的投影, 具体怎么玩我还没想到太适合的。


<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
        #wrap {
            position: relative;
            height: 350px;
            width: 500px;
            margin: 50px auto;
            overflow: hidden;
        }

        #n {
            border: 1px solid gray;
            width: 50px;
            box-shadow: 0 200px;
            transform: rotate(10deg);
            filter: drop-shadow(70px 0) drop-shadow(140px 0px);
        }
    </style>
</head>

<body>
    <div id="wrap">
        <img id="n" src="./svg/人.svg" /
    </div>
</body>

</html>


七、drop-shadow 复制后的“运动”


     
既然可以投射出那么多投影, 那么如果我元素进行旋转的话, 投影是否也会进行旋转? 并且它是以什么规律运动的那?

下面演示的是, 物体投影 + 物体本身旋转:


<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
        #wrap {
            position: relative;
            height: 350px;
            width: 400px;
            margin: 250px auto;
        }

        #n {
            width: 20px;
            filter: drop-shadow(25px 0) drop-shadow(50px 0px) drop-shadow(100px 0);
            animation: rr 2s linear infinite;
        }

        @keyframes rr {
            0% {
                transform: rotate(0);
            }

            100% {
                transform: rotate(360deg);
            }
        }
    </style>
</head>

<body>
    <div id="wrap">
        <img id="n" src="./svg/人.svg" />
    </div>
</body>

</html>


上面是整体以'元素'本身为旋转点进行旋转

那要如何让'元素'的每个投影都以自身为原点旋转那?


     

这里的思路就是, 在img外包裹一层div, 我们对外层div进行投影, 内部的img负责旋转:


<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
        #wrap {
            position: relative;
            height: 350px;
            width: 400px;
            margin: 250px auto;
        }

        #box {
            width: 20px;
            height: 20px;
            filter: drop-shadow(25px 0) drop-shadow(50px 0px) drop-shadow(100px 0);
        }

        #n {
            width: 20px;
            animation: rr 2s linear infinite;
        }

        @keyframes rr {
            0% {
                transform: rotate(0);
            }

            100% {
                transform: rotate(360deg);
            }
        }
    </style>
</head>

<body>
    <div id="wrap">
        <div id="box">
            <img id="n" src="./svg/人.svg" />
        </div>
    </div>
</body>

</html>


八、filter属性着色(svg + png)图片


     

改变svg颜色最直接的方法就是改其本身的fill属性, 这里不做探讨, 这里要研究的是到底为什么filter可以改变图片的颜色, 是什么原理? 这里我们就一起探究一下(这里只讨论纯色图片)。


轮廓的形成

并不是所有的图片被赋予drop-shadow属性后都会呈现出物体的轮廓, 投影会忽略透明背景的地方, 所以png这种可以定义透明背景的图片才可以被投射出相应的轮廓而不是矩形轮廓, svg同理。

比如jpg图片无法设置透明的背景, 所以其投影效果就与box-shadow相同了。

svg + png 投影变色


我们可以利用drop-shadow制作一个指定颜色的投影, 然后只要将元素本身隐藏, 只留下投影就ok了。





<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
        #box2 {
            display: inline-block;
            overflow: hidden;
        }

        #glasses2 {
            filter: drop-shadow(200px 0 red);
            transform: translateX(-200px);
        }
    </style>
</head>

<body>
    <div>
        <div id="box2">
            <img id="glasses2" src="./img/太阳镜.png" />
        </div>
    </div>
</body>
</html>


颜色的叠加变色有点强!

有没有办法是直接改变元素本身的颜色? 我尝试将drop-shadow投射在自身位置, 但是投射的阴影永远在元素后面, 我尝试将元素的opacity改小, 阴影也会随之变小, 如果设置opacity:0则投影也不可见了。

不管什么颜色无非是三原色合成的颜色, filter属性可以定义那么多种滤镜, 那是不是代表着某些滤镜效果的叠加态就是我们想要的目标颜色:

     

手动生成那么多的属性不现实, 顺着这个思路我找到了一个真的这样做的网站:


为图片混合调色官网点击预览:https://codepen.io/sosuke/pen/Pjoqqp



  1. 需要多点几次Compute Filters按钮, 直到生成差异度较小的属性。

  2. 如果我们的元素不是纯黑色, 需要先赋予 filter: brightness(0) saturate(100%) 将其变为纯黑, 因为不同的底色需要变成目标颜色的filter属性不同。

  3. 当然啦这里属于头脑一波, 实际项目中不会这样去做的。


九、局部清晰


    

这里所谓的局部清晰可以想象为, 某张图全部都是模糊的, 但是我们把一个放大镜放在某处, 此处就会变得清晰, 先看我做的效果:



     

这里的原理是这样的, 一共两层, 下层是模糊滤镜的图片,上层是一个圆形的div, 并且这个div的背景图是图片的清晰版, 设置background-position, 在拖动div的同时, 实时变换背景的background-position位置, 就实现了图里的效果。


<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
  <style>
    * {
      box-sizing: border-box;
    }

    #wrap {
      position: relative;
    }

    #acc {
      position: absolute;
      left: 0;
      right: 0;
      width: 540px;
      filter: blur(7px);
      pointer-events: none;
    }

    #mk {
      z-index: 2;
      height: 100px;
      width: 100px;
      border-radius: 50%;
      overflow: hidden;
      border: 1px solid blue;
      position: absolute;
      left: 0;
      top: 0;
      background-image: url("./img/利姆露jpeg.jpeg");
      background-size: 540px 562px;
      background-position: 0 0;
      background-repeat: no-repeat;
    }
  </style>
</head>

<body>
  <div id="wrap">
    <div id="mk"></div>
    <img id="acc" src="./img/利姆露jpeg.jpeg" />
  </div>
  <script>
    function drag(elementId) {
      const element = document.getElementById(elementId);
      const position = {
        offsetX: 0,
        offsetY: 0,
        state: 0,
      }
      function getEvent(event) {
        return event || window.event;
      }
      element.addEventListener(
        "mousedown",
        function (event) {
          var e = getEvent(event);
          position.offsetX = e.offsetX;
          position.offsetY = e.offsetY;
          position.state = 1;
        },
        false
      );
      document.addEventListener(
        "mousemove",
        function (event) {
          var e = getEvent(event);
          if (position.state) {
            position.endX = e.clientX;
            position.endY = e.clientY;
            element.style.top = position.endY - position.offsetY + "px";
            element.style.left = position.endX - position.offsetX + "px";
            element.style.backgroundPositionX = "-" + element.style.left;
            element.style.backgroundPositionY = "-" + element.style.top;
          }
        },
        false
      );
      element.addEventListener(
        "mouseup",
        function (event) {
          position.state = 0;
        },
        false
      );
    }
    drag("mk");
  </script>
</body>

</html>


END


    

这次就是这样, 希望与你一起进步。




点击左下角阅读原文,到 SegmentFault 思否社区 和文章作者展开更多互动和交流,扫描下方”二维码“或在“公众号后台回复“ 入群 ”即可加入我们的技术交流群,收获更多的技术文章~

- END -


关注公众号:拾黑(shiheibook)了解更多

赞助链接:

关注数据与安全,洞悉企业级服务市场:https://www.ijiandao.com/
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接