js基于div絲滑實(shí)現(xiàn)貝塞爾曲線
引言
今天遇到朋友發(fā)來的一個(gè)ui圖,詢問我如何實(shí)現(xiàn)下圖這樣的效果【vue項(xiàng)目】,(聽說是類似LED燈的展示效果),于是便有了今天的小demo,要實(shí)現(xiàn)一個(gè)類似下圖的動(dòng)效,上面的燈會(huì)一直重復(fù)滾動(dòng),但是這個(gè)并不是什么難點(diǎn),主要在于如何實(shí)現(xiàn)這種平滑的曲線,日常我們的開發(fā)的div在我們的腦海中通常就是一個(gè)網(wǎng)格狀,涉及到平滑曲線的往往是圖表,于是我們需要找一個(gè)方案來完成這種布局(非真實(shí)ui圖,是完成之后的效果)

分析
我們需要先簡(jiǎn)單分析一下這個(gè)ui,當(dāng)我們拿到這個(gè)UI圖的時(shí)候,腦海中的第一反應(yīng)是,一個(gè)大的DIV中間套了很多的小的DIV,并且小的的上下位置出現(xiàn)了偏移,但是偏移多少目前我們不得而知,但是基礎(chǔ)的布局方案已經(jīng)完成。
第二步我們考慮球體的顏色,可以看到,軌道是一種顏色,需要一直移動(dòng)的球體是另一種顏色,這個(gè)非常簡(jiǎn)單,我們定義兩組數(shù)據(jù),一組是軌道,一組是高亮的球,通過不段改變高亮的這組數(shù)據(jù),即可響應(yīng)式的完成燈的移動(dòng),第二點(diǎn)我們也解決了
第三點(diǎn),初始的時(shí)候考慮的是y的坐標(biāo)是0, 2, 4, 6, 8, 10 , 8, 6, 4 , 2 ..... ,但是很顯然,這樣的坐標(biāo)出來的形狀一定是一個(gè)折線圖,而不是平滑的曲線,于是我們需要用到數(shù)學(xué)知識(shí)了:需要使用到圓的弧度的概念,在javascript中有兩個(gè)方法**Math.sin()和Math.cos()**都是關(guān)于弧度的公式,關(guān)于這兩個(gè)方法,我們下面再說。
實(shí)現(xiàn)
布局
實(shí)現(xiàn)這個(gè)的布局非常簡(jiǎn)單,外層一個(gè)大的div,內(nèi)層很多小的span,通過flex一排即可到一排
<template>
<div class="container">
<div class="content">
<span class="circle" v-for="(item,index) in list" :key="index"></span>
</div>
</div>
</template>
如何計(jì)算y的偏移量
這一步是我們比較重要的一步,我們有一個(gè)400px的容器,容器中放置了20個(gè)球span,現(xiàn)在他們?cè)谝慌?,我們只需要給他動(dòng)態(tài)綁定樣式**transform: translateY(?px)**即可,重要的是我們?nèi)绾斡?jì)算這個(gè)的坐標(biāo),我們先來了解下兩個(gè)方法的用處:
Math.sin() 和 Math.cos()
Math.sin(x) x 的正玄值。返回值在 -1.0 到 1.0 之間;
Math.cos(x) x 的余弦值。返回的是 -1.0 到 1.0 之間;
可以看到其分別是x點(diǎn)的正弦,這兩個(gè)函數(shù)中的x指的是弧度而不是角度,弧度的計(jì)算公式是:2π/360°
這里涉及到數(shù)學(xué)知識(shí),我們先看看這張圖

我們看我們關(guān)注的sin和cos
sin(∠A) = 對(duì)邊比斜邊(a / c)
cos(∠A) = 臨邊比斜邊 (b / c)
可大致了解一下即可,當(dāng)然,我們今天所需要使用的和這個(gè)關(guān)系不大,這里只是幫大家回顧一下高中知識(shí)(手動(dòng)狗頭)。
好了這里直接推薦一個(gè)在線網(wǎng)站,圖形計(jì)算器可以直接在線調(diào)試各種曲線
我們看看基礎(chǔ)的正弦余弦曲線
正弦曲線

余弦曲線

