整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

大型项目的CSS

大型项目的CSS

ss是前端领域的一个难缠户,一提到css,没有人会说难,也没有人愿意承认自己不会写,甚至在面试的过程中css相关的内容都很少提及,足以说明大家对css的不重视。你真的会写css吗?

关于css有两类问题值得我们重视:样式和工程。样式问题指的是具体的效果实现,能否实现某个效果,同一个效果有多种实现方式,如何抉择;工程问题是如何在大型项目中写出可维护性、可读的css。

在各种论坛中经常看到关于css是否是一门编程语言的争论。在我看来,最好用对待编程语言的态度来看待css,不要忽视css,否则,在项目后期,你会发现,你的css越来越混乱,important会越来越多,不同位置的类名互相冲突覆盖,改一个类的样式,要检查整个项目的页面是否受到影响,项目内部的css共享完全依赖拷贝……从这个角度来说,你敢说css不是编程语言?它完全有了像编程语言一样能把你搞得焦头烂额的能力。而这一切都来源于你在一开始对她的忽视与不屑。出来混,总要还的啊!

css的问题

盲目的定义基础样式

在项目开始之初,拿到UI设计稿,信心满满地开始定义css的全局基础样式,谢天谢地,css对这一点支持得很彻底,一处定义,所有页面都可以引用。

先来一个color-red。

.color-red {
	color: red
}

这样,在整个项目中,都可以给任意元素添加一个color-red类,美滋滋,我真是个小机灵鬼!

几个迭代过去,你已经把color-red这面红旗插满了整个项目。UED说,咱们改个版,所有红色的文本改为蓝色,红色的link不变!

哔!——(你发出的声音)

你又得屁颠屁颠地把一个一个的红旗拔出来,再插上蓝色的旗子(因为你不敢不干呀)。

命名冲突

在一个页面,你定义了一个.header,写了个完美的特效,发布到dev一看,就是不管用,横看竖看,本地是好的啊,神奇(生气)!过了若干时间的排查,另一个同事在另一个地方也写了一个.header,完美的覆盖了你的。把你头打歪!

编辑器可不会提醒你哦!

慢慢你会发现,这种命名冲突可太频繁了呀!头大,要不要用上css modular啊?

子类覆盖

有的小伙伴聪明地将类名嵌套定义,这就不会冲突了吧?嘿嘿,你想多了!

/* in article.css */
.article .title {
  border-bottom: 1px solid;
  font-size: 1.5em;
}

/* in widget.css */
.widget .title {
  color: gray;
  text-transform: uppercase;
}

如果在dom树里面,article和widget在一棵树的路径上,你说title是冲突呢还是不冲突呢?

以上的这些问题,在项目中相信大家都遇见过,并且项目越大,出现的概率就越高,最后就会演变成一座[屎]山。

“大家都别动,牵一发而动全身哦!”

“这就是蝴蝶效应吧???”

认识BEM

难道css这些问题就没法解决了吗?当然不是,我们来看看大神们是如何解决这些问题的。

BEM是Block、Element、Modifier的缩写,是一个类命名的规范。

首先我们来看一个例子:

/* Block */
.btn {}

/* Element that depends upon the block */ 
.btn__price {}

/* Modifier that changes the style of the block */
.btn--orange {} 
.btn--big {}

相信小伙伴们已经有了一个初步的认知:

Block是一个独立的组件(元素),定义的了“组件是什么,按钮?还是菜单?”。

Element是属于Block,是依赖于Block的元素,描述的是“Block里面的什么?比如,文本?图标?”

Modifier用于描述Block或者Element的外在表现,比如大小、颜色、状态等。

看一个例子:

<form class="search-form search-form_theme_islands">
    <input class="search-form__input">
    <button class="search-form__button search-form__button_size_m">Search</button>
</form>

search-form是Block;

search-form_theme_islands是Modifier,描述了theme为islands的search-form;

search-form__input是Element,描述的是search-form内部的input元素;

search-form__button是Element,描述的是search-form内部的button元素;

search-form__button_size_m是Modifier,描述的是size为m的search-form__button;

这样写css是不是非常的清晰?瞬间就提高了可读性和可维护性?

概念如此简单,还不赶紧多了解一下?

