最近做的一個小需求涉及到排序,界面如下所示:
因為項目是使用vue的,所以實現方式很簡單,視圖部分不用管,本質上就是操作數組,代碼如下:
{
// 上移
moveUp (i) {
// 把位置i的元素移到i-1上
let tmp = this.form.replayList.splice(i, 1)
this.form.replayList.splice(i - 1, 0, tmp[0])
},
// 下移
moveDown (i) {
// 把位置i的元素移到i+1上
let tmp = this.form.replayList.splice(i, 1)
this.form.replayList.splice(i + 1, 0, tmp[0])
}
}
這樣就可以正常的交換位置了,但是是突變的,沒有動畫,所以不明顯,于是一個碼農的自我修養(實際上是太閑)讓我打開了vue的網站,看到了這個示例:https://cn.vuejs.org/v2/guide/transitions.html#%E5%88%97%E8%A1%A8%E7%9A%84%E6%8E%92%E5%BA%8F%E8%BF%87%E6%B8%A1
這個示例我已看過多遍,但是一直沒用過,這里剛好就是我要的效果,于是一通復制粘貼大法:
<template>
<transition-group name="flip-list" tag="p">
<!--循環生成列表部分,略-->
</transition-group>
</template>
<style>
.flip-list-move {
transition: transform 0.5s;
}
</style>
這樣就有交換的過渡效果了,如下:
嗯,舒服了很多,這個需求到這里就完了,但是事情并沒有結束,我突然想到了以前看一些算法文章的時候通常會配上一些演示的動畫,感覺跟這個很類似,那么是不是可以用這個來實現呢,當然是可以的。
先寫一下基本的布局和樣式:
<template>
<div class="sortList">
<transition-group name="flip-list" tag="p">
<div
class="item"
v-for="item in list"
:key="item.index"
:style="{height: (item.value / max * 100) + '%'}"
>
<span class="value">{{item.value}}</span>
</div>
</transition-group>
</div>
</template>
<style>
.flip-list-move {
transition: transform 0.5s;
}
</style>
list
是要排序的數組,當然是經過處理的,在真正的源數組上加上了唯一的index
,因為要能正常過渡的話列表的每一項需要一個唯一的key:
const arr = [10, 43, 23, 65, 343, 75, 100, 34, 45, 3, 56, 22]
export default {
data () {
return {
list: arr.map((item, index) => {
return {
index,
value: item
}
})
}
}
}
max
是這個數組中最大的值,用來按比例顯示高度:
{
computed: {
max () {
let max = 0
arr.forEach(item => {
if (item > max) {
max = item
}
})
return max
}
}
}
其他樣式可以自行發揮,顯示效果如下:
簡約而不簡單~,現在萬事俱備,只欠讓它動起來,排序算法有很多,但是本人比較菜,所以就拿冒泡算法來舉例,最最簡單的冒泡排序算法如下:
{
mounted(){
this.bubbleSort()
},
methods: {
bubbleSort() {
let len = this.list.length
for (let i = 0; i < len; i++) {
for (let j = 0; j < len - i - 1; j++) {
if (this.list[j] > this.list[j + 1]) { // 相鄰元素兩兩對比
let tmp = this.list[j] // 元素交換
this.$set(this.list, j, this.list[j + 1])
this.$set(this.list, j + 1, tmp)
}
}
}
}
}
}
但是這樣寫它是不會動的,瞬間就給你排好了:
試著加個延時:
{
mounted () {
setTimeout(() => {
this.bubbleSort()
}, 1000)
}
}
刷新看效果:
有動畫了,不過這種不是我們要的,我們要的應該是下面這樣的才對:
所以來改造一下,因為for循環是只要開始執行就不會停的,所以需要把兩個for循環改成兩個函數,這樣可以控制每個循環什么時候執行:
{
bubbleSort () {
let len = this.list.length
let i = 0
let j = 0
// 內層循環
let innerLoop = () => {
// 每個內層循環都執行完畢后再執行下一個外層循環
if (j >= (len - 1 - i)) {
j = 0
i++
outLoop()
return false
}
if (this.list[j].value > this.list[j + 1].value) {
let tmp = this.list[j]
this.$set(this.list, j, this.list[j + 1])
this.$set(this.list, j + 1, tmp)
}
// 動畫是500毫秒,所以每隔800毫秒執行下一個內層循環
setTimeout(() => {
j++
innerLoop()
}, 800)
}
// 外層循環
let outLoop = () => {
if (i >= len) {
return false
}
innerLoop()
}
outLoop()
}
}
這樣就實現了每一步的動畫效果:
但是這樣不太直觀,因為有些相鄰不用交換的時候啥動靜也沒有,不知道當前具體排到了哪兩個,所以需要突出當前正在比較交換的兩個元素,首先模板部分給當前正在比較的元素加一個類名,用來高亮顯示:
<div
:class="{sortingHighlight: sorts.includes(item.index)}"
>
<span class="value">{{item.value}}</span>
</div>
js部分定義一個數組sorts
來裝載當前正在比較的兩個元素的唯一的index
值:
{
data() {
return {
sorts: []
}
},
methods: {
bubbleSort () {
// ...
// 內層循環
let innerLoop = () => {
// 每個內層循環都執行完畢后再執行下一個外層循環
if (j >= (len - 1 - i)) {
// 清空數組
this.sorts = []
j = 0
i++
outLoop()
return false
}
// 將當前正在比較的兩個元素的index裝到數組里
this.sorts = [this.list[j].index, this.list[j + 1].index]
// ...
}
// 外層循環
// ...
}
}
}
修改后效果如下:
最后,再參考剛才別人的示例把已排序的元素也加上高亮:
{
data() {
return {
sorted: []
}
},
methods: {
bubbleSort () {
// ...
// 內層循環
let innerLoop = () => {
// 每個內層循環都執行完畢后再執行下一個外層循環
if (j >= (len - 1 - i)) {
this.sorts = []
// 看這里,把排好的元素加到數組里就ok了
this.sorted.push(this.list[j].index)
j = 0
i++
outLoop()
return false
}
// ...
}
// 外層循環
// ...
}
}
}
最終效果如下:
接下來看一下選擇排序,這是選擇排序的算法:
{
selectSort() {
for (let i = 0; i < len - 1; i++) {
minIndex = i
for (let j = i + 1; j < len; j++) {
if (this.list[j].value < this.list[minIndex].value) {
minIndex = j
}
}
tmp = this.list[minIndex]
this.$set(this.list, minIndex, this.list[i])
this.$set(this.list, i, tmp)
}
}
}
選擇排序涉及到一個當前最小元素,所以需要新增一個高亮:
<div
:class="{minHighlight: min === item.index , sortingHighlight: sorts.includes(item.index), sortedHighlight: sorted.includes(item.index)}"
>
<span class="value">{{item.value}}</span>
</div>
{
data () {
return {
min: 0
}
},
methods: {
selectSort () {
let len = this.list.length
let i = 0; let j = i + 1
let minIndex, tmp
// 內層循環
let innerLoop = () => {
if (j >= len) {
// 高亮最后要交換的兩個元素
this.sorts = [this.list[i].index, this.list[minIndex].index]
// 延時是用來給高亮一點時間
setTimeout(() => {
// 交換當前元素和比當前元素小的元素的位置
tmp = this.list[minIndex]
this.$set(this.list, minIndex, this.list[i])
this.$set(this.list, i, tmp)
this.sorted.push(this.list[i].index)
i++
j = i + 1
outLoop()
}, 1000)
return false
}
// 高亮當前正在尋找中的元素
this.sorts = [this.list[j].index]
// 找到比當前元素小的元素
if (this.list[j].value < this.list[minIndex].value) {
minIndex = j
this.min = this.list[j].index
}
setTimeout(() => {
j++
innerLoop()
}, 800)
}
let outLoop = () => {
if (i >= len - 1) {
this.sorted.push(this.list[i].index)
return false
}
minIndex = i
this.min = this.list[i].index
innerLoop()
}
outLoop()
}
}
}
效果如下:
其他的排序也是同樣的套路,將for循環或while循環改寫成可以控制的函數形式,然后可能需要稍微修改一下顯示邏輯,如果你也有打算寫排序文章的話現在就可以給自己加上動圖展示了!
之前看到這些動圖的時候也有想過怎么實現,但是都沒有深究,這次業務開發無意中也算找到了其中的一種實現方式,其實核心邏輯很簡單,關鍵是很多時候沒有想到可以這么做,這也許是框架帶給我們的另一些好處吧。
天我們學習的內容有:過渡,動畫,轉換,伸縮盒子。
可以說今天學習的內容都是重量級的大佬,學好了,使用css3做出酷炫的效果 So Easy!~~
1.過渡
在css3中,有一個屬性可以設置過渡效果。
它就是transition,所謂的過渡效果,指的就是以動畫的形式慢慢演化樣式屬性變化的過程。
A.案例:通過transition設置焦點過渡效果
<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Document</title><style>div{width: 200px;height: 200px;margin:200px;background: url(girl.jpg);border-radius:50%;transition:all 1s linear 0.3s;cursor: pointer;}div:hover{box-shadow: 0px 0px 20px blue;}</style></head><body><div></div></body></html>
注意頁面中的代碼:
第一,我們給div添加了一個hover偽類樣式,當我們鼠標懸停在div上方的時候,會給div盒子添加一個藍色的盒子陰影。
第二,我們給div盒子添加了一個transition樣式,設置的值為:all 1s linear 0.3s;
這四個數據分別對應
transition-property(需要過渡的屬性):如果設置為all表示所有樣式屬性都需要過渡。
transition-duration(過渡的時間):以秒作為單位,設置過渡的時間
transition-timing-function(過渡的方式):常用的有linear(勻速),ease(先慢后快),ease-in,ease-out,ease-in-out等
transition-delay(延遲的時間):以秒作為單位進行延遲,延遲之后開始進行過渡效果。
所以,我們通過transition這個復合屬性設置的過渡效果為:
all:需要過渡所有的屬性
1s:過渡的時間為1秒
linear:勻速過渡
0.3s:在延遲0.3秒之后開始過渡動畫。
如果大家理解了上面的描述,那么也就不難理解咱們鼠標放到div上之后,為啥會慢慢出現藍色的光暈了,就是因為咱們添加了過渡,所以,慢慢的就會給盒子添加陰影效果。
2.動畫:
在學習完了過渡之后,發現咱們可以使用transition去以動畫的形式展示樣式的改變以及變化的過程,這可以幫助我們來實現一些過渡的動畫。
但是,有的時候,我們的需求會更加的復雜,要求會更加的多變,那么,transition可能就無法滿足我們的需要了,我們需要有更加炫酷,復雜的效果呈現。
那么,動畫animation就可以滿足我們的需要。
<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Document</title><style>@keyframes moveAndChange{0%{left:0px;top:0px;}25%{left:200px;top:200px;background:green;border-radius: 0;}50%{left:400px;top:200px;background:blue;border-radius: 50%;}75%{left:400px;top:0px;background:#ccc;border-radius: 0;}100%{left:0px;top:0px;background:red;border-radius: 50%;}}div{margin:200px;width: 200px;height: 200px;position: absolute;background:red;border-radius:50%;animation: moveAndChange 5s linear 0.5s infinite normal;}</style></head><body><div></div></body></html>
代碼效果如下:
同樣,讓我們來關注編寫的代碼:
1.在樣式中,首先我們使用@keyframes 來定義了一個復雜的動畫,在css3中,新增了@keyframes可以來幫助我們添加動畫。代碼如下:
/*動畫的名字叫做moveAndChange*/
@keyframes moveAndChange{
/*動畫最初的時候,將left設置為0px,top設置為0px*/
0%{
left:0px;
top:0px;
}
/*當動畫進行到25%的時候,使用動畫將left過渡到200px,top過渡到200px,
背景顏色過渡為綠色,圓角過渡為0(無圓角)*/
25%{
left:200px;
top:200px;
background:green;
border-radius: 0;
}
/*當動畫進行到50%的時候,使用動畫將left過渡到400px,top過渡到200px,
背景顏色過渡為藍色,圓角過渡為50%(正圓)*/
50%{
left:400px;
top:200px;
background:blue;
border-radius: 50%;
}
/*當動畫進行到75%的時候,使用動畫將left過渡到400px,top過渡到0,
背景顏色過渡為灰色,圓角過渡為0(無圓角)*/
75%{
left:400px;
top:0px;
background:#ccc;
border-radius: 0;
}
/*當動畫結束的時候,使用動畫將left過渡到0x,top過渡到0px,
背景顏色過渡為紅色,圓角過渡為50%(正圓)*/
100%{
left:0px;
top:0px;
background:red;
border-radius: 50%;
}
}
這是一個比較復雜的動畫效果,可以發現,它通過百分比的形式將一個完整的動畫拆分成了5個部分,每個部分都有不同的樣式效果,而當我們采用該動畫的元素就會按照設置的順序和樣式效果進行動畫的過渡和展示。
2.上面我們只是通過@keyframes創建了一個動畫,我們還需要通過特定的語法來使用這個動畫。
就是下面這句代碼了:
animation: moveAndChange 5s linear 0.5s infinite normal;
它是一個復合屬性,設置了6個值,分別對應:
animation-name(設置動畫的名稱):用來設置動畫的名字,我們這里寫的是moveAndChange ,也就是說我們就是要使用我們剛剛創建的動畫。
animation-duration(設置整個動畫的時間):以秒作為單位,我們這里寫的是5s,表示整個動畫的時間為5秒
animation-timing-function(設置播放動畫的方式):播放動畫的方式,常用的有linear(勻速),ease(先慢后快),ease-in,ease-out,ease-in-out等,我們使用的是linear勻速播放動畫。
animation-delay(設置動畫的延遲):以秒作為單位,我們寫的是0.5s,表示延遲0.5秒之后開始播放動畫。
animation-iteration-count(設置動畫播放的次數):播放動畫的次數,我們這里寫的是infinite ,表示動畫將會被播放無限次,如果寫數字,那么就會播放數字對應的次數。
animation-direction(設置是否反向播放動畫):我們寫的是normal,表示正常播放動畫,如果寫的是
alternate則表示要反向播放動畫,大家也可以自己試一試這個效果。
最終,我們通過@keyframes創建動畫,通過animation設置動畫,成功完成了這個復雜的動畫效果。
3.轉換
在css3中,我們通過transform屬性可以設置元素的轉換效果,具體的效果如下:
A.平移
<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Document</title><style>body{background:pink;}div{width: 200px;height: 200px;position: absolute;background: green;left:0px;top:0px;transform: translate(300px,300px);}</style></head><body><div></div></body></html>
代碼效果如下:
如上圖所示,本來div盒子的位置是left:0,top:0;
但是我們通過transform: translate(300px,300px);將盒子進行了偏移,所以,盒子的位置發生了改變。
B.旋轉
<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Document</title><style>body {background: pink;}div {width: 200px;height: 200px;margin: 200px;position: absolute;background: green;left: 0px;top: 0px;transform: rotate(45deg);}</style></head><body><div></div></body></html>
代碼效果如下:
如上圖所示,本來div盒子應該是四四方方的。
但是,經過我們的代碼transform: rotate(45deg); //deg為單位,表示度數。
進行了45度的旋轉之后,呈現出來的就是一個菱形的盒子了,旋轉的正方向為順時針,負方向為逆時針。
C.縮放
<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Document</title><style>body {background: pink;}div {width: 200px;height: 200px;margin: 200px;position: absolute;background: green;left: 0px;top: 0px;transform: scale(0.5,0.25);}</style></head><body><div></div></body></html>
代碼效果如下:
如上圖所示,本來盒子的寬高為200*200,而我們通過transform: scale(0.5,0.25);進行的縮放
scale的第一個參數為0.5,表示橫向縮小為0.5倍
scale的第二個參數為0.25,表示縱向縮小為0.25倍。
scale的參數如果為1,則表示不進行任何縮放,小于1就是做縮小,而大于1表示做放大。
小結:transform轉換中其實還包含了skew(傾斜),matrix(矩陣轉換),相對來說用到的不是特別多,所以在本文中我們便不再做介紹。
4.flex布局
Flex布局,可以簡便、完整、響應式地實現各種頁面布局。
Flex是Flexible Box的縮寫,翻譯成中文就是“彈性盒子”,用來為盒裝模型提供最大的靈活性。任何一個容器都可以指定為Flex布局。
<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Document</title><style>div{box-sizing: border-box;}.parent {width: 600px;height: 200px;margin: 100px;position: absolute;background: green;left: 0px;top: 0px;display: flex;justify-content: flex-start}.parent div{width: 20%;border:1px solid #ccc;background:pink;}</style></head><body><div><div>1</div><div>2</div><div>3</div><div>4</div></div></body></html>
代碼效果如下:
如圖所示,咱們通過display:flex將.parent元素設置為了flex盒子,那么子元素將會按照justify-content設置的方式進行元素的排列,目前看來,和我們沒有設置flex盒子的效果是一致的。
接下來我們更改一下,將justify-content設置為flex-end,效果如下圖所示:
所以我們就應該發現,flex-start是讓所有的子元素從父元素的左側開始排列
而flex-end是讓所有的子元素從元素的右側開始排列。
我們再來更改一下,將justify-content設置為center,效果如下圖所示:
更厲害了,子元素在父盒子的中央位置排列顯示了。
然后,我們再將justify-content設置為space-around,效果如下圖所示:
它是平均分配的形式為每一個子元素設置了間距,但是看起來有點變扭。
所以我們推薦將justify-content設置為space-between,效果如下圖:
我們還可以通過flex-wrap來設置子元素是否換行顯示,以及flex-direction設置子元素排列的順序。
這兩個屬性可以設置的值如下:
flex-wrap: nowrap;//不換行,會自動收縮
flex-wrap: warp;//換行,會自動收縮
flex-wrap: warp-reverse;//反轉,從默認的從上到下排列反轉為從下到上。
flex-direction:row; //從左至右一行一行進行子元素的排列
flex-direction:column; //從上到下一列一列進行子元素的排列
flex-direction:row-reverse; //從右至左一行一行進行子元素的排列
flex-direction:column-reverse; //從下到上一列一列進行子元素的排列
案例代碼如下:
<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Document</title><style>div{box-sizing: border-box;}.parent {width: 600px;height: 200px;margin: 100px;position: absolute;background: green;left: 0px;top: 0px;display: flex;justify-content: space-between;flex-wrap: nowrap;flex-direction: row-reverse;}.parent div{width: 20%;border:1px solid #ccc;background:pink;}</style></head><body><div><div>1</div><div>2</div><div>3</div><div>4</div><div>5</div><div>6</div></div></body></html>
我們設置了flex-wrap: nowrap;(不換行,壓縮所有的子元素在一行中顯示),以及flex-direction: row-reverse;(反向排列)
代碼效果如下:
如果設置為flex-wrap: warp(換行顯示無法在一行顯示的子元素),則效果如下:
如果將flex-direction: column;,則會縱向排列元素,效果如下圖:
除了上面的這些給伸縮盒子父元素設置的樣式之外,我們還可以可以伸縮盒子的子元素設置flex屬性,用來設置平均分配整個父盒子的空間。
代碼如下:
<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Document</title><style>div{box-sizing: border-box;}.parent {width: 600px;height: 200px;margin: 100px;position: absolute;background: green;left: 0px;top: 0px;display: flex;justify-content: space-between;}.parent div{flex:1;width: 20%;border:1px solid #ccc;background:pink;}</style></head><body><div><div>1</div><div>2</div><div>3</div><div>4</div></div></body></html>
效果如下:
如上圖所示,每個盒子平均分配了父盒子的空間,原本寬度為20%,現在被拉伸了。
除此之外,咱們還可以使用flex屬性進行進一步的設置,代碼如下:
<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Document</title><style>div{box-sizing: border-box;}.parent {width: 600px;height: 200px;margin: 100px;position: absolute;background: green;left: 0px;top: 0px;display: flex;justify-content: space-between;}.parent div:nth-of-type(1){flex:1;border:1px solid #ccc;background:red;}.parent div:nth-of-type(2){flex:2;border:1px solid #ccc;background:green;}.parent div:nth-of-type(3){flex:2;border:1px solid #ccc;background:blue;}.parent div:nth-of-type(4){flex:1;border:1px solid #ccc;background:pink;}</style></head><body><div><div>1</div><div>2</div><div>3</div><div>4</div></div></body></html>
效果如下圖:
我們分別給四個子盒子設置了flex:1 , flex:2, flex:2 ,flex:1.
這是什么意思呢?
四個flex加起來一共是6.那么第一個盒子就占據整個父盒子的1/6寬度。
同理,另外三個盒子分別占據2/6,2/6,1/6的寬度,所以就形成了我們現在看到的效果。
原文來源于:黑馬程序員社區
學習資源:
想學習css,可以關注:黑馬程序員頭條號,后臺回復:css
眨眼的工夫,很快就又到了新一年的七夕節了,正好碰到今天公司搞了一個七夕小活動的工夫,就用JS寫了一個冒泡排序算法,順便寫了動畫排序過程。
其實這種算法動畫效果網上有很多例子,但今天興趣使然,就抽空想自己實現一下。
代碼的優化這里就不做討論了,完全是為了實現自己小小的滿足感,感興趣的童鞋可以自己在電腦上做一下代碼優化。
下面是效果圖(部分):
廢話不多說,直接上代碼(大佬會做的比我更好):
// 渲染容器
<div id="container"></div>
// 要排序的數組
let arr = [27, 37, 2, 50, 10, 5, 41, 12, 41, 6, 4, 24, 47, 31, 12];
// 每個dom元素的左邊距單位
let posLeft = 57;
// 獲取渲染容器
const container = document.getElementById('container');
/**
* @description 動態渲染移動元素
* @param { Object } elem 渲染容器
* @param { Array } arr 數據列表
* @return { Void }
*/
const renderHTML = (elem, arr) => {
let html = '',
className = '',
totalWidth = 0;
arr.forEach((item, index) => {
if (item * 3 < 18) className = 'out';
else className = '';
totalWidth = index;
html += `<li class="item" data-index="${ index }" style="height: ${ item * 3 }px; left: ${ posLeft * index }px;">
<span class="${ className }">${ item }</span>
</li>`;
});
elem.innerHTML = `<ul class="list" style="width: ${ posLeft * totalWidth + 48 }px;">${ html }</ul>`;
}
renderHTML(container, arr);
html, body {
margin: 0;
padding: 0;
}
#container,
h1 {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
h1 {
height: auto;
margin: 6% 0;
}
.list {
position: relative;
display: flex;
align-items: flex-end;
list-style: none;
padding: 0;
margin: 0;
margin-top: 10%;
}
.item {
width: 45px;
margin-right: 12px;
display: inline-flex;
justify-content: center;
align-items: center;
background-color: rgb(173, 216, 230);
font-size: 20px;
color: #333;
pointer-events: none;
transition: all .3s ease;
position: absolute;
}
.item:last-child {
margin-right: 0;
}
.item.left,
.item.right {
background-color: rgb(0, 128, 0);
}
.item.left::before,
.item.right::before {
content: "";
position: absolute;
left: -4px;
top: -4px;
width: calc(100% + 4px);
height: calc(100% + 4px);
border: 2px solid red;
}
.item.left::after,
.item.right::after {
content: "";
position: absolute;
left: 50%;
bottom: -20px;
width: 0;
height: 0;
border: 10px solid transparent;
border-bottom-color: red;
transform: translateX(-50%);
}
.item.success {
background-color: rgb(255, 165, 0);
}
.out {
position: absolute;
bottom: calc(100% + 2px);
left: 50%;
transform: translateX(-50%);
color: #333;
}
在上面的代碼中,className = 'out'是用來判斷數字是否已超出元素內部,如果超出,則顯示在元素外部,而非內部。防止字體與元素顯示交叉感。
${ item * 3 },乘以3完全是為了元素看起來更高大一些,要不然,當要排序的數值中包括<10的數字時,元素的高度就會幾乎看不見。
${ posLeft * index },會隨著元素的渲染,left值會隨著index索引值的增大而改變。
${ posLeft * totalWidth + 48 },根據子元素渲染個數設置父元素的總寬度,因為子元素采用了定位布局,所以父元素撐不開,需要動態設置寬度,以適配頁面的居中顯示。
當然不設置父元素的寬度也不影響執行,只是會在一定程度上,列表會不方便居中在視窗的水平垂直居中位置。
此時已經渲染出列表了,但是沒有執行排序算法,下面添加排序算法。
const bubbleSort = arr => {
const len = arr.length;
for (let i = 0; i < len; i ++) {
for (let j = i + 1; j < len; j ++) {
if (arr[i] > arr[j]) {
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
}
return arr;
}
// 原數組:[27, 37, 2, 50, 10, 5, 41, 12, 41, 6, 4, 24, 47, 31, 12]
bubbleSort(arr);
冒泡排序算法有很多種方法,這里只介紹了一種,就是:套用兩層循環,一個一個對比,如果找到符合的元素,就通過[arr[i], arr[j]] = [arr[j], arr[i]]數組解構的方式,調換兩個元素的位置。
也許這樣表達大家有些難以理解,當然高手飄過哈。其實我也當初不理解,不過通過自己摸索再結合動畫的形式來看,對理解算法的過程會更加明確。
結果是已經排好了,但是還不能讓它們動起來,想要動起來,繼續和我一步一步做下去。
想要讓元素動起來前,我們需要有兩個標識,一個左一個右。
左稱為:.item.left 左邊界元素。右稱為:.item.right 右邊界元素。我在這里用了這兩個元素,完全是為了在執行動畫的時候方便區分理解。
代表:左邊界元素需要每次和右邊界元素去對比,如果有小于左元素的,則進行調換,否則不動。
比如這樣:
動的一直是右邊界,而非左邊界,左邊界在這里只是充當一個基點的角色,用來和其它元素進行對比。
改造一下:
const list = document.getElementsByClassName('list')[0];
// 這里需要擴展字符串把元素列表擴展成真正的dom數組,否則不能進行下面的filter語法
const items = [...document.getElementsByClassName('item')];
const setPos = (left, right) => {
// 獲取左、右邊界元素
let eleLeft = items.filter(item => item.getAttribute('data-index') == left);
let eleRight = items.filter(item => item.getAttribute('data-index') == right);
let leftInfo = {
pos: eleLeft[0].offsetLeft,
index: eleLeft[0].getAttribute('data-index')
};
let rightInfo = {
pos: eleRight[0].offsetLeft,
index: eleRight[0].getAttribute('data-index')
};
// 設置左、右邊界元素的距離與高亮
// 因為要互換位置,所以class類名要互相調換
eleLeft[0].style.left = rightInfo.pos + 'px';
eleLeft[0].className = 'item right';
eleRight[0].style.left = leftInfo.pos + 'px';
eleRight[0].className = 'item left';
}
// 小于左邊界索引的元素,全部設置成高亮,代表已經排序完成的元素
const setSuccess = (arr, index) => {
for (let i = 0, len = arr.length; i < len; i ++) {
if (i < index) arr[i].className = 'item success';
}
}
// type未傳值,或,className包含right時,清空所有高亮
const clearClass = type => {
for (let i = 0, len = items.length; i < len; i ++) {
if (!type || items[i].className.includes(type)) {
items[i].className = 'item';
break;
}
}
}
const bubbleSort = arr => {
const len = arr.length;
for (let i = 0; i < len; i ++) {
// 重新獲取列表元素
const items = document.getElementsByClassName('item');
// 清空樣式
clearClass();
// 設置完成排序的高亮元素
setSuccess(items, i);
items[i].className = 'item left';
if (!items[i + 1]) {
// 如果后面已經沒有元素了,則停止排序,完成操作,高亮最后一個元素
setSuccess(items, i + 1);
break;
}
// 依次用左邊界元素對比右界所有元素
for (let j = i + 1; j < len; j ++) {
// 只清空包含右邊界元素的高亮
clearClass('right');
items[j].className = 'item right';
// 如果左邊界比右邊界大
if (arr[i] > arr[j]) {
// 則調換兩個元素的位置
setPos(i, j);
// 調換數組中兩個值的位置
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
}
}
此時刷新頁面,好吧,我傻眼了。。。都亂了。
因為我在上面的CSS中加了300毫秒的元素動畫時間transition: all .3s ease。
但是循環太快了,還來不及做動畫,元素在運動的過程中又再次執行下次渲染,就造成了這種無腦局面。。。
想要解決這個問題,需要有一種可以讓循環慢下來的辦法才行,下面我們可以這樣做,想讓它多慢,就有多慢。
const sleep = time => {
return new Promise(resolve => setTimeout(resolve, time));
}
const bubbleSort = async arr => {
const len = arr.length;
for (let i = 0; i < len; i ++) {
// 重新獲取列表元素
const items = document.getElementsByClassName('item');
// 清空樣式
clearClass();
// 設置完成排序的高亮元素
setSuccess(items, i);
items[i].className = 'item left';
// 隔600毫秒后再執行后續操作
await sleep(600);
if (!items[i + 1]) {
// 如果后面已經沒有元素了,則停止排序,完成操作,高亮最后一個元素
setSuccess(items, i + 1);
break;
}
// 依次用左邊界元素對比右界所有元素
for (let j = i + 1; j < len; j ++) {
// 只清空包含右邊界元素的高亮
clearClass('right');
items[j].className = 'item right';
// 隔600毫秒
await sleep(600);
// 如果左邊界比右邊界大
if (arr[i] > arr[j]) {
// 則調換兩個元素的位置
setPos(i, j);
// 調換數組中兩個值的位置
[arr[i], arr[j]] = [arr[j], arr[i]];
// 保證移動動畫執行完成后,再次進行下一輪比較
await sleep(800);
}
}
}
}
我們可以使用async與promise語法搭配來模擬異步操作過程,在上面的例子中,循環的過程必須要等到sleep方法有返回值后,才可以進行后面的循環,采用傳入的時間參數毫秒,來生成一個設置在時間范圍內的定時器,直至等待返回。
這是怎么回事,雖然在有條理的做著動畫,但是明顯不對吧。都亂套了。。。
const setPos = (left, right) => {
// ...
// 需要加個延遲,等動畫特效執行完成后,再去設置調換元素的索引值與實際位置
setTimeout(() => {
// 因為此時元素的位置已經改變,所以需要重新獲取元素列表
const items = document.getElementsByClassName('item');
// 設置左邊界元素的索引值為右邊界元素的索引值
eleLeft[0].setAttribute('data-index', rightInfo.index);
// 把左邊界元素插入到右邊界元素的下一個兄弟元素的前面
list.insertBefore(eleLeft[0], items[right].nextElementSibling);
// 設置右邊界元素的索引值為左邊界元素的索引值
eleRight[0].setAttribute('data-index', leftInfo.index);
// 把右邊界元素插入到左邊界元素的前面
list.insertBefore(eleRight[0], items[left]);
}, 400);
}
那是因為運動元素動畫完成后,只是視圖上更新而已,視覺上看確實變了,但變的是元素的left值,而非真實的dom列表位置。
所以我們需要在每次調換元素位置后,需要重新獲取一下dom列表,因為此時的元素位置已經發生變化,需要重新更新此列表。
然后再設置一下調換元素的索引值,保證新設置的索引和調換后的索引是一一對應的。
最后需要list.insertBefore(eleLeft[0], items[right].nextElementSibling);,把左邊界元素,插入到右邊界下一個兄弟元素的前面。
if (!items[i + 1]) {
// 如果后面已經沒有元素了,則停止排序,完成操作,高亮最后一個元素
setSuccess(items, i + 1);
break;
}
在循環的時候,我這里做了判斷后面是否還有其它元素。
如果沒有,則不會執行調換元素的函數,否則當循環到最后一個元素,后面再沒有元素的時候,執行items[right].nextElementSibling會報錯。
list.insertBefore(eleRight[0], items[left]);,把右邊界元素插入到左邊界元素的前面。
這樣再看的話,是不是就沒有問題啦!
其實這個demo其實哪有完美的,如果追求完美,需要優化的地方還有很多,比如:代碼復用性、代碼不簡潔、命名是否規范、兼容性是否可行等等。
感興趣的小伙伴可以自己去試試做下優化,我相信你們肯定比我強。
感謝您抽出寶貴的時間閱讀本文,希望對您有所幫助。
如果您遇到什么疑問或者建議,歡迎多多交流,大家共同進步。
在閱讀過程中,如果有不正確的地方,希望您能提出來,我會努力改正并提供更優質的文章。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。