CSS Tips:连续动态渐变

在如今天丰富多彩的数字创意世界中,"连续动态渐变"无疑是一抹亮丽而生动的色彩。它不仅是设计师手中的一大利器,能为作品注入跃动的生命力,更是用户体验设计中不可或缺的艺术表达手法。今天,我们就来揭开实现"连续动态渐变"背后的技术秘密。

我们的目标

假设你需要开发一个简单的表单页,例如登录或注册页,它可以包含多种元素,比如文本、按钮、图标等。要使它们共享同一个渐变背景。这个渐变还应该在所有元素之间同步地进行动画。换句话说,需求很明确,在一个页面范围内的使用一个"连续动态渐变",供一系列元素共享使用。类似上图所示效果。假如是你,你会怎么做?

有没有兴趣挑战一下自己!

完成这一目标,你需要具备以下几个方面的知识:

  • CSS 渐变和 SVG 渐变

  • 渐变文本,甚至是渐变边框

  • 渐变动画:CSS 和 SVG 渐变动画

  • CSS 和 SVG 动画

  • CSS 遮罩

接下来,我们一步一步来拆解它!

Web 中的渐变

现如今天,谈到 Web 中的渐变,大家首先会想到 CSS 渐变。是的,CSS 提供了一系列丰富的选项,包括线性渐变、径向渐变和锥形渐变,以及它们的重复版本:

Demo 地址:codepen.io/airen/full/...

  • 线性渐变linear-gradient()repeating-linear-gradient() ,可以在两个或多个指定的颜色之间创建一个沿着一条直线的方向进行过渡的渐变效果。可以通过指定渐变的起点和终点来控制渐变的方向,也可以添加多个颜色和色标,以实现更加复杂的效果

  • 径向渐变radial-gradient()repeating-radial-gradient() ,在一个指定的圆形或椭圆形区域内创建一个渐变效果,从中心向外辐射。与线性渐变不同的是,径向渐变的起点是渐变的中心,可以通过指定渐变的形状、渐变中心位置和半径大小来控制渐变的效果

  • 锥形渐变conic-gradient()repeating-conic-gradient() ,在一个指定的圆形或椭圆形区域内创建一个渐变效果。与径向渐变不同的是,锥形渐变是沿着一个中心点向外呈锥形扩展的,可以通过指定渐变的起始角度和旋转角度来控制渐变的方式和样式

例如,我们可以像下面这样快速给一个元素添加渐变效果:

CSS 复制代码
body {
    background-image: linear-gradient(
        45deg,
        hsl(240deg 100% 20%) 0%,
        hsl(289deg 100% 21%) 11%,
        hsl(315deg 100% 27%) 22%,
        hsl(329deg 100% 36%) 33%,
        hsl(337deg 100% 43%) 44%,
        hsl(357deg 91% 59%) 56%,
        hsl(17deg 100% 59%) 67%,
        hsl(34deg 100% 53%) 78%,
        hsl(45deg 100% 50%) 89%,
        hsl(55deg 100% 50%) 100%
    );
}

Demo 地址:codepen.io/airen/full/...

当然,你也可以使用一些在线工具帮助你获得更精致的渐变效果:

URL:www.joshwcomeau.com/gradient-ge...

正如你所看到的,在 Web 开发的过程中,使用 CSS 创建一个渐变效果并不难,也不复杂。不过,CSS 渐变中也有很多不为人知的细节,比如说渐变中的计算和渐变的灰色死亡区等。如果你希望进一步了解渐变中不为人知的一面,那么我个建议你移步阅读《你不知道的 CSS 渐变》一文。

除此之外,在 SVG 中可以使用 <linearGradient><radialGradient> 来创建线性渐变和径向渐变:

