7.7 动画插件

整个tile插件基本上就算完成了,但是还差一个动画部分。Win8上经常有一些这样的效果:磁贴片里的内容有规律地滚动,比如向上逐条显示新闻或邮件。本节,我们就将一步一步实现这些效果。

先分析一下该插件都需要设置什么内容。由于该插件和旋转轮播效果类似,所以通过回顾轮播插件,可以分析到,需要以下几个方面的内容:

❑可以在上下左右4个方向进行秩序滚动,如果能有左右(或上下)弹球式滚动就更好了,要求能在声明式用法里设置滚动效果,比如slideLeft、slideUp等。

❑要求能设置滚动时间、滚动以后的暂停时间。

❑是否能支持更炫的动画特效。

经过考虑,我们发现前两点比较容易实现,第3点的更炫特效由于已经偏离了本书的内容,所以只能利用第三方的jQuery的easing插件进行调用,也就是暴露一个接口,让开发人员自行选择。

首先要考虑一下,多个滚动的内容如何定义。前面我们在容器内定义了一个主区域内容tile-content,可以在里面连续放置多个充满tile-content元素的子元素用于滚动。可是,再设置一个层级的子元素,又要处理很多相关的CSS样式,这里,就大胆使用多个tile-content元素试试,此时只需要显示一个tile-content,其他的tile-content都默认隐藏即可,因为其他的tile-content可以通过tile动画插件来显示。具体结构如下:

  1. <div class="tile double bg-amber" data-toggle="tile" data-direction="slideLeftRight"
  2. data-period="700" data-duration="3000" data-easing="doubleSqrt">
  3. <div class="tile-content bg-red">a区</div>
  4. <div class="tile-content bg-green">b区</div>
  5. <div class="tile-content bg-orange ">c 区</div>
  6. <div class="tile-content bg-grayDark">d区 </div>
  7. </div>

这里,除了在tile元素放置多个tile-content子容器以外,还定义了5个参数,分别表示:插件标识、滚动方向、单个tile-content滚动时间、滚动结束后的停留时间、动画过渡效果。

此时,我们需要解决一个问题,那就是在一个tile里,默认只能显示一个tile-content容器,这个内容可以使用CSS来控制,默认设置只显示第一个tile-content容器(此时,也不影响原先定义的CSS样式)。具体代码如下:

  1. .tile .tile-content { display: none;}
  2. .tile .tile-content:first-child { display: block;}

声明式布局定义好以后,就开始定义插件内容。

步骤1 立即调用的函数。

  1. // 1.定义立即调用的函数
  2. +function ($) {
  3. "use strict"; // 使用严格模式 ES5支持
  4. // 后续步骤
  5. // 2.tile插件类及原型方法的定义
  6. // 3.在jQuery上定义tile插件,并重设插件构造器
  7. // 并重设插件构造器,可以通过该属性获取插件的真实类函数
  8. // 4. 防冲突处理
  9. // 5. 绑定触发事件
  10. }(window.jQuery);

步骤2 插件核心代码。定义基础的Tile类函数和相关原型方法,Tile类函数主要是接收options值、设置各种默认值等。

Tile类函数的定义代码如下:

  1. // Tile类函数定义
  2. var Tile = function (element, options) {
  3. this.$element = $(element); // 容器元素
  4. this.options = options;
  5. // 插件运行参数,根据初始化代码,优先级最高的是所单击元素上的data-属性,
  6. // 然后是容器上的data-属性,最后才是默认值
  7.  
  8. this.frames = this.$element.children(".tile-content");
  9. // 查找有多少个主区域需要滚动
  10. this.currentIndex = 0; // 当前所显示主区域的索引
  11. this.interval = 0; // 触发设置
  12. this.size = { // 获取当前磁贴的高度和宽度
  13. 'width': this.$element.width(),
  14. 'height': this.$element.height()
  15. };
  16.  
  17. // 确保默认的参数都是正常传值,如果是undefined,就使用默认值
  18. if (this.options.direction == undefined) { this.options.direction =
  19. Tile.DEFAULTS.direction; }
  20. if (this.options.period == undefined) { this.options.period =
  21. Tile.DEFAULTS.period; }
  22. if (this.options.duration == undefined) { this.options.duration =
  23. Tile.DEFAULTS.duration; }
  24. if (this.options.easing == undefined) { this.options.easing =
  25. Tile.DEFAULTS.easing; }
  26.  
  27. // 定义一个默认的动画过渡效果,可以使用jQuery的easing插件
  28. $.easing.doubleSqrt = function (t) { return Math.sqrt(Math.sqrt(t)); };
  29. }
  30.  
  31. // 默认值定义
  32. Tile.DEFAULTS = {
  33. direction: 'slideLeft', // 默认滚动方向
  34. period: 3000, // 默认暂停间隔
  35. duration: 700, // 默认滚动时间
  36. easing: 'doubleSqrt' // 默认动画过渡效果
  37. }