我們知道圓周率(π), 1π=180°,2π=360°,就是一周,所以我們只需要截圖(0-2π)一個(gè)周期的曲線即可,后續(xù)不管要什么曲線,都在這個(gè)上面進(jìn)行變換即可,通過上面對(duì)比,發(fā)現(xiàn)正弦曲線的起始點(diǎn)是(0,0),比余弦的(0,1)更好計(jì)算,我們就直接用正弦吧,那么我們列出已知條件:
- 在曲線中 y = cos(x)
- 在曲線中,曲線的寬度是2π
- 在曲線中,曲線的高度最高點(diǎn)到最低點(diǎn)是2
- 在我們的需求中,總寬度是400px
- 在我們需求中, 共有二十個(gè)圓圈,所以我們可以算出每個(gè)球的寬度平均是20px,所以坐標(biāo)就是
(index+1)*20 - 現(xiàn)在我們知道了很多信息,我們就可以計(jì)算出更多信息了
計(jì)算更多信息
我們知道曲線的寬度和我們的物理實(shí)際寬度就可以得出寬度比: 400 / 2π
這個(gè)時(shí)候我們需要通過這個(gè)比例計(jì)算出物理的x坐標(biāo)對(duì)應(yīng)的曲線中的x坐標(biāo),那么 物理寬度/x坐標(biāo) = 2π/曲線中x坐標(biāo)
/* 400 / x = 2π / y, 我們的x是已知的,等下自己可以拿,這樣拿到了曲線中實(shí)際的x坐標(biāo) */ const z = 400 x / 400 * Math.PI*2
有個(gè)曲線中的對(duì)應(yīng)x坐標(biāo),通過公式我們就可以拿到其曲線中實(shí)際y坐標(biāo)了
/* 這樣就拿到了曲線中的y坐標(biāo) */ y = Math.sin(z)
拿到了曲線中的y坐標(biāo),那么們又知道,曲線中的總高是2,通過xy的坐標(biāo)對(duì)比,我們可以計(jì)算出我們所需的真實(shí)的y
/* 真實(shí)寬度400/曲線寬度2π = 真實(shí)高度y/曲線中的y 通過對(duì)比得到真實(shí)的y點(diǎn) */ Y = Math.sin(z) * 400 / Math.PI * 2 / 2
然后通過這樣的一個(gè)計(jì)算公式把這個(gè)y值賦值給我們的y點(diǎn)就可以得到這樣的曲線