XML 复制代码
<svg viewBox="0 0 1024 1024">
    <defs>
        <linearGradient id="linearGradient" x1="732" x2="732" y1="0" y2="609" gradientUnits="userSpaceOnUse">
            <stop stop-color="#ED1515" />
            <stop offset=".48" stop-color="#CB6F19" />
            <stop offset="1" stop-color="#A71EC9" />
        </linearGradient>
    </defs>
    <path fill="url(#linearGradient)" d="M0 0h1464v609H0z" />
</svg>

<svg viewBox="0 0 1024 1024">
    <defs>
        <radialGradient id="radialGradient" cx="0" cy="0" r="1" gradientTransform="rotate(-23.039 1113.031 -1643.569) scale(778.059 798.177)" gradientUnits="userSpaceOnUse">
            <stop stop-color="#ED1515" />
            <stop offset=".348" stop-color="#CB6F19" />
            <stop offset=".904" stop-color="#A71EC9" />
        </radialGradient>
    </defs>
    <path fill="url(#radialGradient)" d="M0 0h1464v609H0z" />
</svg>

Demo 地址:codepen.io/airen/full/...

与 CSS 渐变相比,SVG 渐变显得更鸡肋一点,但它们有着不同的适用场景,稍后会简单聊到这方面。

在达成今天的目标,在使用渐变时可能会碰到几个具有挑战性的方面,比如渐变文本、渐变边框、渐变图标和渐变动画。

渐变文本

到目前为止,在 CSS 中还不能直接将渐变应用于 color 属性。如果要创建一个渐变文本,我们可以使用 CSS 的渐变函数,并结合 CSS 的 background-cliptext-fill-colortext-stroke 来实现文本的渐变填充和渐变描边等效果:

CSS 复制代码
:root {
    --fluid: clamp(1rem, 1.5rem + 3vw, 2.5rem);
    --color-one: hsl(15 90% 55%);
    --color-two: hsl(40 95% 55%);
    --color-background: #000119;
    --stroke-width: calc(1em / 16);
    --letter-spacing: calc(1em / 24);
}

h1 {
    text-transform: uppercase;
    letter-spacing: var(--letter-spacing);
    background: linear-gradient(
        90deg,
        var(--color-one),
        var(--color-two),
        var(--color-one)
    ) 0 0 / 100% 100%;
    background-clip: text;
    color: var(--color-background);
    
    &:nth-child(1) {
        -webkit-text-fill-color: transparent;
    }
    
    &:nth-child(2) {
        -webkit-text-stroke-color: transparent;
        -webkit-text-stroke-width: var(--stroke-width);
    }
}

Demo 地址:codepen.io/airen/full/...

这里有两个小技巧。其一,将渐变文本的 width 设置为 fit-content ,这样可以将元素的宽度限制为文本的实际宽度,我们就可以一次看到整个渐变效果。其二,你可以使用 background-size 来调整渐变尺寸,获得不同的渐变文本效果:

CSS 复制代码
@layer demo {
    :root {
        --fluid: clamp(1rem, 2.5rem + 3vw, 3.5rem);
        --color-one: #09f1b8;
        --color-two: #00a2ff;
        --color-three:  #ff00d2;
        --color-four: #fed90f;
        --color-background: #000119;
        --stroke-width: calc(1em / 16);
        --letter-spacing: calc(1em / 24);
        --bg-size: 50vw;
    }

    h1 {
        text-transform: uppercase;
        letter-spacing: var(--letter-spacing);
        font-size: round(down, var(--fluid), 2px);
        background: linear-gradient(
            to right,
            var(--color-one),
            var(--color-two),
            var(--color-three),
            var(--color-four)
          )
          center / var(--bg-size) 100%;
        background-clip: text;
        color: var(--color-background);
        width: fit-content;
        transition: all .2s ease-in-out;

        &:nth-child(1) {
            -webkit-text-fill-color: transparent;
        }
        
        &:nth-child(2) {
            -webkit-text-stroke-color: transparent;
            -webkit-text-stroke-width: var(--stroke-width);
        }
    }
}

