内容


HTML5 组件

专有组件,第 2 部分

支持事件监听器,使用 CSS3 创建元素动画,并将内容注入到 DOM 中

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: HTML5 组件

敬请期待该系列的后续内容。

此内容是该系列的一部分:HTML5 组件

敬请期待该系列的后续内容。

HTML5 是一种适用于 Web 的复杂编程平台,具有强大的功能,比如 canvas元素、CSS3 和一个事件模型。HTML5 提供了您实现自己的组件所需的所有基础功能。

本文延续本系列的 第 1 期中关于滑块组件的讨论。您将学习如何合并事件监听器,如何创建滑块手柄的动画,以及如何将滑块本身注入现有的 DOM 树中。具体而言,您将学习如何:

  • 对滑块使用备用控件
  • 实现对注册和触发变更监听器的支持
  • 使用 CSS3 过渡为 HTML 元素创建动画
  • 将内容注入 DOM 树中
  • 使用元素属性自定义专有组件

备用滑块控件

图 1显示了来自 第 1 部分的使用滑块组件的简单应用程序的一个变体。该变体使用链接代替按钮,以递增和递减滑块的值。对于这些控件,可使用任何生成鼠标单击事件的元素。

图 1. 使用链接代替按钮
包含链接而不是按钮的滑块的屏幕截图
包含链接而不是按钮的滑块的屏幕截图

用户也可单击滑块的滑轨或拖动它的手柄来更改它的值。第 1 部分中的 “拖动手柄” 一节解释了滑块如何简化其拖动。在本文的 “为滑块手柄创建动画” 一节中,您将看到在用户单击滑块的滑轨时,滑块如何通过 CSS3 过渡来调整手柄的位置。

清单 1显示了 图 1中所示的应用程序的 HTML。

清单 1. 包含链接而不是按钮的滑块应用程序的 HTML
 <html> 
   <head> 
      ... 
   <head> 

   <body> 
      <div id='slider-component'> 
         <div id='controls'> 
            <a href='#'class='slider-control' id='decrement'>-</a> 
            <a href='#'class='slider-control' id='increment'>+</a> 

            <div id='slider'>  <!-- slider goes here-->  </div> 
         </div> 

         <div id='readout'>0</div> 
      </div> 
   </body> 

   <script type="text/javascript" src="lib/slider.js"></script> 
   <script type="text/javascript" src="sliderExample.js"></script> 
 </html>

清单 1中的 HTML 简单易懂。两个链接和两个滑块位于一个 controls DIV中,后者位于一个 slider-component DIV中。当用户单击某个链接时,应用程序的 JavaScript 就会处理单击事件,如 清单 2中所示。

清单 2. 链接单击事件
 var slider = new COREHTML5.Slider('black', 'cornflowerblue', 0), 
 ... 

 document.getElementById('decrement').onclick= function (e) { 
   slider.knobPercent -= 0.1; 
   slider.redraw(); 
   updateReadout(); 
 } 

 document.getElementById('increment').onclick= function (e) { 
   slider.knobPercent += 0.1; 
   slider.redraw(); 
   updateReadout(); 
 }

清单 2中的事件处理函数递增和递减滑块的值(存储在滑块的 knobPercent属性中),重新绘制滑块,并更新 readout元素。

滑块的值发生更改时,应用程序会使用附加到滑块的一个更改监听器来更新 readout元素,如 清单 3中所示。

清单 3. 滑块更改事件
 var readoutElement = document.getElementById('readout'); 

 function updateReadout() { 
   if (readoutElement) 
      readoutElement.innerHTML = slider.knobPercent.toFixed(2); 
 } 

 slider.addChangeListener(updateReadout);

请注意,readout 元素是可选的;如果该元素不存在,那么 updateReadout()方法不会执行任何操作。

下一节将会介绍滑块如何实现对更改监听器的支持。

