正文

当然在我们不清楚具体操作细节前我们可以先假设一下,我们能够用什么来实现。按照以前看过的排序动画我将其分为


  1. 1.Js操作Dom,再搭配简单的css
  2. 2.Canvas动画

之后在查资料的时候发现还有人用d3这个库来完成。

作为一个有(被)梦(坑)想(多)的前端,一开始就得考虑到如何实现套入多个算法,如果实现单步运行(可能的话还得有往回走的功能),如何实现动画速度的控制等等。

当然幻想那么多一下子实现是不现实的,我们得先找一个简单的例子来看看再一步步深入。

先来看下效果图

之后我们分析源码:

  1. css:
  2. #field {width:500px;height:510px;background:black;position:relative}
  3. .bar {position:absolute;bottom:0;background:orange;border:1px solid brown;width:24px}
  4. html:
  5. <div id="field">
  6. <div class="bar"></div>
  7. <div class="bar"></div>
  8. <div class="bar"></div>
  9. <div class="bar"></div>
  10. <div class="bar"></div>
  11. <div class="bar"></div>
  12. <div class="bar"></div>
  13. <div class="bar"></div>
  14. <div class="bar"></div>
  15. <div class="bar"></div>
  16. </div>
  17. javascript:
  18. !function(d){
  19. var bars = [].slice.call(d.querySelectorAll('.bar'));
  20. var arr = [8, 10, 3, 5, 6, 9, 2, 4, 7, 1];
  21. var state = [];
  22. var draw = function(){
  23. var bar, s;
  24. s = state.shift() || [];
  25. for(bar in bars){
  26. bars[bar].style.height = 25 * s[bar] + 'px';
  27. bars[bar].style.left = 25 * bar + 'px';
  28. }
  29. }
  30. var sort = function(arr){
  31. for(var i = 0; i < arr.length; i++){
  32. for(var j = 0; j < arr.length - i - 1; j++){
  33. if(arr[j] > arr[j+1]){
  34. arr[j] = arr[j] + arr[j+1];
  35. arr[j+1] = arr[j] - arr[j+1];
  36. arr[j] = arr[j] - arr[j+1];
  37. state.push(JSON.parse(JSON.stringify(arr)));
  38. }
  39. }
  40. }
  41. }
  42. sort(arr);
  43. setInterval(draw, 500);
  44. }(document)

整个流程其实很清晰,但是其中部分代码让我疑惑了一段时间,经过google和问群里的朋友,终于解惑。我们先来理清这段代码的思路

  1. 首先这个动画是将大小宽度都定死了。以及排序数组的数量需要和html结构里bar的数量一致。
  2. 1.htmlbar是长方体,它的宽是24px然后有个1pxborder,因此在代码中动态改变left的时候需要设定为25.
  3. 2.js代码中用一个匿名立即函数包裹代码。
  4. bars = [].slice.call(d.querySelectorAll('.bar'));
  5. 这段将获取的nodelist转为成一个对象数组,这样方便对其中每个bar进行单独修改样式
  6. 3.设定一个state空数组来保存每一个状态,记住这才是动画的关键。
  7. 4.state.shift()临时像将数组模拟成队列,draw函数根据其第一个出列的内容来重新排列列表,在
  8. setInterval(draw, 400)的配合下,就能形成一个动画排序。
  9. 5 sort函数和我们之前介绍的冒泡排序是一样的,只不过这里有一句
  10. state.push(JSON.parse(JSON.stringify(arr)));
  11. 这句是核心,一看是乍看是不是很奇怪,为什么要JSON.stringify然后再JSON.parse。这里需要大家认真思考一下。
  12. 想想在哪里看过它?
  13. 深拷贝?
  14. 对,就是深拷贝。对于深拷贝不理解的我这里给出它的含义:
  15. 深拷贝是复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制。
  16. 我这两天也整理过深拷贝,但是我还是一下子没理解为什么这里要这么写。
  17. 一开始我想偏差了,我一开始认为arr作为一个数字数组,对它进行深拷贝和用一个中间变量进行操作不是一样么,于是我加了这么几行代码
  18. var temp = arr
  19. state.push(temp);
  20. 然后 动画消失了,页面变成最后排好序的样子。
  21. 这时候群里有人提醒了我一句浅复制会修改原数组。我这才根据state.push反应过来。
  22. sort内部,每一个push都是为了保存交换后排序数组的状态,如果我用temp来代替它,那么state里面将全放着相同的最后排完序的状态。而JSON.parse(JSON.stringify(arr))对arr进行深复制,不会改动arr原数组,因此它就类似快照一样把每次排序的状态给pushstate.然后配合setInterval一张一张的放映形成动画。

