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è)谝慌牛覀冎恍枰o他動(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)文章
可拖動(dòng)窗口,附帶鼠標(biāo)控制漸變透明,開啟關(guān)閉功能
可拖動(dòng)窗口,附帶鼠標(biāo)控制漸變透明,開啟關(guān)閉功能...2006-06-06JS前端可視化canvas動(dòng)畫原理及其推導(dǎo)實(shí)現(xiàn)
這篇文章主要為大家介紹了JS前端可視化canvas動(dòng)畫原理及其推導(dǎo)實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08JavaScript專題之underscore防抖實(shí)例學(xué)習(xí)
這篇文章主要為大家介紹了JavaScript專題之underscore防抖實(shí)例學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09JavaScript前端實(shí)現(xiàn)小說分頁功能示例
這篇文章主要為大家介紹了JavaScript前端實(shí)現(xiàn)小說分頁功能示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07使用JavaScript練習(xí)動(dòng)畫最好的方式封面過渡
這篇文章主要為大家介紹了使用JavaScript練習(xí)動(dòng)畫最好的方式封面過渡實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07JavaScript使用JSON.stringify()方法帶參及不帶參示例詳解
這篇文章主要介紹了JavaScript使用JSON.stringify()方法帶參及不帶參示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07