图 1中所示的应用程序使用了滑块的 appendTo()方法,将滑块附加到 DOM:

 slider.appendTo('slider'); // Append the slider to the DOM element with an ID of slider 
 slider.draw();

本文将在 “将滑块附加到 DOM 元素” 一节中讨论该方法的实现。

支持滑块更改事件

上一节中,您已经了解了 图 1中的应用程序如何通过将变更监听器附加到滑块上,让可选的 readout 保持同步。清单 4展示了该滑块如何支持开发人员添加变更监听器,以及它如何为这些监听器触发变更事件。

清单 4. 添加变更监听器和触发事件
 COREHTML5.Slider = function { 
   ... 
   this.changeEventListeners= []; 
   ... 
 }; 

 COREHTML5.Slider.prototype = { 
   ... 

   addChangeListener: function (listenerFunction) { 
      this.changeEventListeners.push(listenerFunction); 
   }, 

   fireChangeEvent: function(e) { 
      for (var i=0; i < this.changeEventListeners.length; ++i) { 
         this.changeEventListeners[i](e); 
      } 
   }, 
   ... 
 };

每个滑块包含一个函数数组。当滑块的值发生更改时,fireChangeEvent()方法就会迭代该数组,依次调用每个函数。清单 5展示了滑块如何调用其 fireChangeEvent()方法。