一个简单的排序动画其实里面也包含了不少有价值的内容。

回过头这么一看,这不是很容易套公(算)式(法)么

把我们之前学的插入排序拿来改改:


  1. function exchange(array, i, j) {
  2. var t = array[i];
  3. array[i] = array[j];
  4. array[j] = t;
  5. }
  6. var sort2 = function(numbers){
  7. for (var i = 0; i < numbers.length; i++) {
  8. /*
  9. * 当已排序部分的当前元素大于value,
  10. * 就将当前元素向后移一位,再将前一位与value比较
  11. */
  12. for (var j = i; j > 0 && numbers[j] < numbers[j - 1]; j--) {
  13. // If the array is already sorted, we never enter this inner loop!
  14. exchange(numbers, j, j - 1);
  15. state.push(JSON.parse(JSON.stringify(numbers)));
  16. console.log("此时数组:" + numbers)
  17. }
  18. }
  19. }

分分钟改变动画效果。

这样我们就完成目标中的一小步了。

然后之前考虑的单步运行和动画速度控制我们都可以改变相应参数来完成。

不过在继续深入之前我们先来看看如何用canvans来实现。

先来看效果

代码如下:


  1. html:
  2. <div id="restart">重新生成数据并排序</div>
  3. <canvas id="canvas"><canvas>
  4. css:
  5. body {
  6. background-color: black;
  7. text-align: center;
  8. }
  9. #restart {
  10. color: white;
  11. font-family: monospace;
  12. }
  13. javascript:
  14. !function(){
  15. var canvas = document.getElementById('canvas');
  16. var data = [];
  17. canvas.width = window.innerWidth-30;
  18. canvas.height = window.innerHeight-35;
  19. CreateData(IntRandom(300, 100));
  20. Render();
  21. function Restart() {
  22. data = [];
  23. CreateData(IntRandom(300, 100));
  24. }
  25. function CreateData(val) {
  26. for(var i = 0; i <= val; i++)
  27. data[i] = IntRandom(500, 10);
  28. }
  29. function BubbleSort() {
  30. var temp;
  31. for(var i = 0; i <= data.length-1; i++) {
  32. if(data[i] > data[i+1]) {
  33. temp = data[i];
  34. data[i] = data[i+1];
  35. data[i+1] = temp;
  36. }
  37. }
  38. }
  39. function Draw() {
  40. var posX = 0,
  41. posY = canvas.height;
  42. for(var i = 0; i <= data.length-1; i++) {
  43. c.fillStyle = RandomColor(i);
  44. c.fillRect(posX, canvas.height, 5, -data[i]);
  45. posY--;
  46. posX+=6;
  47. }
  48. }
  49. document.onclick = function() {
  50. Clear();
  51. Restart();
  52. };
  53. function Render() {
  54. requestAnimationFrame(Render);
  55. Clear();
  56. BubbleSort();
  57. Draw();
  58. }
  59. function Clear() {
  60. c.fillStyle = "black";
  61. c.fillRect(0, 0, canvas.width, canvas.height);
  62. }
  63. function RandomColor(i) {
  64. var n = Math.random() * 360;
  65. return "hsl("+parseInt(i)+", 100%, 50%)"
  66. }
  67. function IntRandom(max, min) {
  68. return Math.floor(Math.random() * max + min);
  69. }
  70. }()