完善剩余
看起來有點(diǎn)意思了,這就是一個(gè)完整的2π,或者我們理解為就是曲線的一個(gè)周期,但是很明顯曲線的度數(shù)不對(duì),我們?nèi)绾握{(diào)整呢,回到剛剛的那個(gè)網(wǎng)站之中,我們要想曲線更加平滑,只需要對(duì)sin()除以/x即可,x最大線越平,我們到剛剛的網(wǎng)站去自己調(diào)試到自己理想的高度,

我們調(diào)試發(fā)現(xiàn)除以4就得到了差不多我們想要的曲線,所以我們只需要在上面的基礎(chǔ)上/4就得到了我們真正想要的y。

此時(shí)我們的曲線就已經(jīng)完成了,所以其實(shí)是不是就是我們的高中數(shù)學(xué)知識(shí)呢
完成跑馬燈制作
前面的曲線畫完,后面就已經(jīng)不難了,我們只需要定義一段高亮的下標(biāo)數(shù)組,我們寫一個(gè)方法,創(chuàng)建一個(gè)自己想要高亮幾個(gè)就生成0-x的數(shù)組
createActiveIndex(len = 6){
return Array.from({length:len}, (v,k) => k)
},
然后在給span動(dòng)態(tài)綁定一個(gè)背景顏色。當(dāng)index屬于高亮的時(shí)候就給高亮的顏色,不是則反之,然后我們寫一個(gè)定時(shí)器一直修改這個(gè)高亮的數(shù)組即可,每次讓其里面所有元素加1,就可以讓他一直跑下去了,當(dāng)然邊界的時(shí)候我們需要對(duì)他進(jìn)行歸0
changeIndex(){
this.activeIndex = this.activeIndex.map( item => item === this.list.length - 1 ? 0 : item + 1)
},
最后我們啟動(dòng)即可,就實(shí)現(xiàn)了我們開頭想要的效果。

至此這個(gè)需求算是完成了,這只是一個(gè)小的場(chǎng)景通過這樣的方式我們可以繪制出更多好玩的東西,你可以改變各種參數(shù)對(duì)齊進(jìn)行調(diào)整修改,看看是不是你想要的效果
貝塞爾曲線
我們知道,前端的動(dòng)畫經(jīng)常出現(xiàn)一個(gè)名詞貝塞爾曲線,就是動(dòng)畫的執(zhí)行過程,我們剛剛的曲線其實(shí)就是同理,如果此時(shí)我們需要去手動(dòng)書寫一個(gè)貝塞爾曲線我們應(yīng)該怎么做呢,剛剛我們知道,我們?nèi)萜鞯目倢挾仁?00,曲線的周長(zhǎng)是2π,比例就是400/2π,同理,當(dāng)我們換算成時(shí)間的時(shí)候,假如動(dòng)畫是1秒。
那么我們需要60幀,一幀動(dòng)畫的時(shí)間就是1000/60=16.7ms,我們通過2π/60就知道我們每一幀動(dòng)畫在什么位置了,當(dāng)我們手寫貝塞爾曲線的時(shí)候,利用差不多的公式一樣可以完成。
簡(jiǎn)單封裝一下方法
看起來似乎很復(fù)雜,但是實(shí)際上我們所需要的其實(shí)只是利用真實(shí)的x點(diǎn),拿到對(duì)應(yīng)曲線求出我們y的坐標(biāo),所以我們需要的參數(shù)有,我們真實(shí)場(chǎng)景的總寬,總寬之中的個(gè)數(shù),我們所需要的曲線的倍率,三個(gè)參數(shù)即可,我們盡量分開步驟寫,這樣你看會(huì)理解的更清楚
js中π就是Math.PI
function getCoordinate(width, count, mag = 1){
/* 通過總寬和個(gè)數(shù)計(jì)算出一個(gè)單個(gè)的寬 */
const singleWidth = width / count
/* 通過物理寬度/曲線周長(zhǎng)計(jì)算出比率 */
const ratio = 400 / Math.PI*2
/* 上面實(shí)例代碼我們是動(dòng)態(tài)一次計(jì)算一個(gè),而現(xiàn)在是方法,我們應(yīng)該一次去拿到所有,所以我們返回一個(gè)數(shù)組對(duì)象記錄xy */
let result = new Array(count).fill({})
/* 遍歷總長(zhǎng)度的dom個(gè)數(shù),在數(shù)組中填充寬高 */
result = result.map( (item,index) => {
/* x的坐標(biāo) */
const x = (index + 1) * singleWidth
/* 定義變量z計(jì)算曲線中x的坐標(biāo) */
const z = x / width * Math.PI*2
/* 計(jì)算出真實(shí)的y的坐標(biāo) */
let y = Math.sin(z) / 4 * 400 / Math.PI * 2 / 2
/* y還需要通過倍率改變曲線,得到最終我們想要的y */
y = y / mag
/* 寫入數(shù)組對(duì)象中 */
return {x, y}
})
return result;
} 完整示例
style
.container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #000;
}
.content{
display: flex;
justify-content: space-between;
align-items: center;
width: 400px;
height: 50px;
}
.container .circle{
width: 15px;
height: 15px;
border-radius: 50%;
background-color: #befbf7;
}SCript
<template>
<div class="container">
<div class="content">
<span class="circle" v-for="(item,index) in list" :key="index" :style="{transform: `translateY(${calcY(index)}px)`,backgroundColor: getCurrentBgColor(index)}"></span>
</div>
<div class="content" style="margin-top: 50px">
<span class="circle" v-for="(item,index) in list" :key="index" :style="{transform: `translateY(${calcY(index)}px)`,backgroundColor: getRandomBgColor(index)}"></span>
</div>
</div>
</template>
<script>
export default {
data() {
return {
list: [], //定義總長(zhǎng)度
activeIndex: [], // 運(yùn)動(dòng)中的球的顏色
interval: 300, //運(yùn)動(dòng)速度
colors: { // 定義軌道顏色和高亮顏色
active: '#2b88ff',
basic: '#b6f3f7'
},
cache: []
};
},
methods: {
init() {
this.list = new Array(20).fill(0)
this.start()
},
getCurrentBgColor(index){
return this.activeIndex.includes(index) ? '#6e9cae' : this.getRandomColor()
},
getRandomBgColor(index){
const color = this.activeIndex.includes(index) ? 'active' : 'basic'
return this.colors[color]
},
start(){
this.activeIndex = this.createActiveIndex()
setInterval(() => this.changeIndex(), this.interval)
},
changeIndex(){
this.activeIndex = this.activeIndex.map( item => item === this.list.length - 1 ? 0 : item + 1)
},
/* 生成需要?jiǎng)拥那虻膫€(gè)數(shù) */
createActiveIndex(len = 6){
return Array.from({length:len}, (v,k) => k)
},
getRandomColor(){
return `#${Math.floor(Math.random() * 0xffffff) .toString(16)}`;
},
getCoordinate(width, count, mag = 1){
/* 通過總寬和個(gè)數(shù)計(jì)算出一個(gè)單個(gè)的寬 */
const singleWidth = width / count
/* 通過物理寬度/曲線周長(zhǎng)計(jì)算出比率 */
const ratio = 400 / Math.PI*2
/* 上面實(shí)例代碼我們是動(dòng)態(tài)一次計(jì)算一個(gè),而現(xiàn)在是方法,我們應(yīng)該一次去拿到所有,所以我們返回一個(gè)數(shù)組對(duì)象記錄xy */
let result = new Array(count).fill({})
/* 遍歷總長(zhǎng)度的dom個(gè)數(shù),在數(shù)組中填充寬高 */
result = result.map( (item,index) => {
/* x的坐標(biāo) */
const x = (index + 1) * singleWidth
/* 定義變量z計(jì)算曲線中x的坐標(biāo) */
const z = x / width * Math.PI*2
/* 計(jì)算出真實(shí)的y的坐標(biāo) */
let y = Math.sin(z) / 4 * 400 / Math.PI * 2 / 2
/* y還需要通過倍率改變曲線,得到最終我們想要的y */
y = y / mag
/* 寫入數(shù)組對(duì)象中 */
return {x, y}
})
return result;
}
},
created(){
this.cache = this.getCoordinate(400, 20, 1)
this.init()
},
computed:{
calcY(){
return (index) => {
/* 使用封裝的方法計(jì)算 */
// return this.cache[index].y
const x = (index + 1) * 20
const z = x / 400 * Math.PI*2
const y = Math.sin(z) * 400 / Math.PI * 2 / 2 / 4
return y
}
}
}
};
</script>以上就是js基于div絲滑實(shí)現(xiàn)貝塞爾曲線的詳細(xì)內(nèi)容,更多關(guān)于js div實(shí)現(xiàn)貝塞爾曲線的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Servlet3.0與純javascript通過Ajax交互的實(shí)例詳解
Servlet與純javascript通過Ajax交互,對(duì)于很多人來說應(yīng)該很簡(jiǎn)單。不過還是寫寫,方便Ajax學(xué)習(xí)的后來者2018-03-03
微信小程序 require機(jī)制詳解及實(shí)例代碼
這篇文章主要介紹了微信小程序 require機(jī)制詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2016-12-12
微信小程序開發(fā)一鍵登錄 獲取session_key和openid實(shí)例
這篇文章主要介紹了微信小程序開發(fā)一鍵登錄 獲取session_key和openid實(shí)例的相關(guān)資料,需要的朋友可以參考下2016-11-11
JS前端宏任務(wù)微任務(wù)及Event Loop使用詳解
這篇文章主要為大家介紹了JS前端宏任務(wù)微任務(wù)及Event Loop使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07
微信小程序 開發(fā)之滑塊視圖容器(swiper)詳解及實(shí)例代碼
這篇文章主要介紹了微信小程序 開發(fā)之滑塊視圖容器(swiper)詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-02-02
ant-design的upload組件中實(shí)現(xiàn)粘貼上傳實(shí)例詳解
這篇文章主要為大家介紹了ant-design的upload組件中實(shí)現(xiàn)粘貼上傳實(shí)例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05
JS版的date函數(shù)(和PHP的date函數(shù)一樣)
這篇文章主要介紹了JS版的date函數(shù),使用方法和PHP的date函數(shù)一樣,需要的朋友可以參考下2014-05-05