Demo 地址:codepen.io/airen/full/...

注意,fit-content 属性是内在尺寸,它与 min-contentmax-content 属性能根据元素内容来决定元素大小。有关于这方面更详细的介绍,可以移步阅读现代 Web 布中的《内在 Web 设计》!

你可能会问,那 SVG 渐变能构建渐变文本和描边文本?简单地说,SVG 的渐变只能用于填充或描边 SVG 的 <text><tspan> 元素。即使 SVG 内联嵌套在 HTML 文档中,它也无法用于填充和描边 HTML 元素的文本内容。

渐变边框

对于渐变边框,我们需要使用虚拟边框相关的技术,最常见的方式之一,就是将边框设置为透明色,然后应用 background-imagebackground-originbackground-clip 等特性,将渐变背景应用于边框。

除此之外,你还可以利用伪元素 ::before::after 以及 border-imagemask 等特性来创建渐变边框。

Demo 地址:codepen.io/airen/full/...

有关于这方面更详细的介绍,请移步阅读《CSS Tips:边框动画》!

渐变图标

对于渐变图标,那得分情况来看,因为这和图标创建的技术方案很有关系。在 Web 开发当中,有多种不同的方式给 Web 添加图标,最为常见的是字体图标(Icon Fonts)和 SVG 图标。

如果你采用的是字体图标技术方案,那么创建一个带有渐变效果的图标和创建渐变文本是一样的:

HTML 复制代码
<i class="fa fa-instagram instagram" aria-hidden="true"></i>
CSS 复制代码
@import url("https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css")

.fa {
    background: linear-gradient(
        to right,
        var(--color-one),
        var(--color-two),
        var(--color-three),
        var(--color-four)
      )
      center / var(--bg-size) 100%;
    background-clip: text;
    color: var(--color-background);
    -webkit-text-fill-color: transparent;
}

Demo 地址:codepen.io/airen/full/...

如果你的图标是 SVG 图标,并且以内联的方式嵌套在 HTML 文档中,那么 SVG 渐变是一个不错方案:

XML 复制代码
<svg class="sr-only">
    <defs>
        <linearGradient id="gradient-horizontal">
            <stop offset="0%" stop-color="var(--stop-color-1, #447799)" />
            <stop offset="50%" stop-color="var(--stop-color-2,#224488)" />
            <stop offset="100%" stop-color="var(--stop-color-3, #112266)" />
        </linearGradient>
        <linearGradient id="gradient-vertical" x2="0" y2="100%" href="#gradient-horizontal">
            <stop offset="0%" stop-color="var(--stop-color-1, #447799)" />
            <stop offset="50%" stop-color="var(--stop-color-2,#224488)" />
            <stop offset="100%" stop-color="var(--stop-color-3, #112266)" />
        </linearGradient>
        <linearGradient id="gradient-angle" x2="100%" y2="100%" href="#gradient-horizontal">
            <stop offset="0%" stop-color="var(--stop-color-1, #447799)" />
            <stop offset="50%" stop-color="var(--stop-color-2,#224488)" />
            <stop offset="100%" stop-color="var(--stop-color-3, #112266)" />
        </linearGradient>
        <radialGradient id="radialGradient">
            <stop offset="0%" stop-color="var(--stop-color-1, #447799)" />
            <stop offset="50%" stop-color="var(--stop-color-2,#224488)" />
            <stop offset="100%" stop-color="var(--stop-color-3, #112266)" />
        </radialGradient>
    </defs>
</svg>

<svg class="icon icon--business">
    <use href="#business" />
</svg>
CSS 复制代码
#gradient-horizontal {
    --stop-color-1: #a770ef;
    --stop-color-2: #cf8bf3;
    --stop-color-3: #fdb99b;
}

#gradient-vertical {
    --stop-color-1: #00c3ff;
    --stop-color-2: #77e190;
    --stop-color-3: #ffff1c;
}
  
