CSS 有多少种方式可以实现网格蔓延变色效果!

798 阅读6分钟

起因

最近负责公司的一些代码效果优化工作,碰到了一个网格动画变色的需求,之前的开发同学觉得这个效果比较麻烦不好实现,当时是搁置下来了,现在我仔细思索之后发现实现该效果的方法有很多,而且都不需要JS的参与!故此写篇文章分享给大家~

01.gif

根据图中的效果我们可以先拆解一下需要实现的效果

  1. 这个网格是由重复的横线竖线组合而成
  2. 网格线段颜色会随着线段蔓延从透明过度到橘色
  3. 网格蔓延时越靠近底部的区域起点越大

如何实现一个网格

实现一个网格前我们要仔细观察需求,能发现效果图上横线竖线分别是独立运行的,这也代表我们这个网格形状的实现也要分别实现横线竖线

我们都知道在CSS中需要实现重复背景时第一个考虑的属性就应该是background

.hor {  
    width: 100%;  
    height: 100%;  
    background-image: linear-gradient(to bottom, orange,transparent);
}

此时我们得到的是一个渐变的背景~,如果我们调整一下渐变的距离!

.hor {  
    width: 100%;  
    height: 100%;  
    background-image: linear-gradient(to bottom, orange 10%, transparent 10%);
}

如果盒子高度100 * 100,我们就得到了一个10px橘色,90px的透明色背景,此时转换一下思路,如果盒子高度20 * 20我们就能得到一个高度2px的橘色线条!不断重复这个橘色线条就可以绘制出一个网格线!在不能改变盒子高度的情况下我们只需要用background-size来改变背景色的高度~

.hor {  
width: 100%;  
height: 100%;  
background-image: linear-gradient(to bottom, orange 10%, transparent 10%);  
background-size: 2px 20px;  
}

成功完成横向后,竖线我们也依葫芦画瓢~


.hor {  
    width: 100%;  
    height: 100%;  
    position: relative;  
    background-image: linear-gradient(to bottom, orange 10%, transparent 10%);  
    background-size: 2px 20px;  
}  
  
.hor:before{  
    content: '';  
    position: absolute;  
    top: 0;  
    left: 0;  
    right: 0;  
    bottom: 0;  
    width: 100%;  
    height: 100%;  
    background-image: linear-gradient(to right, orange 10%, transparent 10%);  
    background-size: 20px 20px;  
}

网格如何渐变过渡

完成了一个网格之后我们就需要考虑颜色渐变的问题了,background-image本身是不支持过渡效果的,这也是这个效果实现的难点之一!

此时我们应该转变一下思路background-image不支持过渡,background却支持过渡,如果可以在保留网格的情况把background的颜色映射到网格上就好了!此时就需要我们的混合模式出场了!

解法一 mix-blend-mode

混合模式本身的效果非常多,我们这次用到的是darken属性,故名思义这个混合模式是把图层交汇处更暗的图像显示出来,而白色本身是最亮的颜色!所以如果我们把底色改成橘色到黑色的渐变,网格改成白色网格,则代表着底部的橘色背景会被混合到外层的网格上来!

可以看到效果确实实现了,但是这个方案有一个弊端就是要配合mix-blend-mode: darken;所以底色要求比较高,下面介绍的方案二是既兼顾了需求又十分方便!

解法二 @property

关于@property的用法可以点击CSS @property,让不可能变可能

我们这里的解法是既然你不支持直接的background-image过渡效果,那么我就使用@property让渐变色强行支持过渡!


@property --color {  
syntax: '<color>';  
inherits: false;  
initial-value: transparent;  
}  
 
  
.hor {  
width: 100%;  
height: 100%;  
background-image: linear-gradient(to bottom, var(--color) 10%, transparent 10%);  
background-size: 2px 20px;  
background-repeat: repeat;  
position: relative;  
animation: hor 2s ease-in-out infinite alternate;  
}

@keyframes hor {  
0% {  
--color: transparent;    
}  
100% {  
--color: orange;   
}  
}

但是需要注意的是@property至今仍是被标记成实验性语法,兼容性如下

02.png

那么有没有兼容性又好又灵活的办法呢?有,请看解法三!

解法三 svg

