固然在咱们不清楚具体操做细节前咱们能够先假设一下,咱们可以用什么来实现。按照之前看过的排序动画我将其分为javascript
1.Js操做Dom,再搭配简单的css 2.Canvas动画
以后在查资料的时候发现还有人用d3
这个库来完成。css
做为一个有(被)梦(坑)想(多)的前端,一开始就得考虑到如何实现套入多个算法,若是实现单步运行(可能的话还得有往回走的功能),如何实现动画速度的控制等等。html
固然幻想那么多一会儿实现是不现实的,咱们得先找一个简单的例子来看看再一步步深刻。前端
先来看下效果图java
以后咱们分析源码:node
css: #field {width:500px;height:510px;background:black;position:relative} .bar {position:absolute;bottom:0;background:orange;border:1px solid brown;width:24px} html: <div id="field"> <div class="bar"></div> <div class="bar"></div> <div class="bar"></div> <div class="bar"></div> <div class="bar"></div> <div class="bar"></div> <div class="bar"></div> <div class="bar"></div> <div class="bar"></div> <div class="bar"></div> </div> javascript: !function(d){ var bars = [].slice.call(d.querySelectorAll('.bar')); var arr = [8, 10, 3, 5, 6, 9, 2, 4, 7, 1]; var state = []; var draw = function(){ var bar, s; s = state.shift() || []; for(bar in bars){ bars[bar].style.height = 25 * s[bar] + 'px'; bars[bar].style.left = 25 * bar + 'px'; } } var sort = function(arr){ for(var i = 0; i < arr.length; i++){ for(var j = 0; j < arr.length - i - 1; j++){ if(arr[j] > arr[j+1]){ arr[j] = arr[j] + arr[j+1]; arr[j+1] = arr[j] - arr[j+1]; arr[j] = arr[j] - arr[j+1]; state.push(JSON.parse(JSON.stringify(arr))); } } } } sort(arr); setInterval(draw, 500); }(document)
整个流程其实很清晰,可是其中部分代码让我疑惑了一段时间,通过google和问群里的朋友,终于解惑。咱们先来理清这段代码的思路git
首先这个动画是将大小宽度都定死了。以及排序数组的数量须要和html结构里bar的数量一致。 1.html的bar是长方体,它的宽是24px而后有个1px的border,所以在代码中动态改变left的时候须要设定为25. 2.js代码中用一个匿名当即函数包裹代码。 bars = [].slice.call(d.querySelectorAll('.bar')); 这段将获取的nodelist转为成一个对象数组,这样方便对其中每一个bar进行单独修改样式 3.设定一个state空数组来保存每个状态,记住这才是动画的关键。 4.state.shift()临时像将数组模拟成队列,draw函数根据其第一个出列的内容来从新排列列表,在 setInterval(draw, 400)的配合下,就能造成一个动画排序。 5 sort函数和咱们以前介绍的冒泡排序是同样的,只不过这里有一句 state.push(JSON.parse(JSON.stringify(arr))); 这句是核心,一看是乍看是否是很奇怪,为何要JSON.stringify而后再JSON.parse。这里须要你们认真思考一下。 想一想在哪里看过它? 深拷贝? 对,就是深拷贝。对于深拷贝不理解的我这里给出它的含义: 深拷贝是复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制。 我这两天也整理过深拷贝,可是我仍是一会儿没理解为何这里要这么写。 一开始我想误差了,我一开始认为arr做为一个数字数组,对它进行深拷贝和用一个中间变量进行操做不是同样么,因而我加了这么几行代码 var temp = arr state.push(temp); 而后 动画消失了,页面变成最后排好序的样子。 这时候群里有人提醒了我一句浅复制会修改原数组。我这才根据state.push反应过来。 在sort内部,每个push都是为了保存交换后排序数组的状态,若是我用temp来代替它,那么state里面将全放着相同的最后排完序的状态。而JSON.parse(JSON.stringify(arr))对arr进行深复制,不会改动arr原数组,所以它就相似快照同样把每次排序的状态给push到state.而后配合setInterval一张一张的放映造成动画。
一个简单的排序动画其实里面也包含了很多有价值的内容。github
回过头这么一看,这不是很容易套公(算)式(法)么web
把咱们以前学的插入排序拿来改改:算法
function exchange(array, i, j) { var t = array[i]; array[i] = array[j]; array[j] = t; } var sort2 = function(numbers){ for (var i = 0; i < numbers.length; i++) { /* * 当已排序部分的当前元素大于value, * 就将当前元素向后移一位,再将前一位与value比较 */ for (var j = i; j > 0 && numbers[j] < numbers[j - 1]; j--) { // If the array is already sorted, we never enter this inner loop! exchange(numbers, j, j - 1); state.push(JSON.parse(JSON.stringify(numbers))); console.log("此时数组:" + numbers) } } }
分分钟改变更画效果。
这样咱们就完成目标中的一小步了。
而后以前考虑的单步运行和动画速度控制咱们均可以改变相应参数来完成。
不过在继续深刻以前咱们先来看看如何用canvans
来实现。
先来看效果
代码以下:
html: <div id="restart">从新生成数据并排序</div> <canvas id="canvas"><canvas> css: body { background-color: black; text-align: center; } #restart { color: white; font-family: monospace; } javascript: !function(){ var canvas = document.getElementById('canvas'); var data = []; canvas.width = window.innerWidth-30; canvas.height = window.innerHeight-35; CreateData(IntRandom(300, 100)); Render(); function Restart() { data = []; CreateData(IntRandom(300, 100)); } function CreateData(val) { for(var i = 0; i <= val; i++) data[i] = IntRandom(500, 10); } function BubbleSort() { var temp; for(var i = 0; i <= data.length-1; i++) { if(data[i] > data[i+1]) { temp = data[i]; data[i] = data[i+1]; data[i+1] = temp; } } } function Draw() { var posX = 0, posY = canvas.height; for(var i = 0; i <= data.length-1; i++) { c.fillStyle = RandomColor(i); c.fillRect(posX, canvas.height, 5, -data[i]); posY--; posX+=6; } } document.onclick = function() { Clear(); Restart(); }; function Render() { requestAnimationFrame(Render); Clear(); BubbleSort(); Draw(); } function Clear() { c.fillStyle = "black"; c.fillRect(0, 0, canvas.width, canvas.height); } function RandomColor(i) { var n = Math.random() * 360; return "hsl("+parseInt(i)+", 100%, 50%)" } function IntRandom(max, min) { return Math.floor(Math.random() * max + min); } }()
这份代码实际上是有缺陷的,不过不要紧,在下面的分析中咱们能够边看边改
1.首先是常规canvas操做,若是对canvas不上很熟悉的同窗建议把高程相关部分刷一遍。 具体操做就是,获取整个浏览器屏幕长款,减去一部分做为画布的长宽,这是为了让生成的序列不会跟浏览器边缘贴合。 CreateData()这个方法就是随机生成一堆随机高度的长方形。 BubbleSort()就是常见的冒泡排序,可是在这里咱们看到这只是一个冒泡算法,并无像以前作一系列快照。(这里就涉及到了咱们提到的缺陷) draw()方法 从屏幕左侧坐标为0的点开始,这个posY其实毫无用处,由于咱们的高度是根据以前随机生成一堆随机高度的长方形数组data来生成的。posX自加是为了保持间隔。 RandomColor() 这个方法根据高度来改变颜色。 以后是本代码的核心render() 以前咱们看到操做dom的版本,动画效果是经过setInterval(draw, 500)来实现的,那么这里的动画效果哪里来? 咱们能够看到这里用到了HTML5的API: requestAnimationFrame 它的优点在于保证跟浏览器的绘制走,若是浏览设备绘制间隔是16.7ms,那就这个间隔绘制;若是浏览设备绘制间隔是10ms, 就10ms绘制。不会存在过分绘制的问题,动画不会掉帧。 这个从咱们的效果图里也能看出动画的确很流畅。然而,这段代码是有问题的。问题在于它没有设置停止的标识,也就是它会不停刷新浏览器,时间久了将会卡住。并且细心的会发现以前咱们看到的冒泡排序它只有一层循环。render()方法靠着循环调用硬生生把这些无序数组给堆成有序了。。。 并且太流畅的动画一鼓作气,让咱们没法仔细观察排序的通过,所以咱们须要对其进行修改。
这里咱们停下来思考一下,该如何改?
想一想以前js操做dom版是怎么作的?咱们一样能够套进去。
只须要把排序算法换成以前同样的。而后把render改为以下:
var fps = 1; //每秒几帧 var lastExecution = new Date().getTime(); function Render() { if (state.length > 0) { //动画播放,没播完继续 var now = new Date().getTime(); if ((now - lastExecution) > (1000 / fps)) { Clear(); Draw(); lastExecution = new Date().getTime(); } requestAnimationFrame(Render); } }
不过须要注意的是当你想重置requestAnimationFrame的时候,须要一开始就注明var stopId = requestAnimationFrame(Render);
而后配合cancelAnimationFrame(stopId)便可暂停继续。
最终效果以下:
有了以上基础你彻底能够本身开始构建一个属于本身的排序动画。这篇就到这里。
全部代码和别的补充已经放在github
并且会不断更新。有兴趣的能够去看看并动手敲一遍。