#gradient-angle {
    --stop-color-1: #77e190;
    --stop-color-2: #a770ef;
    --stop-color-3: #FF5722;
}

#radialGradient {
    --stop-color-1: #03A9F4;
    --stop-color-2: #ff98007d;
    --stop-color-3: #F44336;
}

Demo 地址:codepen.io/airen/full/...

需要知道的是,如果你使用的是 <symbol> 创建的雪碧图,然后通过 <use> 引用已实例的的图标,那么你需要知道穿透 <use> 给其后代元素(Shadow DOM)设置样式。如果你从未接触过方面的知识,那么个人建议你移步阅读《CSS Tips:CSS 如何穿透 SVG 的 <use>》!

渐变动画

我们今天有一个非常明确的目标需求,那就是渐变是有动画效果的。

如果你对 CSS 渐变稍微有所了解的话,你应该知道,要在 @keyframes 中动画化渐变颜色是比较困难的,你得另辟蹊径。为什么呢?

在 CSS 中,使用 CSS 渐变函数创建的效果,会被视为 <image> 类型。这意味着,你要给渐变函数已绘制好的图片设置动画效果,那么只能通过改变 background-position 来实现。

CSS 复制代码
@layer animation {
    @keyframes move-bg {
        to {
            background-position: calc(100vw + var(--bg-size)) 0;
        }
    }
  
    :is(h1, .fa) {
        animation: move-bg 10s infinite linear alternate;
    }
}

Demo 地址:codepen.io/airen/full/...

注意,上面示例中通过 @keyframes 定义了一个关键帧动画,这是一个非常简单的动画效果,就是改变背景图片的位置。因此,你看到的渐变是在动的。不管是 CSS 动画,还是 SVG 动画,如果你想制作出一些有创意的动画效果,你必须得对动画相关的知识有所了解。如果你是一名 Web 动画设计师,或者你对 Web 动画非常感兴趣,那么我的小册《Web 动画之旅》是不错的选择。你将从小册中获得有关于 Web 动画更全面的知识,包括 Web 动画理论基础设计原则CSS 动画SVG 动画JavaScript 动画制作及相关技术等。

随着更多的 CSS 新特性的出现,我们可以利用CSS 自定义属性中的 @property 规则直接对渐变颜色进行动画处理。

CSS 复制代码
@property --startColor {
    syntax: "<color>";
    initial-value: magenta;
    inherits: false;
}

@property --stopColor {
    syntax: "<color>";
    initial-value: magenta;
    inherits: false;
}

@property --stop {
    syntax: "<percentage>";
    initial-value: 50%;
    inherits: false;
}

.gradient__css__houdini {
    --startColor: #2196f3;
    --stopColor: #ff9800;
  
    transition: --stop 0.5s, --startColor 0.2s, --stopColor 0.2s;
    background: linear-gradient(
        to right,
        var(--startColor) var(--stop),
        var(--stopColor)
    );
}

.gradient__css__houdini:hover {
    --startColor: #ff9800;
    --stopColor: #2196f3;
    --stop: 80%;
}

Demo 地址:codepen.io/airen/full/...

如何让渐变连续化

到目前为止,我们的做准备工作已就绪。但要达成最终目标,还有最为关键的一步,那就是如何让渐变连续化。

今天的主要目标之一,是需要所有元素共享一个动画化的渐变。那么问题就来了,如何让所有元素应用的渐变看起来是共享同一个渐变呢?

以渐变文本为例,我们把上面的结果稍微调整一下:

HTML 复制代码
<h1><span>Hello, CSS!</span><span> CSS is awesome!</span></h1>

<h1> 文本分成两段,分别应用于两个 <span> 元素中。与此同时,文本渐变的样式应用于 <span> 元素上:

CSS 复制代码
span {
    text-transform: uppercase;
    letter-spacing: var(--letter-spacing);
    font-size: round(down, var(--fluid), 2px);
    background: linear-gradient(
        to right,
        var(--color-one),
        var(--color-two),
        var(--color-three),
        var(--color-four)
      )
      center / var(--bg-size) 100%;
    background-clip: text;
    color: var(--color-background);
    width: fit-content;
    transition: all 0.2s ease-in-out;
    -webkit-text-fill-color: transparent;
    outline: 1px dashed lime;
}

每个 span 应用了同一个渐变,但两个 span 元素结合起来,它们更像是独立的渐变,而不是同一个渐变。或者说,我们需要的是像下面这样的渐变效果,即使两个 span 应用的是同一个渐变,但视觉效果上要看起来是真的同一个渐变,而不是让你看起来像两个不同的渐变。

这才是我们想要的效果。

在 CSS 中,我们只需要一行 CSS 代码即可:

CSS 复制代码
span {
    background-attachment: fixed;
}

这样会将背景固定到视口而不是元素本身,给人一种所有东西共享同一大渐变的印象。

Demo 地址:codepen.io/airen/full/...

我曾在我的小册《防御式 CSS 精讲》的《Web 图片:你不应该遗忘的 CSS 技巧》中提到过,background-attachment: fixed; 很容易被大家遗忘,但在一些特殊的场景之下,它真的非常有用。比如我们今天就用上了,除此之外,它还是构建滚动视差的必备技术之一:

Demo 地址:codepen.io/airen/full/...

该方法被称为"背景吸附大法" !(不是吸星大法!)

案例:应用连续渐变背景的表单

回到我们文章最初的目标:

Demo 地址:codepen.io/airen/full/...

我就直接上代码了。

HTML 复制代码
<div class="grid">
    <h3>Sign In</h3>
    <form class="login">
        <div class="control">
            <label for="login__username">
                <svg class="icon">
                    <use xlink:href="#icon-user" />
                </svg>
                <span class="sr-only">Username</span>
            </label>
            <input autocomplete="username" id="login__username" type="text" name="username" class="form__input" placeholder="Username" required />
        </div>
    
        <div class="control">
            <label for="login__password">
                <svg class="icon">
                    <use xlink:href="#icon-lock" />
                </svg>
                <span class="sr-only">Password</span>
            </label>
            <input id="login__password" type="password" name="password" class="form__input" placeholder="Password" required />
        </div>
    
        <div class="control">
              <button class="button">Sign In</button>
        </div>
    </form>

    <p class="text--center">
        Not a member? 
        <a href="#">Sign up now</a> 
        <svg class="icon">
            <use xlink:href="#icon-arrow-right" fill="#fff" />
        </svg>
    </p>
</div>

<svg xmlns="http://www.w3.org/2000/svg" class="sr-only">
    <symbol id="icon-arrow-right" viewBox="0 0 1792 1792">
        <path d="M1600 960q0 54-37 91l-651 651q-39 37-91 37-51 0-90-37l-75-75q-38-38-38-91t38-91l293-293H245q-52 0-84.5-37.5T128 1024V896q0-53 32.5-90.5T245 768h704L656 474q-38-36-38-90t38-90l75-75q38-38 90-38 53 0 91 38l651 651q37 35 37 90z" />
    </symbol>
    <symbol id="icon-lock" viewBox="0 0 1792 1792">
        <path d="M640 768h512V576q0-106-75-181t-181-75-181 75-75 181v192zm832 96v576q0 40-28 68t-68 28H416q-40 0-68-28t-28-68V864q0-40 28-68t68-28h32V576q0-184 132-316t316-132 316 132 132 316v192h32q40 0 68 28t28 68z" />
    </symbol>
    <symbol id="icon-user" viewBox="0 0 1792 1792">
        <path d="M1600 1405q0 120-73 189.5t-194 69.5H459q-121 0-194-69.5T192 1405q0-53 3.5-103.5t14-109T236 1084t43-97.5 62-81 85.5-53.5T538 832q9 0 42 21.5t74.5 48 108 48T896 971t133.5-21.5 108-48 74.5-48 42-21.5q61 0 111.5 20t85.5 53.5 62 81 43 97.5 26.5 108.5 14 109 3.5 103.5zm-320-893q0 159-112.5 271.5T896 896 624.5 783.5 512 512t112.5-271.5T896 128t271.5 112.5T1280 512z" />
    </symbol>
