效果演示
制作思路
用一个Item类来代表每一个数组成员,调用window.requestAnimationFrame 来绘制每一帧
制作Item类
对每一个数组成员我们需要知道他的数值、宽高、颜色以及canvas上的位置,并且还要设置好每帧移动的像素数,用vx、vy来代表速度向量。 drawItem函数是其绘制函数。
// Item.js
class Item {
constructor(value, width, heigth){
this.value = value;
this.width = width;
this.heigth = heigth;
// 左下角点
this.x = 0;
this.y = 0;
this.vx = 5;
this.vy = 0;
this.color = 'gray';
}
drawItem (ctx) {
ctx.save();
//坐标轴移动到左下角
ctx.translate(this.x, this.y);
// 绘制矩形
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(this.width, 0);
ctx.lineTo(this.width, -this.heigth);
ctx.lineTo(0,-this.heigth);
ctx.lineTo(0, 0);
ctx.closePath();
ctx.stroke();
ctx.fill();
//绘制数字
ctx.fillStyle = "black";
ctx.font = `${this.width-20}px Arial`;
ctx.fillText(this.value, 0, 0);
ctx.restore();
}
}
由于耗时主要耗费在两物体移动上(另一个是比较大小上色过程),若需要设置倍速演示,只需要简单地在之后程序中将vx值乘以对应倍率。
处理数组
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
//随机生成一个arr数组
let arr = new Array(Math.ceil(Math.random()*15+1)).fill(0);
arr = arr.map(() => Math.ceil(Math.random()*29+1));
//arrHeigth数组依据arr成员值存放对应高度(设置最高高度为400px)
//utils.js里设置一个返回高度的函数arrHeigth
let arrHeigth = utils.arrHeigth(arr, 400);
let arrWidth = 50;
//Arr存放对应的Item成员
let Arr = [];
let numArr = arr.length;
// utils.js
let utils = {};
// 通过数值按比例确定数组成员高度
utils.arrHeigth = function (_array, maxheigth) {
let max = Math.max(..._array);
let pro = maxheigth / max;
let _arrHeigth = _array.map((item, index) => {
return item * pro;
});
return _arrHeigth;
}
Arr存放Item,之后只需循环Arr来绘制每一个成员
for (let item, i = 0; i < numArr; i++) {
item = new Item(arr[i], arrWidth, arrHeigth[i]);
item.x = Math.ceil((canvas.width - 60*numArr) / 2) + 60*i;
item.y = 400;
Arr.push(item);
}
比较(上色)
由于比较大小(同时变绿色、黄色)在一帧里就可以完成,但移动需要很多帧,如果不处理的话会导致瞬间就完成上色与褪色,不符合观感,如下图。
所以定义一个变量来控制保持状态的时间,这里定义了frame。 在比较大小的bubbleCompare函数中有一个数组compareArr, 其第一个值代表是否完成比较(如果你想让比较时间也就是绿色状态保持30帧的时间,就用 frame%30),如果没到时间就不进行之后的操作,继续下一帧 ;
其第二个值代表是否需要交换位置,为后面是否需要绘制交换过程提供判断。
let frame = 0;
let compareArr = [0, 0];
function bubbleCompare(array, j){
frame++;
compareArr = [0, 0];
array[j].color = 'green';
array[j+1].color = 'yellow';
if (frame%30 == 0){
frame = 0;
compareArr[0] = 1;
}
if (array[j].value > array[j+1].value){
compareArr[1] = 1;
}
}
绘制移动
移动需要很多帧的时间,为判断是否交换完成,让drawBubble函数返回一个布尔值,供后续判断是否执行操作(及将Arr数组中两个Item交换位置,以及调节对应参数)
function drawBubble(array, j){
array[j].x += array[j].vx;
array[j+1].x -= array[j+1].vx;
if (array[j].x == (Math.ceil((canvas.width - 60*numArr) / 2) + 60*(j+1)))
{
return true;
} else {
return false;
}
}
帧
现在可以开始绘制每一帧了
整体结构:
(function drawFrame() {
let raf = window.requestAnimationFrame(drawFrame, canvas);
ctx.clearRect(0, 0, canvas.width, canvas.height);
// ......
// 进行比较
// 移动物体(改变Item.x)
// 参数调整
// 判断是否完成
// ...
// 遍历Arr 绘制每一个Item :
Arr.forEach((item) => {
item.drawItem(ctx);});
}());
完整代码:
let _i = 0,
_j = 0,
//compare_bool判断是否需要执行bubbleCompare比较函数
compare_bool = 1;
(function drawFrame() {
let raf = window.requestAnimationFrame(drawFrame, canvas);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (compare_bool){
// 开始比较
bubbleCompare(Arr, _j);
}
if (compareArr[0] == 1){
compare_bool = 0;
if(compareArr[1] == 1){
// 开始移动(设置Item.x)
if(drawBubble(Arr, _j)){
// 到达位置,移动完成
let t = Arr[_j];
Arr[_j] = Arr[_j+1];
Arr[_j+1] = t;
Arr[_j].color = 'gray';
Arr[_j+1].color = 'gray';
_j++;
compare_bool = 1;
}
}
if (compareArr[1] == 0){
Arr[_j].color = 'gray';
Arr[_j+1].color = 'gray';
_j++;
compare_bool = 1;
}
}
// 调整参数
if (_j == numArr-_i-1) {
Arr[_j].color = 'red';
_j = 0;
_i++;
// 判断是否完成所有排序
if (_i == numArr-1) {
Arr[0].color = 'red';
Arr[0].drawItem(ctx);
window.cancelAnimationFrame(raf);
}
}
// 绘制
Arr.forEach((item) => {
item.drawItem(ctx);});
}());