这份代码其实是有缺陷的,不过没关系,在下面的分析中我们可以边看边改


  1. 1.首先是常规canvas操作,如果对canvas不上很熟悉的同学建议把高程相关部分刷一遍。
  2. 具体操作就是,获取整个浏览器屏幕长款,减去一部分作为画布的长宽,这是为了让生成的序列不会跟浏览器边缘贴合。
  3. CreateData()这个方法就是随机生成一堆随机高度的长方形。
  4. BubbleSort()就是常见的冒泡排序,但是在这里我们看到这只是一个冒泡算法,并没有像之前做一系列快照。(这里就涉及到了我们提到的缺陷)
  5. draw()方法 从屏幕左侧坐标为0的点开始,这个posY其实毫无用处,因为我们的高度是根据之前随机生成一堆随机高度的长方形数组data来生成的。posX自加是为了保持间隔。
  6. RandomColor() 这个方法根据高度来改变颜色。
  7. 之后是本代码的核心render()
  8. 之前我们看到操作dom的版本,动画效果是通过setInterval(draw, 500)来实现的,那么这里的动画效果哪里来?
  9. 我们可以看到这里用到了HTML5API
  10. requestAnimationFrame
  11. 它的优势在于保证跟浏览器的绘制走,如果浏览设备绘制间隔是16.7ms,那就这个间隔绘制;如果浏览设备绘制间隔是10ms, 10ms绘制。不会存在过度绘制的问题,动画不会掉帧。
  12. 这个从我们的效果图里也能看出动画的确很流畅。然而,这段代码是有问题的。问题在于它没有设置中止的标识,也就是它会不停刷新浏览器,时间久了将会卡住。而且细心的会发现之前我们看到的冒泡排序它只有一层循环。render()方法靠着循环调用硬生生把这些无序数组给堆成有序了。。。
  13. 而且太流畅的动画一气呵成,让我们无法仔细观察排序的经过,因此我们需要对其进行修改。

这里我们停下来思考一下,该如何改?

想想之前js操作dom版是怎么做的?我们同样可以套进去。
只需要把排序算法换成之前一样的。然后把render改成如下:


  1. var fps = 1; //每秒几帧
  2. var lastExecution = new Date().getTime();
  3. function Render() {
  4. if (state.length > 0) { //动画播放,没播完继续
  5. var now = new Date().getTime();
  6. if ((now - lastExecution) > (1000 / fps)) {
  7. Clear();
  8. Draw();
  9. lastExecution = new Date().getTime();
  10. }
  11. requestAnimationFrame(Render);
  12. }
  13. }

不过需要注意的是当你想重置requestAnimationFrame的时候,需要一开始就注明var stopId = requestAnimationFrame(Render);

然后配合cancelAnimationFrame(stopId)即可暂停继续。

最终效果如下:

有了以上基础你完全可以自己开始构建一个属于自己的排序动画。这篇就到这里。

结尾

所有代码和别的补充已经放在github
而且会不断更新。有兴趣的可以去看看并动手敲一遍。

资料

visualgo.net的排序动画

David Galles教授Canvas+JS实现排序动画