</svg>

核心 CSS 代码:

CSS 复制代码
@layer gradient {
    :root {
        --gradient: linear-gradient(135deg, rgb(240, 105, 102), rgb(250, 214, 166));
    }

    .grid {
        :is(h3, label, input, p, a, .button) {
            background-image: var(--gradient) !important;
            background-attachment: fixed;
            background-size: 200%;
            background-position: 0% 0;
            animation: shimmer 2s linear infinite alternate;
        }
    }

    :is(h3,p,a) {
        -webkit-background-clip: text;
        color: transparent;
        background-clip: text;
        width: fit-content;
    }

    use {
        mix-blend-mode: color-dodge;
    }

    @keyframes shimmer {
        from {
             background-position: 0 0;
        }
        to {
             background-position: 100% 0;
        }
    }
}

你也可以尝试着配置不同的渐变颜色,最终达到你想要的效果:

Demo 地址:codepen.io/airen/full/...

注意,示例中应用了许多较新的 CSS 的特性,如果你阅读代码感到吃力的话,建议你移步阅读 《现代 CSS》,里面涵盖了最新 CSS 特性

最后再插一句,如果你希望图标也达到渐变文本相似的效果,需要应用 CSS 的遮罩图像特效等功能。例如下面这个效果:

Demo 地址:codepen.io/airen/full/...

具体代码不在这里呈现了,感兴趣请阅读示例源码!

小结

连续性渐变的核心目标是确保渐变在不同元素之间连续流动,而不是在每个元素中重新开始。为了实现这一目标,我们使用了 background-attachment: fixed; 这个属性将背景固定在视口而不是元素本身上。这样做可以创建一个整体上共享相同大渐变的假象。

最后,希望这个小技巧能给你的工作带来便利!


如果你对 CSS 方面的技巧感兴趣,请移步阅读:


如果你觉得该教程对你有所帮助,请给我点个赞。要是你喜欢 CSS ,或者想进一步了解和掌握 CSS 相关的知识,请关注我的专栏,或者移步阅读下面这些系列教程:

相关推荐
gqkmiss3 分钟前
Chrome 浏览器 131 版本开发者工具(DevTools)更新内容
前端·chrome·浏览器·chrome devtools
Summer不秃9 分钟前
Flutter之使用mqtt进行连接和信息传输的使用案例
前端·flutter
旭日猎鹰13 分钟前
Flutter踩坑记录(二)-- GestureDetector+Expanded点击无效果
前端·javascript·flutter
Viktor_Ye19 分钟前
高效集成易快报与金蝶应付单的方案
java·前端·数据库
hummhumm21 分钟前
第 25 章 - Golang 项目结构
java·开发语言·前端·后端·python·elasticsearch·golang
乐闻x1 小时前
Vue.js 性能优化指南:掌握 keep-alive 的使用技巧
前端·vue.js·性能优化
一条晒干的咸魚1 小时前
【Web前端】创建我的第一个 Web 表单
服务器·前端·javascript·json·对象·表单
Amd7941 小时前
Nuxt.js 应用中的 webpack:compiled 事件钩子
前端·webpack·开发·编译·nuxt.js·事件·钩子
生椰拿铁You1 小时前
09 —— Webpack搭建开发环境
前端·webpack·node.js
狸克先生1 小时前
如何用AI写小说(二):Gradio 超简单的网页前端交互
前端·人工智能·chatgpt·交互