另外,可能有些小伙伴也注意到了,Block和Element使用双下划线分隔,和Modifier是用中划线分隔,这也不是一成不变的,可以按照自己的喜好来决定如何分割。

Saas、Css Module

有些小伙伴可能会有疑问,BEM和Saas、Css Module有什么区别?解决的问题是一样的吗?

Sass是css的预处理器,在写css的时候定义了一套规范,经过编译处理后输出为css,和BEM是两个不同的概念。使用saas或less也能实现BEM。BEM其实是不推崇类名的嵌套定义的,如果想像sass那样嵌套的写出标准的BEM,可以使用@at-root。

Css Module解决的问题是多个模块的css的命名冲突问题,个人觉得是傻瓜式解决方案,在应用层的css-in-js应用比较多,适合css入门选手。要想写好css,还是得从根本上入手呀!

家好,我是皮汤。最近业务调整,组内开启了前端工程化方面的基建,我主要负责 CSS 技术选型这一块,针对目前业界主流的几套方案进行了比较完善的调研与比较,分享给大家。

目前整个 CSS 工具链、工程化领域的主要方案如下:

而我们技术选型的标准如下:

- 开发速度快

- 开发体验友好

- 调试体验友好

- 可维护性友好

- 扩展性友好

- 可协作性友好

- 体积小

- 有最佳实践指导

目前主要需要对比的三套方案:

- Less/Sass + PostCSS 的纯 CSS c侧方案

- styled-components / emotion 的纯 CSS-in-JS 侧方案

- TailwindCSS 的以写辅助类为主的 HTML 侧方案

## 纯 CSS 侧方案

### 介绍与优点

> 维护状态:一般

> Star 数:16.7K

> 支持框架:无框架限制

> 项目地址:https://github.com/less/less.js

Less/Sass + PostCSS 这种方案在目前主流的组件库和企业级项目中使用很广,如 ant-design 等

它们的主要作用如下:

- 为 CSS 添加了类似 JS 的特性,你也可以使用变量、mixin,写判断等

- 引入了模块化的概念,可以在一个 less 文件中导入另外一个 less 文件进行使用

- 兼容标准,可以快速使用 CSS 新特性,兼容浏览器 CSS 差异等

这类工具能够与主流的工程化工具一起使用,如 Webpack,提供对应的 loader 如 sass-loader,然后就可以在 React/Vue 项目中建 `.scss` 文件,写 sass 语法,并导入到 React 组件中生效。

比如我写一个组件在响应式各个断点下的展示情况的 sass 代码:

```

.component {

width: 300px;

@media (min-width: 768px) {

width: 600px;

@media (min-resolution: 192dpi) {

background-image: url(/img/retina2x.png);

}

}

@media (min-width: 1280px) {

width: 800px;

}

}

```

或导入一些用于标准化浏览器差异的代码:

```

@import "normalize.css";

// component 相关的其他代码

```

### 不足

这类方案的一个主要问题就是,只是对 CSS 本身进行了增强,但是在帮助开发者如何写更好的 CSS、更高效、可维护的 CSS 方面并没有提供任何建议。

- 你依然需要自己定义 CSS 类、id,并且思考如何去用这些类、id 进行组合去描述 HTML 的样式

- 你依然可能会写很多冗余的 Less/Sass 代码,然后造成项目的负担,在可维护性方面也有巨大问题

### 优化

- 可以引入 CSS 设计规范:BEM 规范,来辅助用户在整个网页的 HTML 骨架以及对应的类上进行设计

- 可以引入 CSS Modules,将 CSS 文件进行 “作用域” 限制,确保在之后维护时,修改一个内容不会引起全局中其他样式的效果

#### BEM 规范

B (Block)、E(Element)、M(Modifier),具体就是通过块、元素、行为来定义所有的可视化功能。

拿设计一个 Button 为例:

```

/* Block */

.btn {}

/* 依赖于 Block 的 Element */

.btn__price {}

/* 修改 Block 风格的 Modifier */

.btn--orange {}

.btn--big {}

```

遵循上述规范的一个真实的 Button:

```

<a class="btn btn--big btn--orange" href="#">

<span class="btn__price"></span>

<span class="btn__text">BIG BUTTON</span>

</a>

```

可以获得如下的效果:

#### CSS Modules

