使用vue實現排序算法演示動畫

博客:lxqnsys.com/css

公衆號:理想青年實驗室html

緣起

最近作的一個小需求涉及到排序,界面以下所示:vue

由於項目是使用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的網站,看到了這個示例:cn.vuejs.org/v2/guide/tr…數組

這個示例我已看過多遍,可是一直沒用過,這裏恰好就是我要的效果,因而一通複製粘貼大法:框架

<template>
    <transition-group name="flip-list" tag="p">
        <!--循環生成列表部分,略-->
    </transition-group>
</template>

<style> .flip-list-move { transition: transform 0.5s; } </style>
複製代碼

這樣就有交換的過渡效果了,以下:ide

嗯,舒服了不少,這個需求到這裏就完了,可是事情並無結束,我忽然想到了之前看一些算法文章的時候一般會配上一些演示的動畫,感受跟這個很相似,那麼是否是能夠用這個來實現呢,固然是能夠的。函數

實現算法演示動畫

先寫一下基本的佈局和樣式:oop

<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循環改寫成能夠控制的函數形式,而後可能須要稍微修改一下顯示邏輯,若是你也有打算寫排序文章的話如今就能夠給本身加上動圖展現了!

總結

以前看到這些動圖的時候也有想過怎麼實現,可是都沒有深究,此次業務開發無心中也算找到了其中的一種實現方式,其實核心邏輯很簡單,關鍵是不少時候沒有想到能夠這麼作,這也許是框架帶給咱們的另外一些好處吧。

相關文章
相關標籤/搜索