ss是前端領(lǐng)域的一個(gè)難纏戶,一提到css,沒有人會(huì)說難,也沒有人愿意承認(rèn)自己不會(huì)寫,甚至在面試的過程中css相關(guān)的內(nèi)容都很少提及,足以說明大家對(duì)css的不重視。你真的會(huì)寫css嗎?
關(guān)于css有兩類問題值得我們重視:樣式和工程。樣式問題指的是具體的效果實(shí)現(xiàn),能否實(shí)現(xiàn)某個(gè)效果,同一個(gè)效果有多種實(shí)現(xiàn)方式,如何抉擇;工程問題是如何在大型項(xiàng)目中寫出可維護(hù)性、可讀的css。
在各種論壇中經(jīng)常看到關(guān)于css是否是一門編程語言的爭論。在我看來,最好用對(duì)待編程語言的態(tài)度來看待css,不要忽視css,否則,在項(xiàng)目后期,你會(huì)發(fā)現(xiàn),你的css越來越混亂,important會(huì)越來越多,不同位置的類名互相沖突覆蓋,改一個(gè)類的樣式,要檢查整個(gè)項(xiàng)目的頁面是否受到影響,項(xiàng)目內(nèi)部的css共享完全依賴拷貝……從這個(gè)角度來說,你敢說css不是編程語言?它完全有了像編程語言一樣能把你搞得焦頭爛額的能力。而這一切都來源于你在一開始對(duì)她的忽視與不屑。出來混,總要還的啊!
盲目的定義基礎(chǔ)樣式
在項(xiàng)目開始之初,拿到UI設(shè)計(jì)稿,信心滿滿地開始定義css的全局基礎(chǔ)樣式,謝天謝地,css對(duì)這一點(diǎn)支持得很徹底,一處定義,所有頁面都可以引用。
先來一個(gè)color-red。
.color-red {
color: red
}
這樣,在整個(gè)項(xiàng)目中,都可以給任意元素添加一個(gè)color-red類,美滋滋,我真是個(gè)小機(jī)靈鬼!
幾個(gè)迭代過去,你已經(jīng)把color-red這面紅旗插滿了整個(gè)項(xiàng)目。UED說,咱們改個(gè)版,所有紅色的文本改為藍(lán)色,紅色的link不變!
嗶!——(你發(fā)出的聲音)
你又得屁顛屁顛地把一個(gè)一個(gè)的紅旗拔出來,再插上藍(lán)色的旗子(因?yàn)槟悴桓也桓裳剑?/p>
命名沖突
在一個(gè)頁面,你定義了一個(gè).header,寫了個(gè)完美的特效,發(fā)布到dev一看,就是不管用,橫看豎看,本地是好的啊,神奇(生氣)!過了若干時(shí)間的排查,另一個(gè)同事在另一個(gè)地方也寫了一個(gè).header,完美的覆蓋了你的。把你頭打歪!
編輯器可不會(huì)提醒你哦!
慢慢你會(huì)發(fā)現(xiàn),這種命名沖突可太頻繁了呀!頭大,要不要用上css modular啊?
子類覆蓋
有的小伙伴聰明地將類名嵌套定義,這就不會(huì)沖突了吧?嘿嘿,你想多了!
/* 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是沖突呢還是不沖突呢?
以上的這些問題,在項(xiàng)目中相信大家都遇見過,并且項(xiàng)目越大,出現(xiàn)的概率就越高,最后就會(huì)演變成一座[屎]山。
“大家都別動(dòng),牽一發(fā)而動(dòng)全身哦!”
“這就是蝴蝶效應(yīng)吧???”
難道css這些問題就沒法解決了嗎?當(dāng)然不是,我們來看看大神們是如何解決這些問題的。
BEM是Block、Element、Modifier的縮寫,是一個(gè)類命名的規(guī)范。
首先我們來看一個(gè)例子:
/* Block */
.btn {}
/* Element that depends upon the block */
.btn__price {}
/* Modifier that changes the style of the block */
.btn--orange {}
.btn--big {}
相信小伙伴們已經(jīng)有了一個(gè)初步的認(rèn)知:
Block是一個(gè)獨(dú)立的組件(元素),定義的了“組件是什么,按鈕?還是菜單?”。
Element是屬于Block,是依賴于Block的元素,描述的是“Block里面的什么?比如,文本?圖標(biāo)?”
Modifier用于描述Block或者Element的外在表現(xiàn),比如大小、顏色、狀態(tài)等。
看一個(gè)例子:
<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內(nèi)部的input元素;
search-form__button是Element,描述的是search-form內(nèi)部的button元素;
search-form__button_size_m是Modifier,描述的是size為m的search-form__button;
這樣寫css是不是非常的清晰?瞬間就提高了可讀性和可維護(hù)性?
概念如此簡單,還不趕緊多了解一下?
另外,可能有些小伙伴也注意到了,Block和Element使用雙下劃線分隔,和Modifier是用中劃線分隔,這也不是一成不變的,可以按照自己的喜好來決定如何分割。
有些小伙伴可能會(huì)有疑問,BEM和Saas、Css Module有什么區(qū)別?解決的問題是一樣的嗎?
Sass是css的預(yù)處理器,在寫css的時(shí)候定義了一套規(guī)范,經(jīng)過編譯處理后輸出為css,和BEM是兩個(gè)不同的概念。使用saas或less也能實(shí)現(xiàn)BEM。BEM其實(shí)是不推崇類名的嵌套定義的,如果想像sass那樣嵌套的寫出標(biāo)準(zhǔn)的BEM,可以使用@at-root。
Css Module解決的問題是多個(gè)模塊的css的命名沖突問題,個(gè)人覺得是傻瓜式解決方案,在應(yīng)用層的css-in-js應(yīng)用比較多,適合css入門選手。要想寫好css,還是得從根本上入手呀!
家好,我是皮湯。最近業(yè)務(wù)調(diào)整,組內(nèi)開啟了前端工程化方面的基建,我主要負(fù)責(zé) CSS 技術(shù)選型這一塊,針對(duì)目前業(yè)界主流的幾套方案進(jìn)行了比較完善的調(diào)研與比較,分享給大家。
目前整個(gè) CSS 工具鏈、工程化領(lǐng)域的主要方案如下:
而我們技術(shù)選型的標(biāo)準(zhǔn)如下:
- 開發(fā)速度快
- 開發(fā)體驗(yàn)友好
- 調(diào)試體驗(yàn)友好
- 可維護(hù)性友好
- 擴(kuò)展性友好
- 可協(xié)作性友好
- 體積小
- 有最佳實(shí)踐指導(dǎo)
目前主要需要對(duì)比的三套方案:
- Less/Sass + PostCSS 的純 CSS c側(cè)方案
- styled-components / emotion 的純 CSS-in-JS 側(cè)方案
- TailwindCSS 的以寫輔助類為主的 HTML 側(cè)方案
## 純 CSS 側(cè)方案
### 介紹與優(yōu)點(diǎn)
> 維護(hù)狀態(tài):一般
> Star 數(shù):16.7K
> 支持框架:無框架限制
> 項(xiàng)目地址:https://github.com/less/less.js
Less/Sass + PostCSS 這種方案在目前主流的組件庫和企業(yè)級(jí)項(xiàng)目中使用很廣,如 ant-design 等
它們的主要作用如下:
- 為 CSS 添加了類似 JS 的特性,你也可以使用變量、mixin,寫判斷等
- 引入了模塊化的概念,可以在一個(gè) less 文件中導(dǎo)入另外一個(gè) less 文件進(jìn)行使用
- 兼容標(biāo)準(zhǔn),可以快速使用 CSS 新特性,兼容瀏覽器 CSS 差異等
這類工具能夠與主流的工程化工具一起使用,如 Webpack,提供對(duì)應(yīng)的 loader 如 sass-loader,然后就可以在 React/Vue 項(xiàng)目中建 `.scss` 文件,寫 sass 語法,并導(dǎo)入到 React 組件中生效。
比如我寫一個(gè)組件在響應(yīng)式各個(gè)斷點(diǎn)下的展示情況的 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;
}
}
```
或?qū)胍恍┯糜跇?biāo)準(zhǔn)化瀏覽器差異的代碼:
```
@import "normalize.css";
// component 相關(guān)的其他代碼
```
### 不足
這類方案的一個(gè)主要問題就是,只是對(duì) CSS 本身進(jìn)行了增強(qiáng),但是在幫助開發(fā)者如何寫更好的 CSS、更高效、可維護(hù)的 CSS 方面并沒有提供任何建議。
- 你依然需要自己定義 CSS 類、id,并且思考如何去用這些類、id 進(jìn)行組合去描述 HTML 的樣式
- 你依然可能會(huì)寫很多冗余的 Less/Sass 代碼,然后造成項(xiàng)目的負(fù)擔(dān),在可維護(hù)性方面也有巨大問題
### 優(yōu)化
- 可以引入 CSS 設(shè)計(jì)規(guī)范:BEM 規(guī)范,來輔助用戶在整個(gè)網(wǎng)頁的 HTML 骨架以及對(duì)應(yīng)的類上進(jìn)行設(shè)計(jì)
- 可以引入 CSS Modules,將 CSS 文件進(jìn)行 “作用域” 限制,確保在之后維護(hù)時(shí),修改一個(gè)內(nèi)容不會(huì)引起全局中其他樣式的效果
#### BEM 規(guī)范
B (Block)、E(Element)、M(Modifier),具體就是通過塊、元素、行為來定義所有的可視化功能。
拿設(shè)計(jì)一個(gè) Button 為例:
```
/* Block */
.btn {}
/* 依賴于 Block 的 Element */
.btn__price {}
/* 修改 Block 風(fēng)格的 Modifier */
.btn--orange {}
.btn--big {}
```
遵循上述規(guī)范的一個(gè)真實(shí)的 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 也能具有組件化。
一個(gè)例子如下:
```
import React from 'react';
import style from './App.css';
export default ()=> {
return (
<h1 className={style.title}>
Hello World
</h1>
);
};
```
```
.title {
composes: className;
color: red;
}
```
上述經(jīng)過編譯會(huì)變成如下 hash 字符串:
```
<h1 class="_3zyde4l1yATCOkgn-DBWEL">
Hello World
</h1>
```
```
._3zyde4l1yATCOkgn-DBWEL {
color: red;
}
```
CSS Modules 可以與普通 CSS、Less、Sass 等結(jié)合使用。
## 純 JS 側(cè)方案
### 介紹與優(yōu)點(diǎn)
> 維護(hù)狀態(tài):一般
> Star 數(shù):35.2K
> 支持框架:React ,通過社區(qū)支持 Vue 等框架
> 項(xiàng)目地址:https://github.com/styled-components/styled-components
使用 JS 的模板字符串函數(shù),在 JS 里面寫 CSS 代碼,這帶來了兩個(gè)認(rèn)知的改變:
- 不是在根據(jù) HTML,然后去寫 CSS,而是站在組件設(shè)計(jì)的角度,為組件寫 CSS,然后應(yīng)用組件的組合思想搭建大應(yīng)用
- 自動(dòng)提供類似 CSS Modules 的體驗(yàn),不用擔(dān)心樣式的全局污染問題
同時(shí)帶來了很多 JS 側(cè)才有的各種功能特性,可以讓開發(fā)者用開發(fā) JS 的方式開發(fā) CSS,如編輯器自動(dòng)補(bǔ)全、Lint、編譯壓縮等。
比如我寫一個(gè)按鈕:
```
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>
);
```
可以獲得如下效果:
還可以擴(kuò)展樣式:
```
// 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 的插值、組合等特性,然后應(yīng)用 React 組件等組合思想,將組件與 CSS 進(jìn)行細(xì)粒度綁定,讓 CSS 跟隨著組件一同進(jìn)行組件化開發(fā),同時(shí)提供和組件類似的模塊化特性,相比 Less/Sass 這一套,可以復(fù)用 JS 社區(qū)的最佳實(shí)踐等。
但是它仍然有一些不足:
- 仍然是是對(duì) CSS 增強(qiáng),提供非常大的靈活性,開發(fā)者仍然需要考慮如何去組織自己的 CSS
- 沒有給出一套 “有觀點(diǎn)” 的最佳實(shí)踐做法
- 在上層也缺乏基于 styled-components 進(jìn)行復(fù)用的物料庫可進(jìn)行參考設(shè)計(jì)和使用,導(dǎo)致在初始化使用時(shí)開發(fā)速度較低
- 在 JS 中寫 CSS,勢(shì)必帶來一些本屬于 JS 的限制,如 TS 下,需要對(duì) Styled 的組件進(jìn)行類型注釋
- 官方維護(hù)的內(nèi)容只兼容 React 框架,Vue 和其他框架都由社區(qū)提供支持
整體來說不太符合團(tuán)隊(duì)協(xié)作使用,需要人為總結(jié)最佳實(shí)踐和規(guī)范等。
### 優(yōu)化
- 尋求一套寫 CSS 的最佳實(shí)踐和團(tuán)隊(duì)協(xié)作規(guī)范
- 能夠擁有大量的物料庫或輔助類等,提高開發(fā)效率,快速完成應(yīng)用開發(fā)
## 偏向 HTML 側(cè)方案
### 介紹與優(yōu)點(diǎn)
> 維護(hù)狀態(tài):積極
> Star 數(shù):48.9K
> 支持框架:React、Vue、Svelte 等主流框架
> 項(xiàng)目地址:https://github.com/tailwindlabs/tailwindcss
典型的是 TailwindCSS,一個(gè)輔助類優(yōu)先的 CSS 框架,提供如 `flex` 、`pt-4` 、`text-center` 、`rotate-90` 這樣實(shí)用的類名,然后基于這些底層的輔助類向上組合構(gòu)建任何網(wǎng)站,而且只需要專注于為 HTML 設(shè)置類名即可。
一個(gè)比較形象的例子可以參考如下代碼:
```
<button class="btn btn--secondary">Decline</button>
<button class="btn btn--primary">Accept</button>
```
上述代碼應(yīng)用 BEM 風(fēng)格的類名設(shè)計(jì),然后設(shè)計(jì)兩個(gè)按鈕,而這兩個(gè)類名類似主流組件庫里面的 Button 的不同狀態(tài)的設(shè)計(jì),而這兩個(gè)類又是由更加基礎(chǔ)的 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;
}
```
上面的輔助類包含以下幾類:
- 設(shè)置文本相關(guān): `text-base` 、`font-medium` 、`text-white` 、`text-black`
- 設(shè)置背景相關(guān)的:`bg-rose-500` 、`bg-gray-100`
- 設(shè)置間距相關(guān)的:`p-3`
- 設(shè)置邊角相關(guān)的:`rounded-lg`
通過 Tailwind 提供的 `@apply` 方法來對(duì)這些輔助類進(jìn)行組合構(gòu)建更上層的樣式類。
上述的最終效果展示如下:
可以看到 TailwindCSS 將我們開發(fā)網(wǎng)站的過程抽象成為使用 Figma 等設(shè)計(jì)軟件設(shè)計(jì)界面的過程,同時(shí)提供了一套用于設(shè)計(jì)的規(guī)范,相當(dāng)于內(nèi)置最佳實(shí)踐,如顏色、陰影、字體相關(guān)的內(nèi)容,一個(gè)很形象的圖片可以說明這一點(diǎn):
TailwindCSS 為我們規(guī)劃了一個(gè)元素可以設(shè)置的屬性,并且為每個(gè)屬性給定了一組可以設(shè)置的值,這些屬性+屬性值組合成一個(gè)有機(jī)的設(shè)計(jì)系統(tǒng),非常便于團(tuán)隊(duì)協(xié)作與共識(shí),讓我們開發(fā)網(wǎng)站就像做設(shè)計(jì)一樣簡單、快速,但是整體風(fēng)格又能保持一致。
TailwindCSS 同時(shí)也能與主流組件庫如 React、Vue、Svelte 結(jié)合,融入基于組件的 CSS 設(shè)計(jì)思想,但又只需要修改 HTML 上的類名,如我們?cè)O(shè)計(jì)一個(gè)食譜組件:
```
// 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 里面應(yīng)用各種輔助類,結(jié)合 React 的組件化設(shè)計(jì),既可以輕松完成一個(gè)非常現(xiàn)代化且好看的食譜組件。
除了上面的特性,TailwindCSS 在響應(yīng)式、新特性支持、Dark Mode、自定義配置、自定義新的輔助類、IDE 方面也提供非常優(yōu)秀的支持,除此之外還有基于 TailwindCSS 構(gòu)建的物料庫 Tailwind UI ,提供各種各樣成熟、好看、可用于生產(chǎn)的物料庫:

因?yàn)樾枰远ǖ?CSS 不多,而需要自定義的 CSS 可以定義為可復(fù)用的輔助類,所以在可維護(hù)性方面也是極好的。
### 不足
- 因?yàn)橐胍粋€(gè)額外的運(yùn)行時(shí),TailwindCSS 輔助類到 CSS 的編譯過程,而隨著組件越來越多,需要編譯的工作量也會(huì)變大,所以速度會(huì)有影響
- 過于底層,相當(dāng)于給了用于設(shè)計(jì)的最基礎(chǔ)的指標(biāo),但是如果我們想要快速設(shè)計(jì)網(wǎng)站,那么可能還需要一致的、更加上層的組件庫
- 相當(dāng)于引入了一套框架,具有一定的學(xué)習(xí)成本和使用成本
### 優(yōu)化
- Tailwind 2.0 支持 [JIT](https://blog.tailwindcss.com/tailwindcss-2-1 "JIT"),可以大大提升編譯速度,可以考慮引入
- 基于 TailwindCSS,設(shè)計(jì)一套符合自身風(fēng)格的上層組件庫、物料庫,便于更加快速開發(fā)
- 提前探索、學(xué)習(xí)和總結(jié)一套教程與開發(fā)最佳實(shí)踐
- 探索 styled-components 等結(jié)合 TailwindCSS 的開發(fā)方式
## 參考鏈接
- [CSS 工程化發(fā)展歷程](https://bytedance.feishu.cn/docs/doccnTRF0OZtJMgKuo3y0hIDMbc# "CSS 工程化發(fā)展歷程")
以上便是本次分享的全部內(nèi)容,希望對(duì)你有所幫助^_^
喜歡的話別忘了 分享、點(diǎn)贊、收藏 三連哦~
歡迎關(guān)注公眾號(hào) 程序員巴士,來自字節(jié)、蝦皮、招銀的三端兄弟,分享編程經(jīng)驗(yàn)、技術(shù)干貨與職業(yè)規(guī)劃,助你少走彎路進(jìn)大廠。
們每天都在網(wǎng)上摸魚,作為前端開發(fā)人員,網(wǎng)站上微妙的細(xì)節(jié)變化通過比別人會(huì)更關(guān)注。我一直注意到的一件事是網(wǎng)站上的動(dòng)畫的流暢性。動(dòng)畫對(duì)于用戶體驗(yàn)來說是非常好的,有時(shí)我們可以一些有趣的動(dòng)畫來留住用戶。
創(chuàng)建高級(jí)動(dòng)畫聽起來是一個(gè)很難的話題,但好消息是,在CSS中,可以將多個(gè)簡單的動(dòng)畫相互疊加,以創(chuàng)建一個(gè)更復(fù)雜的動(dòng)畫
在這節(jié)課中,我們會(huì)學(xué)習(xí)如下幾點(diǎn):
CSS中的 cubic-bezier 函數(shù)是一個(gè)緩動(dòng)函數(shù),可以讓我們完全控制動(dòng)畫在時(shí)間上的表現(xiàn)。下面是官方的定義:
貝塞爾緩動(dòng)函數(shù)是一種由四個(gè)實(shí)數(shù)定義的緩和函數(shù),指定了貝塞爾曲線的兩個(gè)控制點(diǎn)P1和P2,其端點(diǎn)P0和P3分別固定在(0, 0)和(1, 1)。P1和P2的x坐標(biāo)被限制在[0, 1]范圍內(nèi)。
想象兩個(gè)點(diǎn)P0和P1,其中P0是動(dòng)畫的起點(diǎn),P1是結(jié)束點(diǎn)。現(xiàn)在想象另一個(gè)點(diǎn)在兩點(diǎn)之間線性移動(dòng),如下所示
這就是所謂的線性曲線,也是最簡單的動(dòng)畫。
如下圖所示,有三個(gè)點(diǎn)。P0、P1和P2。我們想讓動(dòng)畫從P0移動(dòng)到P2。在這種情況下,P1是一個(gè)控制點(diǎn),控制動(dòng)畫的曲線。
二次方貝塞爾概念:
請(qǐng)注意,Q0、Q1和B不以相同的速度移動(dòng)。它們都必須在同一時(shí)間開始,并在同一時(shí)間完成它們的路徑。因此,每一個(gè)點(diǎn)都是根據(jù)它所移動(dòng)的線長以適當(dāng)?shù)乃俣纫苿?dòng)的。
三次貝塞爾曲線由4個(gè)點(diǎn)組成。P0, P1, P2和P3。動(dòng)畫開始于P0,結(jié)束于P3。P1和P2是我們的控制點(diǎn)。
三次貝賽爾的工作原理如下:
如果你想更好地了解三次體貝塞爾的工作原理,建議你看看這個(gè)desmos鏈接。玩玩控制點(diǎn),看看動(dòng)畫如何隨時(shí)間變化。(注意,鏈接中的動(dòng)畫是由黑線表示的)。
有很多步驟的大動(dòng)畫可以被分解成多個(gè)小動(dòng)畫。在 css 中,通過添加animation-delay屬性來實(shí)現(xiàn)這一點(diǎn)。計(jì)算延遲很簡單,把你要計(jì)算動(dòng)畫延遲的那個(gè)動(dòng)畫之前的所有動(dòng)畫的時(shí)間加起來。
例如:
animation: movePointLeft 4s linear forwards, movePointDown 3s linear forwards;
這里,我們有兩個(gè)動(dòng)畫,movePointLeft和movePointDown。movePointLeft的動(dòng)畫延遲是零,因?yàn)樗俏覀兿胂冗\(yùn)行的動(dòng)畫。movePointDown的動(dòng)畫延遲是4秒,因?yàn)?span style="color: #EF7060; --tt-darkmode-color: #EF7060;">movePointLeft將在這段時(shí)間后完成。
因此,animation-delay屬性:
animation-delay: 0s, 4s;
注意,如果有兩個(gè)或更多的動(dòng)畫同時(shí)開始,它們的動(dòng)畫延遲將是一樣的。此外,當(dāng)你計(jì)算即將開始的動(dòng)畫的延遲時(shí),把它們視為一個(gè)動(dòng)。例如 :
animation: x 4s linear forwards, y 4s linear forwards, jump 2s linear forwards;
假設(shè)x和y同時(shí)開始。在這種情況下,x和y的動(dòng)畫延遲都將為零,而 jump 動(dòng)畫的延遲將為4秒(而不是8秒!)。
animation-delay: 0s, 0s, 4s;
掌握了上面的知識(shí),是時(shí)候應(yīng)用一下了。
過山車路徑由三部分組成:
我們將首先創(chuàng)建一個(gè)簡單的球,作為我們過山車的 "車"。
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;
}
創(chuàng)建小球滑動(dòng)的部分可以用cubic-bezier函數(shù)來完成! 這個(gè)動(dòng)畫是由2個(gè)動(dòng)畫組成的,一個(gè)是沿x軸的動(dòng)畫,另一個(gè)是沿y軸的動(dòng)畫。X軸動(dòng)畫是一個(gè)沿X軸的普通線性動(dòng)畫。它的關(guān)鍵幀如下:
@keyframes x {
to {
left: 40vw;
}
將其添加到球路徑的 animation 屬性中,如下所示
animation: x 4s linear forwards
y軸動(dòng)畫是我們將使用cubic-bezier函數(shù)的部分。首先定義動(dòng)畫的關(guān)鍵幀。我們希望起始點(diǎn)和結(jié)束點(diǎn)之間的差異很小,以至于球達(dá)到的高度幾乎相同。
@keyframes y {
to {
top: 29.99vh;
}
}}
現(xiàn)在讓我們來思考一下cubic-bezier函數(shù)。我們希望我們的路徑先向右緩慢移動(dòng),然后當(dāng)它滑動(dòng)時(shí),它應(yīng)該走得更快。
現(xiàn)在,我們得到了一個(gè)cubic-bezier函數(shù):
cubic-bezier(0.55, 0, 0.2, -800).
為動(dòng)畫屬性添加關(guān)鍵幀:
animation: x 4s linear forwards,
y 4s cubic-bezier(0.55, 0, 0.2, -5000) forwards;
這是我們動(dòng)畫的第一部分,所以動(dòng)畫延遲為零。我們應(yīng)該添加一個(gè)animation-delay屬性,因?yàn)閺南旅娴膭?dòng)畫開始,動(dòng)畫的開始時(shí)間將與第一個(gè)動(dòng)畫不同。
animation-delay: 0s, 0s;
地址:https://codepen.io/smashingmag/pen/VwxXBQb
在做循環(huán)之前,球應(yīng)該沿著X軸移動(dòng)一小會(huì)兒,所以兩個(gè)動(dòng)畫之間有空間。
定義關(guān)鍵幀
@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;
這個(gè)動(dòng)畫應(yīng)該在滑動(dòng)動(dòng)畫之后開始,而滑動(dòng)動(dòng)畫需要4秒,因此,動(dòng)畫延遲將是4秒。
animation-delay: 0s, 0s, 4s;
地址:https://codepen.io/smashingmag/pen/dyemExY
要在CSS中創(chuàng)建一個(gè)圓(循環(huán)),我們需要把圓移到循環(huán)的中心,然后從那里開始做動(dòng)畫。圓的半徑是100px,所以我們把圓的位置改為top: 20vh(30是期望的半徑(這里是10vh))。然而,這需要在滑動(dòng)動(dòng)畫完成后發(fā)生,所以我們將創(chuàng)建另一個(gè)持續(xù)時(shí)間為0秒的動(dòng)畫,并添加一個(gè)合適的動(dòng)畫延遲。
關(guān)鍵幀:
@keyframes pointOfCircle {
to {
top: 20vh;
}
}
添加到 animation 動(dòng)畫中:
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;
添加動(dòng)畫延遲, 4.5s:
animation-delay: 0s, 0s, 4s, 4.5s;
創(chuàng)建一個(gè)循環(huán)動(dòng)畫:
@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;
添加動(dòng)畫延遲,這里是4.5s:
animation-delay: 0s, 0s, 4s, 4.5s, 4.5s;
地址:https://codepen.io/smashingmag/pen/mdLxZdR
快完成了,最后 只需要在動(dòng)畫之后沿著x軸移動(dòng)球,這樣球就不會(huì)像上圖中那樣在循環(huán)之后完全停止。
關(guān)鍵幀:
@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;
加上適當(dāng)?shù)难舆t,這里是7.5s:
animation-delay: 0s, 0s, 4s, 4.5s, 4.5s, 7.5s;
地址:https://codepen.io/smashingmag/pen/wvjmLKp
在本節(jié)中,我們介紹了如何結(jié)合多個(gè)關(guān)鍵幀來創(chuàng)建一個(gè)復(fù)雜的動(dòng)畫路徑。我們還介紹了貝塞爾以及如何使用它們來創(chuàng)建你自己的緩動(dòng)函數(shù)。建議大家自己多多動(dòng)手,才能更好的掌握 css 動(dòng)畫。
來源:https://www.smashingmagazine.com/2022/10/advanced-animations-css/
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。