CSS Modules 主要为 CSS 添加局部作用域和模块依赖,使得 CSS 也能具有组件化。

一个例子如下:

```

import React from 'react';

import style from './App.css';

export default ()=> {

return (

<h1 className={style.title}>

Hello World

</h1>

);

};

```

```

.title {

composes: className;

color: red;

}

```

上述经过编译会变成如下 hash 字符串:

```

<h1 class="_3zyde4l1yATCOkgn-DBWEL">

Hello World

</h1>

```

```

._3zyde4l1yATCOkgn-DBWEL {

color: red;

}

```

CSS Modules 可以与普通 CSS、Less、Sass 等结合使用。

## 纯 JS 侧方案

### 介绍与优点

> 维护状态:一般

> Star 数:35.2K

> 支持框架:React ,通过社区支持 Vue 等框架

> 项目地址:https://github.com/styled-components/styled-components

使用 JS 的模板字符串函数,在 JS 里面写 CSS 代码,这带来了两个认知的改变:

- 不是在根据 HTML,然后去写 CSS,而是站在组件设计的角度,为组件写 CSS,然后应用组件的组合思想搭建大应用

- 自动提供类似 CSS Modules 的体验,不用担心样式的全局污染问题

同时带来了很多 JS 侧才有的各种功能特性,可以让开发者用开发 JS 的方式开发 CSS,如编辑器自动补全、Lint、编译压缩等。

比如我写一个按钮:

```

const Button=styled.button`

/* Adapt the colors based on primary prop */

background: ${props=> props.primary ? "palevioletred" : "white"};

color: ${props=> props.primary ? "white" : "palevioletred"};

font-size: 1em;

margin: 1em;

padding: 0.25em 1em;

border: 2px solid palevioletred;

border-radius: 3px;

`;

render(

<div>

<Button>Normal</Button>

<Button primary>Primary</Button>

</div>

);

```

可以获得如下效果:

还可以扩展样式:

```

// The Button from the last section without the interpolations

const Button=styled.button`

color: palevioletred;

font-size: 1em;

margin: 1em;

padding: 0.25em 1em;

border: 2px solid palevioletred;

border-radius: 3px;

`;

// A new component based on Button, but with some override styles

const TomatoButton=styled(Button)`

color: tomato;

border-color: tomato;

`;

render(

<div>

<Button>Normal Button</Button>

<TomatoButton>Tomato Button</TomatoButton>

</div>

);

```

可以获得如下效果:

### 不足

虽然这类方案提供了在 JS 中写 CSS,充分利用 JS 的插值、组合等特性,然后应用 React 组件等组合思想,将组件与 CSS 进行细粒度绑定,让 CSS 跟随着组件一同进行组件化开发,同时提供和组件类似的模块化特性,相比 Less/Sass 这一套,可以复用 JS 社区的最佳实践等。

但是它仍然有一些不足:

- 仍然是是对 CSS 增强,提供非常大的灵活性,开发者仍然需要考虑如何去组织自己的 CSS

- 没有给出一套 “有观点” 的最佳实践做法

- 在上层也缺乏基于 styled-components 进行复用的物料库可进行参考设计和使用,导致在初始化使用时开发速度较低

- 在 JS 中写 CSS,势必带来一些本属于 JS 的限制,如 TS 下,需要对 Styled 的组件进行类型注释

- 官方维护的内容只兼容 React 框架,Vue 和其他框架都由社区提供支持

整体来说不太符合团队协作使用,需要人为总结最佳实践和规范等。

### 优化

- 寻求一套写 CSS 的最佳实践和团队协作规范

- 能够拥有大量的物料库或辅助类等,提高开发效率,快速完成应用开发

## 偏向 HTML 侧方案

### 介绍与优点

> 维护状态:积极

> Star 数:48.9K

> 支持框架:React、Vue、Svelte 等主流框架

> 项目地址:https://github.com/tailwindlabs/tailwindcss

典型的是 TailwindCSS,一个辅助类优先的 CSS 框架,提供如 `flex` 、`pt-4` 、`text-center` 、`rotate-90` 这样实用的类名,然后基于这些底层的辅助类向上组合构建任何网站,而且只需要专注于为 HTML 设置类名即可。

