相信作为一个程序猿,大家应该都已经看过一些排序算法的过程教学动画,现在我来总结一下自己使用ES6的generator实现动画的过程,以作为自己对generator的使用实践的一个记录。
为什么用generator实现?
因为对于计算机而言排序是一个十分短暂的过程,然而动画却不是。动画的生成需要获取当前帧的状态,也就是当前数列的状态,若是我们在计算过程中插入动画的播放,也就是在交换时播放交换动画,由于动画播放是异步的,当你完成了这一次的交换两个数的动画播放,排序已经完成了,数列的状态已经是有序,你已经无法在根据数列状态生成后面的动画并播放。有人可能想到,那可以等到动画播放完再进行下一次的比较和交换,然而我并没有想到如何监听交换动画结束的状态的方法,所以我没有实现这种动画播放的同步化。
一个解决方法时,计算过程中发生交换,不播放动画,只生成动画的“帧”并保存,等排序完毕,"帧”也全部生成完毕,然后逐帧显示形成动画就可以了。但是这种方法缺点在于需要保存帧的数据,也就是每一瞬间动画的状态。因此可以每生成一帧便渲染一次,渲染完后再进行后面的比较和交换。但是这样帧与帧之间的时间间隔变得难以控制,因为你在渲染完这帧后渲染下一帧的时间就是继续开始计算并发生下一次交换并生成帧数据的时间,这个时间十分短,几乎接近0,因此这样的动画是不可控并且短暂的。若是想让帧与帧之间固定一个时间间隔,就必须使用setTimeout()或setInterval(),但这样又会产生之前的异步问题了。因此可以使用generator,了解generator的应该明白,它是一个状态机,代表函数内部的执行状态,当执行到某一语句时就会将这个状态“冻结",并且返回该语句的结果,只有调用generator的next()方法,才能继续执行函数内部语句直到下一个"冻结"产生为止。这里我利用了generator的"冻结”特性,当需要交换时,冻结状态,进行两个数交换动画的渲染和播放,这里我可以设置这个交换动画有多少帧,多长时间后进行下一次交换的计算(我用的setInterval,在回调函数中调用next进行下一次交换的计算)。
代码如下(只是部分关键代码):
function* bubbleSort(array, compare, sorter) {
let i = 0, j = 0, l = array.length;
for (i = 0; i < l; ++i) {
for (j = 0; j < l - i - 1; ++j) {
if (!compare(array[j], array[j + 1])) {
yield* swap(j, j + 1, sorter); //嵌套generator函数
[array[j], array[j + 1]] = [array[j + 1], array[j]];
[sorter.states[j], sorter.states[j + 1]] = [sorter.states[j + 1], sorter.states[j]];
}
}
}
}
function* swap(a, b, sorter) {
yield* rise(a, b, sorter); //交换上升动画
yield* move(a, b, sorter); //交换平移动画
yield* down(a, b, sorter); //交换下降动画
}
function *rise(a, b, sorter) {
let stateA = sorter.states[a],
stateB = sorter.states[b];
//一次循环更新一次状态,形成一个新帧的信息
for (let i = 1; i <= FRAME_NUMBER; ++i) {
stateA.y -= sorter.divideLineHeight / FRAME_NUMBER;
stateB.y -= sorter.divideLineHeight / FRAME_NUMBER;
yield; //“冻结”状态,进行渲染
}
}
function *down(a, b, sorter) {
let stateA = sorter.states[a],
stateB = sorter.states[b];
for (let i = 1; i <= FRAME_NUMBER; ++i) {
stateA.y += sorter.divideLineHeight / FRAME_NUMBER;
stateB.y += sorter.divideLineHeight / FRAME_NUMBER;
yield;
}
}
function *move(a, b, sorter) {
let stateA = sorter.states[a],
stateB = sorter.states[b],
p = sorter.numbers[a] < sorter.numbers[b],
dist = Math.abs(stateA.x - stateB.x);
for (let i = 0; i < FRAME_NUMBER; ++i) {
if (!p) {
stateA.x += dist / FRAME_NUMBER;
stateB.x -= dist / FRAME_NUMBER;
} else {
stateA.x += dist / FRAME_NUMBER;
stateB.x -= dist / FRAME_NUMBER;
}
yield;
}
}
sort(type) {
if (type == sortType.bubble) {
this.sort = bubbleSort(this.numbers, (a, b) => {
return a - b < 0;
}, this);
}
this.animation = setInterval((() => {
//此语句是进行generator更新,若generator到达最后一个状态,停止动画
if (this.sort.next().done) {
clearInterval(this.animation);
}
//更新后渲染
this.render();
}).bind(this), 0);
}