清单 5. 触发更改事件
 COREHTML5.Slider.prototype = { 
   addMouseListeners: function () { 
      this.knobCanvas.addEventListener('mousemove', function(e) { 
         var mouse = null, 
             percent = null; 

         e.preventDefault(); 

         if (slider.draggingKnob) { 
            slider.deactivateKnobAnimation(); 
            
            mouse = slider.windowToCanvas(e.clientX, e.clientY); 
            percent = slider.knobPositionToPercent(mouse.x); 

            if (percent >= 0 && percent <= 1.0) { 
               slider.fireChangeEvent(e); 
               slider.erase(); 
               slider.draw(percent); 
            } 
         } 
      }, false); 

   },

用户拖动滑块的手柄时,一个附加到手柄画布的鼠标移动事件监听器将调整滑块的值,触发一个更改事件,然后擦除和重新绘制滑块。该事件监听器还在用户拖动手柄时取消激活滑块的手柄动画,以确保该动画不会妨碍用户对手柄的拖动。

现在您已知道滑块取消激活手柄动画的环境,让我们看看该动画的工作原理。

创建滑块手柄的动画

图 2展示了滑块如何实现其手柄的动画。图 2顶部的屏幕截图是在用户单击滑块的滑轨之前获取的。底部的两个屏幕截图显示了手柄朝光标位置的后续移动。(图 2非常近似实际动画;要获得完整的效果,请 下载代码并试用它。)

图 2. 创建滑块的动画
演示滑块动画的 3 个屏幕截图
演示滑块动画的 3 个屏幕截图

如第 1 部分中的 “创建和初始化滑块” 一节中所述,滑块将它的滑轨和手柄绘制到一个独立的画布元素中。如果滑块在同一个画布中绘制滑块和手柄,那么操作将会更加简单;但 CSS3 过渡仅适用于单个元素,所以手柄必须位于独立的画布中。

上一节中,您已看到,在用户拖动手柄时,滑块调用了它的 deactivateKnobAnimation()方法来取消激活手柄动画。该方法和它的反向方法(activateKnobAnimation())如 清单 6中所示。

清单 6. 激活和取消激活手柄动画
 COREHTML5.Slider.prototype = { 
   ... 

   activateKnobAnimation: function () { 
      var transitionString= "margin-left" + 
          (this.knobAnimationDuration / 1000).toFixed(1) + "s"; 

      this.knobCanvas.style.webkitTransition= transitionString; 
      this.knobCanvas.style.MozTransition= transitionString; 
      this.knobCanvas.style.OTransition= transitionString; 
      this.knobCanvas.style.transition= transitionString; 
   }, 

   deactivateKnobAnimation: function () { 
      slider.knobCanvas.style.webkitTransition= "margin-left 0s"; 
      slider.knobCanvas.style.MozTransition= "margin-left 0s"; 
      slider.knobCanvas.style.OTransition= "margin-left 0s"; 
      slider.knobCanvas.style.transition= "margin-left 0s"; 
   }, 
   ... 
 };

清单 6中所示的 activateKnobAnimation()方法以编程方式将一个 CSS3 过渡添加到手柄画布元素的 margin-leftCSS 属性中,并考虑到了过渡属性的名称本身的浏览器变体。该过渡的结果是,在更改 margin-left属性时,浏览器流畅地显示手柄画布从一个位置到另一个位置的动画。动画的持续时间使用滑块的 knobAnimationDuration属性指定,以毫秒为单位。

deactivateKnobAnimation()方法将 CSS3 过渡的持续事件更改为 0 秒,以便实际禁用动画。

当用户单击滑块的滑轨时,清单 7中所示的事件处理函数会将滑块的手柄移动到单击的位置。

清单 7. 单击滑块的滑轨
 COREHTML5.Slider.prototype = { 
   ... 
   addMouseListeners: function () { 
      ... 
   
      this.railCanvas.onmousedown= function(e) { 
         var mouse = slider.windowToCanvas(e.clientX, e.clientY), 
             startPercent, 
             endPercent; 

         e.preventDefault(); 

         startPercent = slider.knobPercent; 
         endPercent = slider.knobPositionToPercent(mouse.x); 

         slider.animatingKnob = true; 

         slider.moveKnob(mouse.x); 
         slider.trackKnobAnimation(startPercent, endPercent); 
      }; 
   }, 
   ... 
 };

滑块的 moveKnob()方法(如 清单 8中所示)通过设置手柄画布元素的 margin-leftCSS 属性来移动手柄。设置该属性可触发 CSS3 手柄动画,前提是该手柄动画已激活。

清单 8. 移动滑块的手柄
 COREHTML5.Slider.prototype = { 
   ... 

   moveKnob: function (position) { 
      this.knobCanvas.style.marginLeft= position - this.knobCanvas.width/2 + "px"; 
   }, 
   ... 
 };

除了移动滑块的手柄,滑轨画布的鼠标按下事件处理函数调用滑块的 trackKnobAnimation()方法,如 清单 7中所示。该方法(将在下一节中讨论)在整个 CSS3 过渡的相应动画中保持滑块的值与手柄一致。

跟踪 CSS3 过渡

可使用一个事件监听器检测 CSS3 过渡的结束。为此,滑块组件从滑块构造函数中调用滑块的 addKnobTransitionListener()方法,如 清单 9所示。

清单 9. 添加手柄过渡监听器
 COREHTML5.Slider = function(strokeStyle, fillStyle, knobPercent, knobAnimationDuration) { 
   ... 
   this.createDOMTree(); 
   this.addMouseListeners(); 
   this.addKnobTransitionListener(); 
 };

滑块的 addKnobTransitionListener()方法(如 清单 10中所示)向手柄画布添加一个过渡监听器,再一次考虑浏览器之间的命名区别。

清单 10. CSS 过渡监听器
 COREHTML5.Slider.prototype = { 
   ... 

   addKnobTransitionListener: function () { 
      var BROWSER_PREFIXES= [ 'webkit', 'o' ]; 

      for (var i=0; i < BROWSER_PREFIXES.length; ++i) { 
         this.knobCanvas.addEventListener( 
            BROWSER_PREFIXES[0]+ "TransitionEnd", // Everything but Mozilla

            function (e) { 
               slider.animatingKnob = false; 
            } 
         ); 
      } 

      this.knobCanvas.addEventListener("transitionend", // Mozilla
         function (e) { 
            slider.animatingKnob = false; 
         } 
      ); 
   },      
   ...

手柄过渡动画完成时,浏览器会调用 清单 10中的过渡监听器。该监听器将滑块的 animatingKnob属性设置为 false,这导致滑块终止对手柄动画的跟踪。此跟踪功能由滑块的 trackKnobAnimation()方法实现,如 清单 11中所示。

清单 11. 跟踪手柄动画
trackKnobAnimation: function (startPercent, endPercent) { 
      var count = 0, 
          KNOB_ANIMATION_FRAME_RATE = 60,  // fps 
          iterations = slider.knobAnimationDuration/1000 * KNOB_ANIMATION_FRAME_RATE + 1, 
          interval; 

      interval = setInterval( function (e) { 
         if (slider.animatingKnob) {
            slider.knobPercent = startPercent + 
                                 ((endPercent - startPercent) / iterations * count++); 

            slider.knobPercent = slider.knobPercent > 1.0 ? 1.0 : slider.knobPercent; 
            slider.knobPercent = slider.knobPercent < 0 ? 0 : slider.knobPercent; 

            slider.fireChangeEvent(e); 
         } 
         else {// Done animating knob
            clearInterval(interval); 
            count = 0; 
         }
      }, slider.knobAnimationDuration / iterations); 
   }, 
   ...   
 };

尽管可通过事件监听器检测 CSS3 过渡动画的结束,如 清单 10中所示,但无法检测该动画的中间步骤,要在手柄动画播放期间触发更改事件,必须检测动画的中间步骤。

因为无法检测 CSS3 过渡动画的步骤,所以滑块的 trackKnobAnimation()方法使用 setInterval()来估算动画的步骤,并触发每一步骤中的更改事件。

现在您已了解如何使用 CSS3 过渡实现滑块的手柄动画,让我们看看如何将滑块附加到现有的 DOM 元素上。

将滑块附加到 DOM 元素

第 1 部分中的 “绘制滑块” 讨论了未引用 CSS3 过渡的滑块组件实现。该实现会得到滑块的一个 DOM 树,如 图 3中所示。

图 3. 滑块的 DOM 树
滑块的 DOM 树的插图,包含两个画布元素和一个 DIV
滑块的 DOM 树的插图,包含两个画布元素和一个 DIV

滑块的构造函数创建了一个 DIV和两个 canvas元素(分别用于滑块的滑轨和它的手柄),并将这些画布附加到 DIV。在将 清单 1中所示的应用程序架构滑块附加到现有的 DOM 元素时,滑块将它的闭包 DOM 元素附加到该现有元素,如 图 4中所示。

图 4. 合并滑块的 DOM 树
该图演示了滑块的 DOM 树的合并
该图演示了滑块的 DOM 树的合并

滑块的构造函数调用了滑块的 createDOMTree()方法,如 清单 12中所示。

清单 12. 创建滑块的 DOM 树
 COREHTML5.Slider = function(strokeStyle, fillStyle, knobPercent, knobAnimationDuration) { 
   ... 
   this.createDOMTree(); 
   this.addMouseListeners(); 
   this.addKnobTransitionListener(); 
 };

清单 13显示了滑块的 createDOMTree()方法。

清单 13. createDOMTree()方法
 COREHTML5.Slider.prototype = { 
   ... 

   createDOMTree: function () { 
      var self = this; 

      this.domElement = document.createElement('div'); 

      this.domElement.appendChild(this.knobCanvas); 
      this.domElement.appendChild(this.railCanvas); 
   }, 
   ... 
 };

滑块调用 createDOMTree()时,它已经为手柄和滑轨创建了画布。createDOMTree()方法创建了滑块的闭包 DIV元素,然后将现有的手柄和滑轨添加到该元素。

滑块创建它的 DOM 树后,该阶段被设置为使用滑块的 appendTo()方法将滑块附加到现有的 DOM 元素上,如 清单 14中所示。

清单 14. appendTo()方法:将滑块附加到一个现有的 DOM 元素
 COREHTML5.Slider.prototype = { 
   ... 

   appendTo: function (elementName) { 
      if (typeof element === 'string') { 
         document.getElementById(element). 
            appendChild(this.domElement); 
      } 
      else { 
         element.appendChild(this.domElement); 
      } 

      this.resize(); 
   }, 
   ... 
 };

可向 appendTo()方法传递一个表示元素 ID 的字符串或元素本身;无论采用那种方式,该方法都会将滑块的闭包 DOM 元素附加到制定的元素,同时调整画布和滑块的闭包 DOM 元素。这个重新调整过程在 resize()方法和它调用的方法中执行,这些方法如 清单 15中所示。

清单 15. 调整滑块以适应其闭包元素
 COREHTML5.Slider.prototype = { 
   ... 

   setRailCanvasSize: function () { 
      var domElementParent = this.domElement.parentNode; 

      this.railCanvas.width = domElementParent.offsetWidth; 
      this.railCanvas.height = domElementParent.offsetHeight; 
   }, 

   
   setKnobCanvasSize: function () { 
      this.knobRadius = this.railCanvas.height/2 - 
                        this.railContext.lineWidth; 

      this.knobCanvas.style.width = this.knobRadius * 2 + "px"; 
      this.knobCanvas.style.height = this.knobRadius * 2 + "px"; 
      this.knobCanvas.width = this.knobRadius*2; 
      this.knobCanvas.height = this.knobRadius*2; 
   }, 

   setSliderSize: function() { 
      this.cornerRadius = (this.railCanvas.height/2 - 
                           2*this.VERTICAL_MARGIN)/2; 

      this.top = this.HORIZONTAL_MARGIN; 
      this.left = this.VERTICAL_MARGIN; 

      this.right = this.left + 
                   this.railCanvas.width - 2*this.HORIZONTAL_MARGIN; 

      this.bottom = this.top + 
                   this.railCanvas.height - 2*this.VERTICAL_MARGIN; 
   }, 

   resize: function() { 
      this.setRailCanvasSize(); 
      this.setKnobCanvasSize(); 
      this.setSliderSize(); 
   }, 
   ... 
 };

自定义组件

滑块组件(它目前已存在)很有用。但无论组件提供了多大的实用性,人们可能仍然希望能够以某种方式自定义该组件。这些自定义可能很简单(比如颜色更改),如 图 5所示,也可能很复杂(如支持垂直滑块,本文中讨论的滑块无法做到这一点)。

图 5. 一个水绿色滑块
一种新颜色(水绿色)滑块的屏幕截图
一种新颜色(水绿色)滑块的屏幕截图

清单 16展示了如何将两种属性添加到滑块组件的 DIV元素,以处理滑块的 strokefill颜色。

清单 16. 指定属性元素
 <!DOCTYPE html> 
 <html> 
   <head> 
      ... 
   </head> 
   
   <body> 
      <div id='title'>A custom slider</div> 

      <p> 
         <div id='slider-component' stroke='blue' fill='aqua'> 
            ... 
         </div> 
      </p> 
   </body> 

   <script type="text/javascript" src="lib/slider.js"></script> 
   <script type="text/javascript" src="sliderExample.js"></script> 
 </html>

应用程序的 JavaScript 使用 getAttribute()方法(如 清单 17中所示)来获取 strokefill属性的值。

清单 17. 访问元素属性
 var sliderElement = document.getElementById('slider-component'), 
    slider = new COREHTML5.Slider(sliderElement.getAttribute('stroke'), 
                                  sliderElement.getAttribute('fill'), 
                                  0), 
    ...

随后,它将使用这些值创建滑块。

结束语

系列中的下一篇文章将讨论 W3C 的 “Web 组件简介” 规范,展示如何使用 Shadow DOM、自定义元素和模板来实现滑块组件。期待下次与您相见。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development
ArticleID=941468
ArticleTitle=HTML5 组件: 专有组件,第 2 部分
publish-date=08202013