关于原型方法,能公开进行调用的,我们只暴露start和pause,其他的一些原型方法都是用于处理滚动效果的,先来看一下核心摘要代码:

  1. // 启动执行动画
  2. Tile.prototype.start = function () {
  3. var that = this;
  4. this.interval = setInterval(function () {
  5. that.animate();
  6. }, that.options.period);
  7. }
  8.  
  9. // 暂停动画
  10. Tile.prototype.pause = function () {
  11. var that = this;
  12. clearInterval(that.interval);
  13. }
  14.  
  15. // 动画处理入口,再分别调用各自方向的动画处理效果
  16. Tile.prototype.animate = function () {
  17. var that = this;
  18. var currentFrame = this.frames[this.currentIndex], nextFrame;
  19. this.currentIndex += 1;
  20. if (this.currentIndex >= this.frames.length) this.currentIndex = 0;
  21. nextFrame = this.frames[this.currentIndex];
  22.  
  23. // 根据滚动方向,分别调用相应的内部方法,参数是:
  24. // 当前要滚动的tile-content、下一个要滚动的tile-content
  25. switch (this.options.direction) {
  26. case 'slideLeft': this.slideLeft(currentFrame, nextFrame); break;
  27. case 'slideRight': this.slideRight(currentFrame, nextFrame); break;
  28. case 'slideDown': this.slideDown(currentFrame, nextFrame); break;
  29. case 'slideUpDown': this.slideUpDown(currentFrame, nextFrame); break;
  30. case 'slideLeftRight': this.slideLeftRight(currentFrame, nextFrame); break;
  31. default: this.slideUp(currentFrame, nextFrame);
  32. }
  33. }
  34.  
  35. // 左右来回滚动效果
  36. Tile.prototype.slideLeftRight = function (currentFrame, nextFrame) {…}
  37. // 上下来回滚动效果
  38. Tile.prototype.slideUpDown = function (currentFrame, nextFrame) {…}
  39. // 一直向上滚动效果
  40. Tile.prototype.slideUp = function (currentFrame, nextFrame) {…}
  41. // 一直向下滚动效果
  42. Tile.prototype.slideDown = function (currentFrame, nextFrame) {…}
  43. // 一直向左滚动效果
  44. Tile.prototype.slideLeft = function (currentFrame, nextFrame) {…}
  45. // 一直向右滚动效果
  46. Tile.prototype.slideRight = function (currentFrame, nextFrame) {…}

这里,在start里使用了setInterval函数来触发动画执行函数,而在pause里使用了clearInterval函数清除动画执行函数。其他方面都很好理解了,看注释即可明白。

而关于4个方向的滚动效果则更简单,主要就是利用jQuery的animate函数将原有的tile-content移出后,再将下一个tile-content移入并显示。代码如下:

  1. // 左右来回滚动效果
  2. Tile.prototype.slideLeftRight = function (currentFrame, nextFrame) {
  3. if (this.currentIndex % 2 == 1) // 用奇偶数来决定滚动方向
  4. this.slideLeft(currentFrame, nextFrame);
  5. else
  6. this.slideRight(currentFrame, nextFrame);
  7. }
  8.  
  9. // 上下来回滚动效果
  10. Tile.prototype.slideUpDown = function (currentFrame, nextFrame) {
  11. if (this.currentIndex % 2 == 1) // 用奇偶数来决定滚动方向
  12. this.slideUp(currentFrame, nextFrame);
  13. else
  14. this.slideDown(currentFrame, nextFrame);
  15. }
  16.  
  17. // 一直向上滚动效果
  18. Tile.prototype.slideUp = function (currentFrame, nextFrame) {
  19. var move = this.size.height;
  20. var options = {
  21. 'duration': this.options.duration,
  22. 'easing': this.options.easing
  23. };
  24.  
  25. $(currentFrame).animate({ top: -move }, options);
  26. $(nextFrame)
  27. .css({ top: move })
  28. .show()
  29. .animate({ top: 0 }, options);
  30. }
  31.  
  32. // 一直向下滚动效果
  33. Tile.prototype.slideDown = function (currentFrame, nextFrame) {
  34. var move = this.size.height;
  35. var options = {
  36. 'duration': this.options.duration,
  37. 'easing': this.options.easing
  38. };
  39.  
  40. $(currentFrame).animate({ top: move }, options);
  41. $(nextFrame)
  42. .css({ top: -move })
  43. .show()
  44. .animate({ top: 0 }, options);
  45. }
  46.  
  47. // 一直向左滚动效果
  48. Tile.prototype.slideLeft = function (currentFrame, nextFrame) {
  49. var move = this.size.width;
  50. var options = {
  51. 'duration': this.options.duration,
  52. 'easing': this.options.easing
  53. };
  54.  
  55. $(currentFrame).animate({ left: -move }, options);
  56. $(nextFrame)
  57. .css({ left: move })
  58. .show()
  59. .animate({ left: 0 }, options);
  60. }
  61.  
  62. // 一直向右滚动效果
  63. Tile.prototype.slideRight = function (currentFrame, nextFrame) {
  64. var move = this.size.width;
  65. var options = {
  66. 'duration': this.options.duration,
  67. 'easing': this.options.easing
  68. };
  69.  
  70. $(currentFrame).animate({ left: move }, options);
  71. $(nextFrame)
  72. .css({ left: -move })
  73. .show()
  74. .animate({ left: 0 }, options);
  75. }