一个比较形象的例子可以参考如下代码:

```

<button class="btn btn--secondary">Decline</button>

<button class="btn btn--primary">Accept</button>

```

上述代码应用 BEM 风格的类名设计,然后设计两个按钮,而这两个类名类似主流组件库里面的 Button 的不同状态的设计,而这两个类又是由更加基础的 TailwindCSS 辅助类组成:

```

.btn {

@apply text-base font-medium rounded-lg p-3;

}

.btn--primary {

@apply bg-rose-500 text-white;

}

.btn--secondary {

@apply bg-gray-100 text-black;

}

```

上面的辅助类包含以下几类:

- 设置文本相关: `text-base` 、`font-medium` 、`text-white` 、`text-black`

- 设置背景相关的:`bg-rose-500` 、`bg-gray-100`

- 设置间距相关的:`p-3`

- 设置边角相关的:`rounded-lg`

通过 Tailwind 提供的 `@apply` 方法来对这些辅助类进行组合构建更上层的样式类。

上述的最终效果展示如下:

可以看到 TailwindCSS 将我们开发网站的过程抽象成为使用 Figma 等设计软件设计界面的过程,同时提供了一套用于设计的规范,相当于内置最佳实践,如颜色、阴影、字体相关的内容,一个很形象的图片可以说明这一点:

TailwindCSS 为我们规划了一个元素可以设置的属性,并且为每个属性给定了一组可以设置的值,这些属性+属性值组合成一个有机的设计系统,非常便于团队协作与共识,让我们开发网站就像做设计一样简单、快速,但是整体风格又能保持一致。

TailwindCSS 同时也能与主流组件库如 React、Vue、Svelte 结合,融入基于组件的 CSS 设计思想,但又只需要修改 HTML 上的类名,如我们设计一个食谱组件:

```

// Recipes.js

import Nav from './Nav.js'

import NavItem from './NavItem.js'

import List from './List.js'

import ListItem from './ListItem.js'

export default function Recipes({ recipes }) {

return (

<div className="divide-y divide-gray-100">

<Nav>

<NavItem href="/featured" isActive>Featured</NavItem>

<NavItem href="/popular">Popular</NavItem>

<NavItem href="/recent">Recent</NavItem>

</Nav>

<List>

{recipes.map((recipe)=> (

<ListItem key={recipe.id} recipe={recipe} />

))}

</List>

</div>

)

}

// Nav.js

export default function Nav({ children }) {

return (

<nav className="p-4">

<ul className="flex space-x-2">

{children}

</ul>

</nav>

)

}

// NavItem.js

export default function NavItem({ href, isActive, children }) {

return (

<li>

<a

href={href}

className={`block px-4 py-2 rounded-md ${isActive ? 'bg-amber-100 text-amber-700' : ''}`}

>

{children}

</a>

</li>

)

}

// List.js

export default function List({ children }) {

return (

<ul className="divide-y divide-gray-100">

{children}

</ul>

)

}

//ListItem.js

export default function ListItem({ recipe }) {

return (

<article className="p-4 flex space-x-4">

<img src={recipe.image} alt="" className="flex-none w-18 h-18 rounded-lg object-cover bg-gray-100" width="144" height="144" />

<div className="min-w-0 relative flex-auto sm:pr-20 lg:pr-0 xl:pr-20">

<h2 className="text-lg font-semibold text-black mb-0.5">

{recipe.title}

</h2>

<dl className="flex flex-wrap text-sm font-medium whitespace-pre">

<div>

<dt className="sr-only">Time</dt>

<dd>

<abbr title={`${recipe.time} minutes`}>{recipe.time}m</abbr>

</dd>

</div>

<div>

<dt className="sr-only">Difficulty</dt>

<dd> · {recipe.difficulty}</dd>

</div>

<div>

<dt className="sr-only">Servings</dt>

<dd> · {recipe.servings} servings</dd>

</div>

<div className="flex-none w-full mt-0.5 font-normal">

<dt className="inline">By</dt>{' '}

<dd className="inline text-black">{recipe.author}</dd>

</div>

<div class="absolute top-0 right-0 rounded-full bg-amber-50 text-amber-900 px-2 py-0.5 hidden sm:flex lg:hidden xl:flex items-center space-x-1">

<dt className="text-amber-500">

<span className="sr-only">Rating</span>

<svg width="16" height="20" fill="currentColor">

<path d="M7.05 3.691c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.372 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.539 1.118l-2.8-2.034a1 1 0 00-1.176 0l-2.8 2.034c-.783.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.363-1.118L.98 9.483c-.784-.57-.381-1.81.587-1.81H5.03a1 1 0 00.95-.69L7.05 3.69z" />

</svg>

</dt>

<dd>{recipe.rating}</dd>

</div>

</dl>

</div>

</article>

)

}

```