上述两个方案我们都是使用了CSS属性来完成的,但我们如果换个思路会发现,使用svg完成这个效果更加的容易!

首先我们还是需要绘制出来一个网格,此时就需要svg中的pattern的标签!

SVG 的 pattern 元素用于定义可重复的图案或背景,可以在其他图形元素(如矩形、圆形、路径等)中应用这个图案。pattern 是一个非常强大的工具,可以用来创建复杂的背景、填充样式,以及具有高度自定义的重复图案。

举个栗子!


<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
    <defs>

        <pattern id="squarePattern" width="100%" height="20" patternUnits="userSpaceOnUse">
            <line x1="0" x2="100%" y1="0" y2="0" stroke="#1677ff" strokeWidth="1"></line>
        </pattern>

        <pattern id="squarePattern2" width="20" height="100%" patternUnits="userSpaceOnUse">
            <line x1="0" x2="0" y1="0" y2="100%" stroke="#1677ff" strokeWidth="1"></line>
        </pattern>

    </defs>
    <rect width="100%" height="100%" fill="url(#squarePattern)" />
    <rect width="100%" height="100%" fill="url(#squarePattern2)" />

如代码所示我们用了两个pattern分别重复了横线和竖线形成了网格的样子,至于颜色变化在svg中就更加简单了!只需要使用animate标签,并设置开始颜色和结束的颜色即可。

<animate
        dur="5s"
        to="orange"
        fill='freeze'
        repeatCount="1"
        from='transparent'
        attributeName="stroke"/>

至此关于网格颜色变化我们已经有三种方案!那么该进入我们的下一步

网格如何蔓延

到了最后一步其实反而非常简单了,我们只需要切割掉这个网格并不断变化切割的路径即可,使用mask或者clip-path都可以!

这里着重写一下关于svg使用clip-path遇到的问题。关于svgclipPath我们要明白一个点是,clipPath默认是不支持百分比单位的


<clipPath id="clipPath">
    <polygon id="polygon" points="0 0, 0 100%,30% 100%, 0 0"/>
</clipPath>

上述这种写法是不生效的,因为本身clipPath中的坐标是基于整个 SVG 画布的绝对坐标来计算的,所以我们需要调整clipPathUnits来改变默认行为!

clipPathUnits 有两个主要取值:

  1. userSpaceOnUse(默认值)
  2. objectBoundingBox

1. userSpaceOnUse

clipPathUnits 设置为 userSpaceOnUse 时,clipPath 内部元素的坐标系统与整个 SVG 文档的用户坐标系统相同。也就是说,裁剪区域的坐标是基于整个 SVG 画布的绝对坐标来计算的。

<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <clipPath id="clip1" clipPathUnits="userSpaceOnUse">
      <rect x="50" y="50" width="100" height="100" />
    </clipPath>
  </defs>
  <circle cx="100" cy="100" r="100" clip-path="url(#clip1)" fill="blue" />
</svg>

  • clipPathUnits="userSpaceOnUse" 表示裁剪矩形的坐标 (x=50, y=50) 是相对于整个 SVG 画布的坐标系。
  • 结果是只有圆形的中间部分(被矩形框包含的部分)会显示出来。

2. objectBoundingBox

clipPathUnits 设置为 objectBoundingBox 时,clipPath 内部元素的坐标系统是相对于被裁剪元素的边界框 (bounding box) 来计算的。这意味着坐标是相对的,取值范围通常在 [0, 1] 之间,表示边界框的比例。

<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <clipPath id="clip2" clipPathUnits="objectBoundingBox">
      <rect x="0.25" y="0.25" width="0.5" height="0.5" />
    </clipPath>
  </defs>
  <circle cx="100" cy="100" r="100" clip-path="url(#clip2)" fill="blue" />
</svg>
  • clipPathUnits="objectBoundingBox" 表示裁剪矩形的坐标 (x=0.25, y=0.25) 是相对于圆形元素边界框的 25% 位置,宽度和高度各为圆形边界框的 50%。

  • 结果是只有圆形中间部分被显示出来,但裁剪区域是根据圆形自身的尺寸动态调整的。

看了上面两个栗子后,我们就应该知道怎么做啦!

结语

一个网格渐变动画我们就能用最少三种实现方式来实现,CSS的趣味性实在令人着迷!