步骤3 jQuery插件定义。在jQuery上定义$.fn.tile插件,有点特殊的代码是options参数的收集和合并,主要收集了3部分:插件的默认参数Defaults、modal元素上的data-属性、执行插件时候传入的option对象。三者的优先级依次升高。源码如下:

  1. // Tile插件定义
  2. var old = $.fn.tile;
  3. // 保留其他库的$.fn.tile代码(如果定义的话),以便在noConflict之后,可以继续使用该老代码
  4. $.fn.tile = function (option) {
  5. return this.each(function () { // 遍历所有符合规则的元素
  6. var $this = $(this) // 当前触发元素的jQuery对象
  7. var data = $this.data('bs.tile')
  8. // 获取自定义属性data-bs.tile的值(其实是tile实例)
  9.  
  10. var options = $.extend({}, Tile.DEFAULTS, $this.data(),
  11. typeof option == 'object' && option)
  12. // 合并参数,优先级依次递增
  13.  
  14. if (!data) $this.data('bs.tile', (data = new Tile(this, options)))
  15. // 如果没有tile实例,就初始化一个,并传入this和参数
  16.  
  17. option === 'pause' ? data.pause() : data.start();
  18. })
  19. }

步骤4 防冲突处理。防冲突处理的源码如下:

  1. $.fn.tile.Constructor = Tile; // 并重设插件构造器,可以通过该属性获取插件的真实类函数
  2. // Tile 防冲突
  3. $.fn.tile.noConflict = function () {
  4. $.fn.tile = old
  5. return this
  6. }

上述代码非常简单了,可以像其他插件一样接收option参数(参数里可以设置滚动方向、时间等)。唯一一个需要注意的就是,默认的执行方式只提供了start和pause,而且,只有在option参数是pause字符串的时候才执行pause方法。否则一律执行start方法。大家在进行JavaScript调用的时候需要注意一下。

步骤5 绑定触发事件。DATA-API绑定触发事件则更简单了,由于不需要绑定相关的click事件,只需要在data-toggle="tile"属性的tile元素上执行tile插件即可。

  1. // Tile DATA-API
  2. // 由于不需要click相关的时间,所以这里只是简单触发tile插件
  3. $(window).on('load', function () {
  4. $('[data-toggle="tile"]').each(function () { // 遍历所有符合规则的元素
  5. $(this).tile(); // 实例化插件以便自动运行
  6. })
  7. })

一般情况下,该插件使用声明式用法即可。关于插件的JavaScript用法,我们来举两个例子。代码如下:

  1. // 触发所有的tile磁贴
  2. $(function () {
  3. $(".tile").tile();
  4. });
  5. // 对特定磁贴启用特殊参数
  6. $(function () {
  7. $("#firstTile").tile({
  8. direction: "slideRight",
  9. duration: 700
  10. });
  11. });

如果需要在其他元素上作用单击事件,来手动触发tile插件的滚动效果,则利用现有插件的实例,在停止的时候传入pause参数即可。代码如下:

  1. // 通过开始和暂停按钮手动触发磁贴动画
  2. $(function () {
  3. $("#btnStart").click(function () {
  4. $("#firstTile").tile({
  5. direction: "slideRight",
  6. duration: 700
  7. });
  8. });
  9. $("#btnPause").click(function () {
  10. $("#firstTile").tile('pause');
  11. });
  12. });