[TOC]
# 什么是貝塞爾曲線
貝塞爾曲線于 1962 年,由法國工程師皮埃爾·貝濟埃(Pierre Bézier)所廣泛發表,他運用貝塞爾曲線來為汽車的主體進行設計。
<br>
貝塞爾曲線主要用于二維圖形應用程序中的數學曲線,曲線由起始點,終止點(也稱錨點)和控制點組成,通過調整控制點,通過一定方式繪制的貝塞爾曲線形狀會發生變化。后面會具體介紹繪制的方法。
<br>
在計算機圖形學中貝賽爾曲線的運用很廣泛,例如Photoshop中的鋼筆效果,Flash5的貝塞爾曲線工具,在軟件GUI開發中一般也會提供對應的方法來實現貝賽爾曲線,我們熟知的CSS動畫過渡時間函數也是通過貝塞爾曲線(三階貝塞爾曲線)獲取的。
<br>
<br>
# 貝塞爾曲線分為哪些類型?
貝塞爾曲線根據**控制點**的數量分為:
* 一階貝塞爾曲線(2 個控制點)
* 二階貝塞爾曲線(3 個控制點)
* 三階貝塞爾曲線(4 個控制點)
* n階貝塞爾曲線(n+1 個控制點)
<br>
<br>
# 貝塞爾曲線是如何繪制出來的
下面以二街貝塞爾曲線為例。
在平面內任選 3 個不共線的點,依次用線段連接

<br>
在第一條線段上任選一個點 D。計算該點到線段起點的距離 AD,與該線段總長 AB 的比例。

<br>
根據上一步得到的比例,從第二條線段上找出對應的點 E,使得`AD:AB = BE:BC`。

<br>
連接這兩點 DE。

<br>
從新的線段 DE 上再次找出相同比例的點 F,使得`DF:DE = AD:AB = BE:BC`。

<br>
到這里,我們就確定了貝塞爾曲線上的一個點 F。接下來,請稍微回想一下中學所學的極限知識,讓選取的點 D 在第一條線段上從起點 A 移動到終點 B,找出所有的貝塞爾曲線上的點 F。所有的點找出來之后,我們也得到了這條貝塞爾曲線。

<br>
繪制過程如圖

<br>
當控制點個數為 4 時,步驟都是相同的,只不過我們每確定一個貝塞爾曲線上的點,要進行三輪取點操作。如圖,`AE:AB = BF:BC = CG:CD = EH:EF = FI:FG = HJ:HI`,其中點 J 就是最終得到的貝塞爾曲線上的一個點。

<br>
這樣我們得到的是一條三次貝塞爾曲線。

<br>
<br>
# 如何求貝塞爾曲線上的點坐標?
## 一階貝塞爾曲線

對于一階貝塞爾曲線,我們可以通過幾何知識,很容易根據 t 的值得出線段上那個點的坐標:

<br>
然后可以得出:

<br>
## 二階貝塞爾曲線

對于二階貝塞爾曲線,其實你可以理解為:在`P0P1`上利用一階公式求出點`P0'`,然后在`P1P2`上利用一階公式求出點`P1'`,最后在`P0'P1'`上再利用一階公式就可以求出最終貝塞爾曲線上的點`P0''`。具體推導過程如下:
> 先求出線段上的控制點。


<br>
> 將上面的公式帶入至下列公式中:



<br>
> 得出以下公式:

<br>
## 三階貝塞爾曲線

將 `P0P1P2 `與 `P1P2P3` 看成是2個2階段貝塞爾曲線,可根據上面分別求得坐標點公式:


<br>
將 p0''與p1''連接,根據之前的比例找到點P0''',根據一階貝塞爾曲線,可得到公式:

<br>
分別將P0''和P1''代入以上公式,可得:

<br>
## 多階貝塞爾曲線

即:

<br>
<br>
# 動態繪制貝塞爾曲線
詳細見 [CodePen](https://codepen.io/surahe/pen/VNoOpd)
~~~
/**
* 遍歷 points,畫出對應的點并用線連起來
* @param {Array} points
*/
function drawCubicBezierCurzeHelper(points) {
ctx.save()
// 遍歷 points 的坐標,畫線
points.reduce((p, c) => {
ctx.strokeStyle = "gray"
drawLine(...p, ...c)
return c
})
// 遍歷 points 的坐標,畫紅點
points.forEach((c) => {
ctx.beginPath()
ctx.fillStyle = 'red';
ctx.arc(...c, 20, 0, 2 * Math.PI);
ctx.fill()
})
ctx.restore()
}
/**
* 逐個畫出貝塞爾曲線上的點
*/
function animation2() {
if (t >= 1) {
// 初始點動畫執行完畢標志
isEnded = true;
return
}
// 獲取當前 t 的點坐標
let initPoint2 = drawPoints(points)
ctx.strokeStyle = "blue"
// 以初始點和當前點畫線
drawLine(...initPoint, ...initPoint2)
// 將當前點設置為初始點
initPoint = initPoint2
// 應該是使用緩存優化性能
cache = canvas.toDataURL("image/jpeg", 1);
let img = new Image();
img.src = cache
img.onload = function () {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
// 遞歸執行
window.requestAnimationFrame(() => {
t += 0.1
animation2()
})
}
}
~~~
<br>
<br>
# SVG 與 貝塞爾曲線
path的標簽,可以繪制任意的路徑,包括貝塞爾曲線。
<br>
三次貝塞爾曲線指令:`C x1 y1, x2 y2, x y`兩個控制點`(x1,y1)`和(`x2,y2)`,`(x,y)`代表曲線的終點。字母`C`表示特定動作與行為,這里需要大寫,表示標準三次方貝塞爾曲線。
<br>
下面一些描述貝塞爾曲線的代碼(片段),其中字母M表示特定動作moveTo, 指將繪圖的起點移動到此處。
~~~
<svg width="190px" height="160px">
<path d="M10 10 C 20 20, 40 20, 50 10" stroke="3" fill="none"/>
<path d="M70 10 C 70 20, 120 20, 120 10" stroke="3" fill="none"/>
<path d="M130 10 C 120 20, 180 20, 170 10" stroke="3" fill="none"/>
<path d="M10 60 C 20 80, 40 80, 50 60" stroke="3" fill="none"/>
<path d="M70 60 C 70 80, 110 80, 110 60" stroke="3" fill="none"/>
<path d="M130 60 C 120 80, 180 80, 170 60" stroke="3" fill="none"/>
<path d="M10 110 C 20 140, 40 140, 50 110" stroke="3" fill="none"/>
<path d="M70 110 C 70 140, 110 140, 110 110" stroke="3" fill="none"/>
<path d="M130 110 C 120 140, 180 140, 170 110" stroke="3" fill="none"/>
</svg>
~~~
<br>
曲線效果類似下面這張圖:

可以看到`M`后面的起點,加`C`后面3個點,構成了貝賽爾曲線的`4`個點。
<br>
<br>
# Canvas 與貝塞爾曲線
## 二次方貝塞爾曲線
`CanvasRenderingContext2D.quadraticCurveTo()` 是 Canvas 2D API 新增二次貝塞爾曲線路徑的方法。它需要2個點。 第一個點是控制點,第二個點是終點。 起始點是當前路徑最新的點,當創建二次貝賽爾曲線之前,可以使用 `moveTo()` 方法進行改變。
<br>
語法
~~~
ctx.quadraticCurveTo(cpx, cpy, x, y);
~~~
參數
* `cpx`:控制點的 x 軸坐標。
* `cpy`:控制點的 y?軸坐標。
* `x`:終點的 x 軸坐標。
* `y`:終點的 y?軸坐標。
~~~js
// 二次貝塞爾曲線
ctx.beginPath();
ctx.moveTo(50, 20);
ctx.quadraticCurveTo(230, 30, 50, 100);
ctx.stroke();
// 畫起點和終點
...
// 畫控制點
...
~~~

<br>
## 三次貝塞爾曲線
`CanvasRenderingContext2D.bezierCurveTo()` 是 Canvas 2D API 繪制三次貝賽爾曲線路徑的方法。 該方法需要三個點。 第一、第二個點是控制點,第三個點是結束點。起始點是當前路徑的最后一個點,繪制貝賽爾曲線前,可以通過調用 `moveTo()` 進行修改。
<br>
語法
~~~
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
~~~
參數
* `cp1x`:第一個控制點的 x 軸坐標。
* `cp1y`:第一個控制點的 y?軸坐標。
* `cp2x`:第二個控制點的 x 軸坐標。
* `cp2y`:第二個控制點的 y?軸坐標。
* `x`:結束點的 x 軸坐標。
* `y`:結束點的 y?軸坐標。
~~~js
// 定義點 {x, y}
let start = { x: 50, y: 20 };
let cp1 = { x: 230, y: 30 };
let cp2 = { x: 150, y: 80 };
let end = { x: 250, y: 100 };
// 畫三次貝塞爾曲線
ctx.beginPath();
ctx.moveTo(start.x, start.y);
ctx.bezierCurveTo(cp1.x, cp1.y, cp2.x, cp2.y, end.x, end.y);
ctx.stroke();
// 畫起點和終點
...
// 畫控制點
...
~~~

<br>
<br>
# CSS3動畫與貝塞爾曲線
## timing-function
` timing-function` CSS 數據類型表示一個數學函數,它描述了在一個過渡或動畫中一維數值的改變速度。這實質上讓你可以自己定義一個加速度曲線,以便動畫的速度在動畫的過程中可以進行改變。這些函數通常被稱為緩動函數。
<br>
這是一個表示時間輸出比率的函數,表示為`<number>`,0, 0 代表初始狀態,1, 1 代表終止狀態。
<br>
輸出比可以大于1.0(或者小于0.0)。這是因為在一種反彈效果中,動畫是可以比最后的狀態走的更遠的,然后再回到最終狀態。


<br>
不過,如果輸出值超過了它允許的范圍,比如組成一個顏色的值大于了255或者小于了0,這個值會被修改為允許范圍內的最接近的值(在顏色值這個例子中分別為255和0)。一些貝塞爾曲線展示了這些性質。
<br>
## cubic-bezier屬性
cubic-bezier() 定義了一條 立方貝塞爾曲線(cubic Bézier curve)。這些曲線是連續的,一般用于動畫的平滑變換,也被稱為緩動函數(easing functions)。
<br>
一條立方貝塞爾曲線需要四個點來定義,P0 、P1 、P2 和 P3。P0 和 P3 是起點和終點,這兩個點被作為比例固定在坐標系上,橫軸為時間比例,縱軸為完成狀態。P0 是 (0, 0),表示初始時間和初始狀態。P3 是 (1, 1) ,表示終止時間和終止狀態。
<br>
并非所有的三次貝塞爾曲線都適合作為計時函數,也并非所有的曲線都是數學函數,即給定橫坐標為0或1的曲線。在CSS定義的P0和P3固定的情況下,三次貝塞爾曲線是一個函數,因此,**當且僅當P1和P2的橫坐標都在[0,1]范圍內**時,三次貝塞爾曲線是有效的。
<br>
在[0,1]范圍之外的P1或P2縱坐標的立方貝塞爾曲線可能會產生彈跳效應。
<br>
當您指定無效的cubic-bezier曲線時,CSS會忽略整個屬性。
### 語法
~~~
cubic-bezier(x1, y1, x2, y2)
~~~
***x1*,*y1*,*x2*,*y2***
`<number>`值表示橫坐標,P1和P2點的縱坐標表示三次貝塞爾曲線。 x1和x2必須在[0,1]范圍內,否則該值無效。
### 實例
~~~
cubic-bezier(0.1, 0.7, 1.0, 0.1) The canonical Bézier curve with four <number> in the [0,1] range.
cubic-bezier(0, 0, 1, 1) Using <integer> is valid as any <integer> is also a <number>.
cubic-bezier(-0.2, 0.6, -0.1, 0) Negative values for abscissas are valid, leading to bouncing effects.
cubic-bezier(1.1, 0, 4, 0) Values > 1.0 for abscissas are also valid.
~~~
<br>
## 拋物線運動
將拋物線運動分解為水平和垂直方向。
拋物線運動元素使用至少內外兩層標簽,例如,本demo拋物線運動物體是CSS世界這本書的縮略圖,我們可以外面一層`<div>`,里面是`<img>`圖片:
~~~
<div class="fly-item">
<img src="./book.jpg">
</div>
~~~
然后內外兩次標簽一個負責水平方向的translate移動,一個負責垂直方向的translate移動,然后使用不同的緩動函數,也就是使用不同的`timing-function`,在CSS3`animation`動畫效果中是`animation-timing-function`屬性,在CSS3`transition`過渡效果中是`transition-timing-function`屬性。CSS代碼如下:
~~~
.fly-item {
/* 水平移動,線性勻速 */
transition-timing-function: linear;
}
.fly-item > img {
/* 垂直移動,先慢后快 */
transition-timing-function: cubic-bezier(.55,0,.85,.36);
}
~~~
然后同時執行`translate`移動,拋物線效果就出現了。
<br>
<br>
# 工具
http://cubic-bezier.com/

x軸表示隨著時間勻速前進,y軸表示隨著時間推移運動距離的百分比,x y的最大值都為 1
<br>
<br>
# 參考資料
[貝塞爾曲線與CSS3動畫、SVG和canvas的基情](https://www.zhangxinxu.com/wordpress/2013/08/%E8%B4%9D%E5%A1%9E%E5%B0%94%E6%9B%B2%E7%BA%BF-cubic-bezier-css3%E5%8A%A8%E7%94%BB-svg-canvas/)
[貝塞爾曲線掃盲](http://www.html-js.com/article/1628)
[【干貨滿滿】貝塞爾曲線(Bézier curve)——什么神仙操作](https://juejin.im/post/5be99bf66fb9a049db72a956)
[這回試試使用CSS實現拋物線運動效果](https://www.zhangxinxu.com/wordpress/2018/08/css-css3-%E6%8A%9B%E7%89%A9%E7%BA%BF%E5%8A%A8%E7%94%BB/)
[深入理解貝塞爾曲線](https://juejin.im/post/5b854e1451882542fe28a53d)
[MDN - timing function](https://developer.mozilla.org/zh-CN/docs/Web/CSS/timing-function)
[MDN - quadratic?CurveTo](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/quadraticCurveTo)
[MDN - bezierCurveTo](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/bezierCurveTo)
- 第一部分 HTML
- meta
- meta標簽
- HTML5
- 2.1 語義
- 2.2 通信
- 2.3 離線&存儲
- 2.4 多媒體
- 2.5 3D,圖像&效果
- 2.6 性能&集成
- 2.7 設備訪問
- SEO
- Canvas
- 壓縮圖片
- 制作圓角矩形
- 全局屬性
- 第二部分 CSS
- CSS原理
- 層疊上下文(stacking context)
- 外邊距合并
- 塊狀格式化上下文(BFC)
- 盒模型
- important
- 樣式繼承
- 層疊
- 屬性值處理流程
- 分辨率
- 視口
- CSS API
- grid(未完成)
- flex
- 選擇器
- 3D
- Matrix
- AT規則
- line-height 和 vertical-align
- CSS技術
- 居中
- 響應式布局
- 兼容性
- 移動端適配方案
- CSS應用
- CSS Modules(未完成)
- 分層
- 面向對象CSS(未完成)
- 布局
- 三列布局
- 單列等寬,其他多列自適應均勻
- 多列等高
- 圣杯布局
- 雙飛翼布局
- 瀑布流
- 1px問題
- 適配iPhoneX
- 橫屏適配
- 圖片模糊問題
- stylelint
- 第三部分 JavaScript
- JavaScript原理
- 內存空間
- 作用域
- 執行上下文棧
- 變量對象
- 作用域鏈
- this
- 類型轉換
- 閉包(未完成)
- 原型、面向對象
- class和extend
- 繼承
- new
- DOM
- Event Loop
- 垃圾回收機制
- 內存泄漏
- 數值存儲
- 連等賦值
- 基本類型
- 堆棧溢出
- JavaScriptAPI
- document.referrer
- Promise(未完成)
- Object.create
- 遍歷對象屬性
- 寬度、高度
- performance
- 位運算
- tostring( ) 與 valueOf( )方法
- JavaScript技術
- 錯誤
- 異常處理
- 存儲
- Cookie與Session
- ES6(未完成)
- Babel轉碼
- let和const命令
- 變量的解構賦值
- 字符串的擴展
- 正則的擴展
- 數值的擴展
- 數組的擴展
- 函數的擴展
- 對象的擴展
- Symbol
- Set 和 Map 數據結構
- proxy
- Reflect
- module
- AJAX
- ES5
- 嚴格模式
- JSON
- 數組方法
- 對象方法
- 函數方法
- 服務端推送(未完成)
- JavaScript應用
- 復雜判斷
- 3D 全景圖
- 重載
- 上傳(未完成)
- 上傳方式
- 文件格式
- 渲染大量數據
- 圖片裁剪
- 斐波那契數列
- 編碼
- 數組去重
- 淺拷貝、深拷貝
- instanceof
- 模擬 new
- 防抖
- 節流
- 數組扁平化
- sleep函數
- 模擬bind
- 柯里化
- 零碎知識點
- 第四部分 進階
- 計算機原理
- 數據結構(未完成)
- 算法(未完成)
- 排序算法
- 冒泡排序
- 選擇排序
- 插入排序
- 快速排序
- 搜索算法
- 動態規劃
- 二叉樹
- 瀏覽器
- 瀏覽器結構
- 瀏覽器工作原理
- HTML解析
- CSS解析
- 渲染樹構建
- 布局(Layout)
- 渲染
- 瀏覽器輸入 URL 后發生了什么
- 跨域
- 緩存機制
- reflow(回流)和repaint(重繪)
- 渲染層合并
- 編譯(未完成)
- Babel
- 設計模式(未完成)
- 函數式編程(未完成)
- 正則表達式(未完成)
- 性能
- 性能分析
- 性能指標
- 首屏加載
- 優化
- 瀏覽器層面
- HTTP層面
- 代碼層面
- 構建層面
- 移動端首屏優化
- 服務器層面
- bigpipe
- 構建工具
- Gulp
- webpack
- Webpack概念
- Webpack工具
- Webpack優化
- Webpack原理
- 實現loader
- 實現plugin
- tapable
- Webpack打包后代碼
- rollup.js
- parcel
- 模塊化
- ESM
- 安全
- XSS
- CSRF
- 點擊劫持
- 中間人攻擊
- 密碼存儲
- 測試(未完成)
- 單元測試
- E2E測試
- 框架測試
- 樣式回歸測試
- 異步測試
- 自動化測試
- PWA
- PWA官網
- web app manifest
- service worker
- app install banners
- 調試PWA
- PWA教程
- 框架
- MVVM原理
- Vue
- Vue 餓了么整理
- 樣式
- 技巧
- Vue音樂播放器
- Vue源碼
- Virtual Dom
- computed原理
- 數組綁定原理
- 雙向綁定
- nextTick
- keep-alive
- 導航守衛
- 組件通信
- React
- Diff 算法
- Fiber 原理
- batchUpdate
- React 生命周期
- Redux
- 動畫(未完成)
- 異常監控、收集(未完成)
- 數據采集
- Sentry
- 貝塞爾曲線
- 視頻
- 服務端渲染
- 服務端渲染的利與弊
- Vue SSR
- React SSR
- 客戶端
- 離線包
- 第五部分 網絡
- 五層協議
- TCP
- UDP
- HTTP
- 方法
- 首部
- 狀態碼
- 持久連接
- TLS
- content-type
- Redirect
- CSP
- 請求流程
- HTTP/2 及 HTTP/3
- CDN
- DNS
- HTTPDNS
- 第六部分 服務端
- Linux
- Linux命令
- 權限
- XAMPP
- Node.js
- 安裝
- Node模塊化
- 設置環境變量
- Node的event loop
- 進程
- 全局對象
- 異步IO與事件驅動
- 文件系統
- Node錯誤處理
- koa
- koa-compose
- koa-router
- Nginx
- Nginx配置文件
- 代理服務
- 負載均衡
- 獲取用戶IP
- 解決跨域
- 適配PC與移動環境
- 簡單的訪問限制
- 頁面內容修改
- 圖片處理
- 合并請求
- PM2
- MongoDB
- MySQL
- 常用MySql命令
- 自動化(未完成)
- docker
- 創建CLI
- 持續集成
- 持續交付
- 持續部署
- Jenkins
- 部署與發布
- 遠程登錄服務器
- 增強服務器安全等級
- 搭建 Nodejs 生產環境
- 配置 Nginx 實現反向代理
- 管理域名解析
- 配置 PM2 一鍵部署
- 發布上線
- 部署HTTPS
- Node 應用
- 爬蟲(未完成)
- 例子
- 反爬蟲
- 中間件
- body-parser
- connect-redis
- cookie-parser
- cors
- csurf
- express-session
- helmet
- ioredis
- log4js(未完成)
- uuid
- errorhandler
- nodeclub源碼
- app.js
- config.js
- 消息隊列
- RPC
- 性能優化
- 第七部分 總結
- Web服務器
- 目錄結構
- 依賴
- 功能
- 代碼片段
- 整理
- 知識清單、博客
- 項目、組件、庫
- Node代碼
- 面試必考
- 91算法
- 第八部分 工作代碼總結
- 樣式代碼
- 框架代碼
- 組件代碼
- 功能代碼
- 通用代碼