上述食谱的效果如下:

可以看到我们无需写一行 CSS,而是在 HTML 里面应用各种辅助类,结合 React 的组件化设计,既可以轻松完成一个非常现代化且好看的食谱组件。

除了上面的特性,TailwindCSS 在响应式、新特性支持、Dark Mode、自定义配置、自定义新的辅助类、IDE 方面也提供非常优秀的支持,除此之外还有基于 TailwindCSS 构建的物料库 Tailwind UI ,提供各种各样成熟、好看、可用于生产的物料库:

![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1cb983da6f71470b91ac764d14907998~tplv-k3u1fbpfcp-zoom-1.image)

因为需要自定的 CSS 不多,而需要自定义的 CSS 可以定义为可复用的辅助类,所以在可维护性方面也是极好的。

### 不足

- 因为要引入一个额外的运行时,TailwindCSS 辅助类到 CSS 的编译过程,而随着组件越来越多,需要编译的工作量也会变大,所以速度会有影响

- 过于底层,相当于给了用于设计的最基础的指标,但是如果我们想要快速设计网站,那么可能还需要一致的、更加上层的组件库

- 相当于引入了一套框架,具有一定的学习成本和使用成本

### 优化

- Tailwind 2.0 支持 [JIT](https://blog.tailwindcss.com/tailwindcss-2-1 "JIT"),可以大大提升编译速度,可以考虑引入

- 基于 TailwindCSS,设计一套符合自身风格的上层组件库、物料库,便于更加快速开发

- 提前探索、学习和总结一套教程与开发最佳实践

- 探索 styled-components 等结合 TailwindCSS 的开发方式

## 参考链接

- [CSS 工程化发展历程](https://bytedance.feishu.cn/docs/doccnTRF0OZtJMgKuo3y0hIDMbc# "CSS 工程化发展历程")


/ 感谢支持/

以上便是本次分享的全部内容,希望对你有所帮助^_^

喜欢的话别忘了 分享、点赞、收藏 三连哦~

欢迎关注公众号 程序员巴士,来自字节、虾皮、招银的三端兄弟,分享编程经验、技术干货与职业规划,助你少走弯路进大厂。

们每天都在网上摸鱼,作为前端开发人员,网站上微妙的细节变化通过比别人会更关注。我一直注意到的一件事是网站上的动画的流畅性。动画对于用户体验来说是非常好的,有时我们可以一些有趣的动画来留住用户。

创建高级动画听起来是一个很难的话题,但好消息是,在CSS中,可以将多个简单的动画相互叠加,以创建一个更复杂的动画

在这节课中,我们会学习如下几点:

  • 什么是贝塞尔曲线,以及如何用一行CSS来创建一个 "复杂"的动画
  • 如何将动画相互叠加以创建一个高级动画
  • 如何通过应用上面学到的两点来创建一个过山车动画

什么是贝塞尔曲线

CSS中的 cubic-bezier 函数是一个缓动函数,可以让我们完全控制动画在时间上的表现。下面是官方的定义:

贝塞尔缓动函数是一种由四个实数定义的缓和函数,指定了贝塞尔曲线的两个控制点P1P2,其端点P0P3分别固定在(0, 0)和(1, 1)P1P2x坐标被限制在[0, 1]范围内。

什么是缓动函数?

线性曲线

想象两个点P0P1,其中P0是动画的起点,P1是结束点。现在想象另一个点在两点之间线性移动,如下所示

这就是所谓的线性曲线,也是最简单的动画。

二次贝塞尔曲线

如下图所示,有三个点。P0、P1和P2。我们想让动画从P0移动到P2。在这种情况下,P1是一个控制点,控制动画的曲线。

二次方贝塞尔概念:

  • 在P0和P1之间以及P1和P2之间(用灰线表示)连接虚线
  • 点Q0沿着P0和P1之间的直线移动。同时,点Q1沿着P1和P2之间的直线移动
  • 在Q0和Q1之间连接一条虚线(用绿线表示)
  • 在Q0和Q1开始移动的同时,点B开始沿着绿线移动,B点所走的路径就是动画路径

请注意,Q0、Q1和B不以相同的速度移动。它们都必须在同一时间开始,并在同一时间完成它们的路径。因此,每一个点都是根据它所移动的线长以适当的速度移动的。

三次贝塞尔曲线

三次贝塞尔曲线由4个点组成。P0, P1, P2和P3。动画开始于P0,结束于P3。P1和P2是我们的控制点。

三次贝赛尔的工作原理如下:

  • 在(P0, P1)、(P1, P2)和(P2, P3)之间连接虚线,由灰线表示
  • 点Q0、Q1和Q2分别沿直线(P0,P1)、(P1,P2)和(P2,P3)移动
  • 在(Q0, Q1)和(Q1, Q2)之间连接虚线,它们由绿线表示。
  • 点R0和R1分别沿直线(Q0, Q1)和(Q1, Q2)移动
  • 连接R0和R1之间的线(用蓝线表示)
  • 最后,B点沿着R0和R1之间的连接线移动,B点所走的路径就是动画路径

如果你想更好地了解三次体贝塞尔的工作原理,建议你看看这个desmos链接。玩玩控制点,看看动画如何随时间变化。(注意,链接中的动画是由黑线表示的)。

叠加动画

有很多步骤的大动画可以被分解成多个小动画。在 css 中,通过添加animation-delay属性来实现这一点。计算延迟很简单,把你要计算动画延迟的那个动画之前的所有动画的时间加起来。

例如:

animation: movePointLeft 4s linear forwards, movePointDown 3s linear forwards;

这里,我们有两个动画,movePointLeftmovePointDownmovePointLeft的动画延迟是零,因为它是我们想先运行的动画。movePointDown的动画延迟是4秒,因为movePointLeft将在这段时间后完成。

因此,animation-delay属性:

animation-delay: 0s, 4s;

注意,如果有两个或更多的动画同时开始,它们的动画延迟将是一样的。此外,当你计算即将开始的动画的延迟时,把它们视为一个动。例如 :

animation: x 4s linear forwards, y 4s linear forwards, jump 2s linear forwards;

假设xy同时开始。在这种情况下,xy的动画延迟都将为零,而 jump 动画的延迟将为4秒(而不是8秒!)。

animation-delay: 0s, 0s, 4s;

创建过山车

掌握了上面的知识,是时候应用一下了。

了解动画

过山车路径由三部分组成:

  • 滑动部分
  • 循环部分
  • 还会有一些动画,在上面的两个动画之间创造水平空间

我们将首先创建一个简单的球,作为我们过山车的 "车"。

hmtl 部分:

<div id="the-cart" class="cart"></div>

css 部分:

.cart {
  background-color: rgb(100, 210, 128);
  height: 50px;
  width: 50px;
  border: 1px solid black;
  border-radius: 50px;
  position: absolute;
  left: 10vw;
  top: 30vh;
}

滑动部分

创建小球滑动的部分可以用cubic-bezier函数来完成! 这个动画是由2个动画组成的,一个是沿x轴的动画,另一个是沿y轴的动画。X轴动画是一个沿X轴的普通线性动画。它的关键帧如下:

@keyframes x {
  to {
    left: 40vw;
  }

将其添加到球路径的 animation 属性中,如下所示

animation: x 4s linear forwards

y轴动画是我们将使用cubic-bezier函数的部分。首先定义动画的关键帧。我们希望起始点和结束点之间的差异很小,以至于球达到的高度几乎相同。

@keyframes y {
  to {
    top: 29.99vh;
  }
}}

现在让我们来思考一下cubic-bezier函数。我们希望我们的路径先向右缓慢移动,然后当它滑动时,它应该走得更快。

  • 向右缓慢移动意味着$P1$将沿x轴移动。所以,我们知道它是在(V,0)。
  • 我们需要选择一个合适的V,使我们的动画缓慢地向右移动,但又不能太多,以免占用整个空间。在这种情况下,我发现0.55最适合。
  • 为了达到滑动效果,我们需要将P2Y轴下移(负值),所以P2=(X,-Y)
    • Y应该是一个大值。在这种情况下,我选择 Y=5000
    • 为了得到X,我们知道我们的动画速度在滑动时应该更快,在再次上升时应该更慢。所以,X越接近于零,动画在滑动时就越陡峭。在这种情况下,让X=0.8

现在,我们得到了一个cubic-bezier函数:

cubic-bezier(0.55, 0, 0.2, -800).

为动画属性添加关键帧:

animation: x 4s linear forwards,
    y 4s cubic-bezier(0.55, 0, 0.2, -5000) forwards;

这是我们动画的第一部分,所以动画延迟为零。我们应该添加一个animation-delay属性,因为从下面的动画开始,动画的开始时间将与第一个动画不同。

animation-delay: 0s, 0s;

地址:https://codepen.io/smashingmag/pen/VwxXBQb

添加水平空间

在做循环之前,球应该沿着X轴移动一小会儿,所以两个动画之间有空间。

定义关键帧

@keyframes x2 {
  to {
    left: 50vw;
  }
}

把它添加到 animation 属性中:

animation: x 4s linear forwards,
    y 4s cubic-bezier(0.55, 0, 0.2, -5000) forwards, x2 0.5s linear forwards;

这个动画应该在滑动动画之后开始,而滑动动画需要4秒,因此,动画延迟将是4秒。

animation-delay: 0s, 0s, 4s;

地址:https://codepen.io/smashingmag/pen/dyemExY

循环部分

要在CSS中创建一个圆(循环),我们需要把圆移到循环的中心,然后从那里开始做动画。圆的半径是100px,所以我们把圆的位置改为top: 20vh30是期望的半径(这里是10vh))。然而,这需要在滑动动画完成后发生,所以我们将创建另一个持续时间为0秒的动画,并添加一个合适的动画延迟。

关键帧:

@keyframes pointOfCircle {
  to {
    top: 20vh;
  }
}

添加到 animation 动画中:

animation: x 4s linear forwards,
    y 4s cubic-bezier(0.55, 0, 0.2, -5000) forwards, x2 0.5s linear forwards,
    pointOfCircle 0s linear forwards;

添加动画延迟, 4.5s:

animation-delay: 0s, 0s, 4s, 4.5s;

循环本身

创建一个循环动画:

  • 创建一个关键帧,将球移回原来的位置,然后旋转球。
@keyframes loop {
  from {
    transform: rotate(0deg) translateY(10vh) rotate(0deg);
  }
  to {
    transform: rotate(-360deg) translateY(10vh) rotate(360deg);
  }
}

添加到 animation 中:

animation: x 4s linear forwards,
    y 4s cubic-bezier(0.55, 0, 0.2, -5000) forwards, x2 0.5s linear forwards,
    pointOfCircle 0s linear forwards, loop 3s linear forwards;

添加动画延迟,这里是4.5s:

animation-delay: 0s, 0s, 4s, 4.5s, 4.5s;

地址:https://codepen.io/smashingmag/pen/mdLxZdR

添加水平空间

快完成了,最后 只需要在动画之后沿着x轴移动球,这样球就不会像上图中那样在循环之后完全停止。

关键帧:

@keyframes x3 {
  to {
    left: 70vw;
  }
}

添加到 animation 中:

animation: x 4s linear forwards,
    y 4s cubic-bezier(0.55, 0, 0.2, -800) forwards, x2 0.5s linear forwards,
    pointOfCircle 0s linear forwards, loop 3s linear forwards,
    x3 2s linear forwards;

加上适当的延迟,这里是7.5s:

animation-delay: 0s, 0s, 4s, 4.5s, 4.5s, 7.5s;

地址:https://codepen.io/smashingmag/pen/wvjmLKp

总结

在本节中,我们介绍了如何结合多个关键帧来创建一个复杂的动画路径。我们还介绍了贝塞尔以及如何使用它们来创建你自己的缓动函数。建议大家自己多多动手,才能更好的掌握 css 动画。

来源:https://www.smashingmagazine.com/2022/10/advanced-animations-css/