前端开发——让算法"动"起来的更多相关文章

  1. 前端开发周报: CSS 布局方式方式与JavaScript数据结构和算法

    前端开发周报:CSS 布局方式与JavaScript动画库 1.常见 CSS 布局方式详见: 一些常见的 CSS 布局方式梳理,涉及 Flex 布局.Grid 布局.圣杯布局.双飞翼布局等.http: ...

  2. Front End Developer Questions 前端开发人员问题(二)

    问题来源:http://markyun.github.io/2015/Front-end-Developer-Questions/ 二.CSS 1.介绍一下标准的CSS的盒子模型?与低版本IE的盒子模 ...

  3. 淘宝前端工程师:国内WEB前端开发十日谈

    一直想写这篇"十日谈",聊聊我对Web前端开发的体会,顺便解答下周围不少人的困惑和迷惘.我不打算聊太多技术,我想,通过技术的历练,得到的反思应当更重要. 我一直认为自己是" ...

  4. Web前端开发十日谈

    =========================================================================== 原文章: http://kb.cnblogs.c ...

  5. 绝对精品推荐做前端的看下:Web前端开发体会十日谈

    20151208感悟: 前端人的角度来看的话,感觉像是阅读一个大牛前端的全部武功的一个秘籍说明,里面的思想高价值蛋白真是太多太多,推荐看. Web前端开发体会十日谈 一直想写这篇“十日谈”,聊聊我对W ...

  6. 2022年Web前端开发流程和学习路线(详尽版)

    前言 前端侧重于人机交互和用户体验,后端侧重于业务逻辑和大规模数据处理.理论上,面向用户的产品里,所有问题(包括产品.设计.后端.甚至看不见的问题)的表现形式,都会暴露在前端,而只有部分问题(数据问题 ...

  7. 3. web前端开发分享-css,js提高篇

    一. css基础知识掌握之后(个人的标准是:弄清块元素与内联元素的区别,弄清float的应用场景,弄清position[pə'zɪʃən] 下五个属性static['stætɪk],relative[ ...

  8. web前端开发学习内容

    应该 具备的 知识技能 :懂web标准,熟练手写 xhtml css3 并符合 符合w3c标准                       代码能 兼容主流浏览器.ie6.7.8.9 ff 等.    ...

  9. Web前端开发工程师养成计划【转载】

    Web前端开发工程师养成计划(入门篇) 最原始的忠告:这个世界上有想法的人很多,但是有想法又能实现它的人太少! 首先要感谢伟大的Web2.0概念.产品概念.用户体验概念.jQuery插件,是它们在中国 ...

随机推荐

  1. Eclipse使用github并开启命令行

    1. 安装EGit插件 2. 导入git项目 选择Import: 选择“Clone URI” 输入想要导入的git项目地址和用户名密码: 选择代码分支: 一路点击next完成导入github项目即可. ...

  2. RBAC(基于角色的访问控制)用户权限管理数据库设计

    RBAC(Role-Based Access Control,基于角色的访问控制),就是用户通过角色与权限进行关联.简单地说,一个用户拥有若干角色,每一个角色拥有若干权限.这样,就构造成“用户-角色- ...

  3. cookie和session的详解和区别

    会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话.常用的会话跟踪技术是Cookie与Session.Cookie通过在客户端记录信息确定用户身份,Session通过在服务器端 ...

  4. vue-router实现原理

    vue-router实现原理 近期面试,遇到关于vue-router实现原理的问题,在查阅了相关资料后,根据自己理解,来记录下.我们知道vue-router是vue的核心插件,而当前vue项目一般都是 ...

  5. electron builder 打包多个第三方依赖的软件

    背景 在实际的开发过程中,我们最后打包生成的exe.会依赖一些第三方的软件,或者说是一些系统的环境,比如 .net framework vc++ 等,这些环境不能依赖客户的环境,所以最好的做法是在打包 ...

  6. ansible的基本学习-安装和简单的配置测试

    当下有许多的运维自动化工具(配置管理),例如:ansible.saltstack.puppet.fabric等 ansible 是一种集成it系统的配置管理.应用部署.执行特定任务的开源平台,是ans ...

  7. (转) Apache Shiro 使用手册(三)Shiro 授权

    解惑之处: 使用冒号分隔的权限表达式是org.apache.shiro.authz.permission.WildcardPermission 默认支持的实现方式. 这里分别代表了 资源类型:操作:资 ...

  8. 【bzoj 4059】Non-boring sequences

    这题的重点不在于代码,而在于复杂度分析…… 首先我们肯定会写 $n^2$ 暴力,就是每次暴力扫 $[l,r]$ 区间,找到任意一个在此区间中只出现过一次的数.设其下标为 $mid$,显然在这个区间中任 ...

  9. pytest重复执行

    安装 pip install pytest-repeat 命令: pytest --count=10 test_file.py

  10. shell更改xml中的指定值

    sed -i 's;<id>.*<\/id>;<id>新内容<\/id>;g'  your.xml