内容


HTML5 组件

特殊组件,第 1 部分

理解 HTML5 组件模型并开始实现特殊组件

Comments

系列内容:

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

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

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

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

组件模型对任何编程平台来说都是一个重要组成部分,因为它们提供一组标准 UI 元素和基础架构来构建更多的东西。

HTML5 是一个较新的技术,拥有一组更为年轻的规范,旨在标准化 HTML5 组件的实现以及与其他组件的集成。HTML5 组件模型目前是通过三个规范定义的:

  • “Introduction to Web Components”
  • “Shadow DOM”
  • “HTML Templates”

这三个规范中的功能包括:封装组件 DOM 树(Shadow DOM),为组件网页设计者在其 HTML 页面中使用的组件创建自定义标签(自定义 HTML 元素),以声明的方式指定 Shadow DOM 和插入点(HTML Templates)。(参阅 “HTML5 组件规范” 侧边栏,获取有关的更多详细信息。)

但是,要实现组件,首先需要一个完全不同的技能组合,包括能够创建具有吸引力的图形、对 HTML 元素进行分层来支持插入交互,以及支持事件监听。本 系列文章 涵盖了这些 HTML5 组件开发的所有方面:

  • 特殊组件
  • Shadow DOM、模板和自定义元素
  • Mozilla 的 X-Tag

本文和本系列的下一期都将向您介绍如何从零开始实现特殊的 HTML5 组件,不需要任何新规范支持。稍后几期将介绍相关规范,并介绍如何将 Shadow DOM、自定义元素以及模板合并到您的 HTML5 组件构建系统中。

更具体地说,在本文中,您将学习如何:

  • 实现特殊的 HTML5 组件
  • 使用 画布 元素绘制具有吸引力的图形
  • 对画布元素进行分层来实现特殊效果
  • 实现 HTML 元素的实时拖拽

构建其中一个 HTML5 组件

显示了一个图像处理组件,该组件由其他几个较为常见的组件构成:图标、工具栏、滑块和一个图像显示区。

一个图像处理组件
图像查看器组件的两个截图:上面的截图显示了大小调整滑块的使用,下面的截图显示了使用旋转滑块旋转后的图像。
图像查看器组件的两个截图:上面的截图显示了大小调整滑块的使用,下面的截图显示了使用旋转滑块旋转后的图像。

工具栏上的三个图标(resize、rotate 和 brightness)与滑块相关。当您单击其中一个图标时,图像处理组件会在工具栏的下方显示一个滑块和一组相关按钮。 中上面的截图显示了调整滑块大小的使用;下面的截图显示了使用旋转滑块旋转后的图像。

滑块组件

滑块由一个滑轨和一个旋钮构成;可以通过在滑轨上移动旋钮来改变滑块的值。旋钮的位置决定滑块的值(反之亦然,如果以编程方式设置值并重绘滑块)。该值(介于 0.0 和 1.0 之间)表示旋钮的左侧在滑轨上的百分比;例如,当旋钮的左侧位于滑轨的四分之三处时,滑块的值为 0.75。

是一个说明如何使用滑块的简单应用程序。除了滑块之外,该应用程序还包括两个按钮,单击这两个按钮可以将滑块旋钮向右(+)或向左(—)逐步移动。该应用程序也在滑块下方提供了一个示值读数,显示滑块当前值。参阅 参考资料,获取运行该应用程序的链接;还可以从 下载 部分下载源代码。

一个滑块
滑块的截图
滑块的截图

用户可以采用三种方式移动滑块旋钮:

  • 单击一次按钮滑块值增加(+)或减少(-)10%。
  • 拖拽旋钮。
  • 在滑轨上旋钮之外的任何地方单击,将旋钮移动到单击位置。

如果滑块的值是以程序方式设置的,当您激活加号(+)或减号(-)按钮或者支持 在滑轨上旋钮之外的任何位置单击时,通常可以这样做,滑块的运动是通过一个带有减缓功能的 CSS 转换激活的,该功能在旋钮接近最终目的地时减缓旋钮速度。滑块组件使用 JavaScript 设置转换属性。

要使用滑块,需要先实例化滑块,然后将其附加到现有 DOM 元素中,如 所示。

创建一个滑块
var slider = new COREHTML5.Slider( // All of the following arguments are optional
   'navy',           // Stroke color
   'cornflowerblue', // Fill color
   0.5,              // Initial slider value
   500);             // Knob animation duration in milliseconds
...

slider.appendTo('someDiv');  // Appends the slider to a DOM element with the ID someDiv

滑块的 appendTo() 方法调整了滑块的大小,以便通过伸缩来适应附加滑块的元素。

滑块的特点是:

  • 使用一个 appendTo() 方法附加到 DOM 元素中
  • 自动调整大小来适应附加滑块的 DOM 元素
  • 使用 addChangeListener() 方法注册变更事件监听器
  • 当滑块的旋钮移动时,触发变更事件来更改监听程序
  • 当用户在滑轨上单击时,使用一个 CSS 转换激活旋钮

滑块是 COREHTML5.Slider 的实例,可避免域名空间冲突。想象一下,如果有人使用一个更明显的名称(例如 Slider)实现一个滑块(这并不是没有可能),那么有可能会使用完全相同的名称来代替所有的全局对象。但是,不太可能有人会使用自己的 COREHTML5.Slider

COREHTML5.Slider 构造函数参数都是可选的;所有这些值都有合理的默认值。 列出了关键 COREHTML5.Slider 方法。

关键滑块方法
方法描述
appendTo(elementId)将滑块的 DOM 元素附加到某个元素,该元素的 ID 与传递给该方法的值相匹配。
addChangeListener(listenerFunction)向滑块添加一个变更监听器函数。当滑块的旋钮位置发生改变时,滑块将会触发一个事件来监听所有变更监听器。
draw()绘制滑块的滑轨和旋钮。
erase()擦除滑块。
redraw()擦除然后重绘滑块。

仅列出了开发人员用于操作滑块的外部方法。COREHTML5.Slider 对象也有很多内部使用的方法,比如 initializeStrokeAndFillStyles()createKnobCanvas()

开发人员通过滑块的 knobPercent 属性访问滑块的值。

使用滑块

显示了 中展示的应用程序的 HTML。

滑块示例的 HTML
<!DOCTYPE html>
<html>
   <head>
      <title>Ad hoc components</title>

      <style>
         body {
            background: rgb(240,240,240);
         }
         
         #title {
            font: 18px Arial;
         }

         #slider-component {
            width: 400px;
            text-align: center;
         }

         #buttons {
            display: inline;
            font: 14px Arial;
         }
         
         #readout {
            margin-left: 25%;
            color: blue;
            font: 18px Arial;
            text-shadow: 2px 2px 2px rgb(255,255,255);
         }
         
         #slider {
            width: 75%;
            height: 30px;
            float: right;
         }

         .slider-button {
            background: rgba(100, 100, 100, 0.2);
            font: 24px Arial;
            font-weight: 1;
            border-radius: 4px;
            border: 1px solid rgba(100, 100, 180, 0.7);
            background: rgba(255, 255, 0, 0.2);
            box-shadow: 1px 1px 2px rgba(0,0,0,0.5);
            cursor: pointer;
            margin: 0px;
         }
      </style>
   </head>
   
   <body>
      <div id='title'>A custom slider</div>

      <p>
         <div id='slider-component'>
            <div id='controls'>
              <div id='buttons'>
                 <input type='button' class='slider-button'
                          id='minus-button' value='&ndash;'/>

                 <input type='button' class='slider-button' 
                          id='plus-button' value='&plus;'/>
              </div>

              <div id='slider'></div>
            </div>

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

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

</html>

中的 HTML 创建了 中显示的 DOM 树。

滑块示例的 DOM 树
滑块示例的 DOM 树包含一个带有滑块组件 ID 的 DIV,该滑块组件包含两个其他 DIV,一个带有控件 ID,另一带有按钮 ID。该按钮 DIV 有两个输入元素,每个类型的按钮都带有一类滑块按钮。一个按钮带有减号按钮 ID,另一个带有加号按钮 ID。最后,滑块组件 DIV 包含一个带有滑块 ID 的空 DIV。
滑块示例的 DOM 树包含一个带有滑块组件 ID 的 DIV,该滑块组件包含两个其他 DIV,一个带有控件 ID,另一带有按钮 ID。该按钮 DIV 有两个输入元素,每个类型的按钮都带有一类滑块按钮。一个按钮带有减号按钮 ID,另一个带有加号按钮 ID。最后,滑块组件 DIV 包含一个带有滑块 ID 的空 DIV。

中的 HTML 和 CSS 都比较简单。HTML 引用了两个脚本,一个用于滑块,另一个用于应用程序本身。应用程序脚本如 所示。

滑块示例的 JavaScript 脚本
var slider = new COREHTML5.Slider('black', 'cornflowerblue', 0),
    readoutElement = document.getElementById('readout');

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

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

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

slider.addChangeListener(updateReadout);

slider.appendTo('slider');
slider.draw();

顶部的 JavaScript 脚本中,应用程序创建了一个滑块,黑色描边和浅蓝色填充样式,初始值为零。底部的 JavaScript 脚本表示应用程序将滑块附加到 ID 为 slider 的 DOM 中。中间的 JavaScript 脚本定义了三个事件句柄,对按钮点击和滑块值变更作出响应。

应用程序将 onclick 事件句柄添加到加号(+)和减号(-)按钮,调整滑块值(knobPercent),重绘滑块并更新示值读数。

应用程序还将一个变更监听器添加到该滑块,更新应用程序的示值读数来反映滑块的新值。组件通常会提供一个机制来注册事件监听器,并触发事件来监听这些监听器,滑块组件也不例外。

现在,您已经了解了如何使用一个滑块,接下来,我们将了解滑块组件的实现。

创建和初始化滑块

显示滑块构造函数的 JavaScript 代码。

滑块构造函数
COREHTML5 = COREHTML5 || {};

COREHTML5.Slider = function(strokeStyle, fillStyle,
                            knobPercent, knobAnimationDuration) {
   knobPercent = knobPercent || 0;
   knobAnimationDuration = knobAnimationDuration || 1000; // milliseconds

   this.railCanvas = document.createElement('canvas');
   this.railContext = this.railCanvas.getContext('2d');
   this.changeEventListeners = [];

   this.initializeConstants();
   this.initializeStrokeAndFillStyles(strokeStyle, fillStyle);
   this.initializeKnob(knobPercent, knobAnimationDuration);

   this.createDOMTree();
   this.addMouseListeners();
   this.addKnobTransitionListeners();

   return this;
}

第一行使用一个常见的 JavaScript 脚本来确保存在全局对象,在本例中,该对象是 COREHTML5。(如果确实存在该对象,则将其分配给其本身,否则,将其分配给一个空对象。)

COREHTML5.Slider 构造函数带 4 个可选参数:描边颜色、填充颜色、初始滑块值和旋钮动画周期(以毫秒为单位)。knobPercent 代表滑块的值。

构造函数创建了一个画布,即 railCanvas,该画布包含滑块的滑轨。构造函数还创建了第二个画布,即 knobCanvas,该画布带有 createKnobCanvas()(如 所示),可在 中通过 initializeKnob() 调用它。最后,构造函数创建了滑块的 DOM 树,并向滑块添加了监听程序。

前三个通过滑块的构造函数调用的滑块方法(initializeConstants()initializeStrokeAndFillStyles()initializeKnob())如 所示。

滑块的初始化方法
COREHTML5.Slider.prototype = {
   initializeConstants: function () {
      this.SHADOW_COLOR = 'rgba(100, 100, 100, 0.4)';
      this.SHADOW_OFFSET_X = 3;
      this.SHADOW_OFFSET_Y = 3;
      this.SHADOW_BLUR = 4;

      this.KNOB_SHADOW_COLOR = 'rgba(255,255,0,0.8)';
      this.KNOB_SHADOW_OFFSET_X = 1;
      this.KNOB_SHADOW_OFFSET_Y = 1;
      this.KNOB_SHADOW_BLUR = 0;

      this.KNOB_FILL_STYLE = 'rgba(255, 255, 255, 0.45)';
      this.KNOB_STROKE_STYLE = 'rgb(0, 0, 80)';

      this.HORIZONTAL_MARGIN = 2.5 * this.SHADOW_OFFSET_X;

      this.VERTICAL_MARGIN = 2.5 * this.SHADOW_OFFSET_Y;

      this.DEFAULT_STROKE_STYLE = 'gray';
      this.DEFAULT_FILL_STYLE = 'skyblue';
   },

   initializeStrokeAndFillStyles: function(strokeStyle, fillStyle) {
      this.strokeStyle = strokeStyle ? strokeStyle : this.DEFAULT_STROKE_STYLE;
      this.fillStyle = fillStyle ? fillStyle : this.DEFAULT_FILL_STYLE;
   },

   initializeKnob: function (knobPercent, knobAnimationDuration) {
      this.animatingKnob = false;
      this.draggingKnob = false;

      this.knobPercent = knobPercent;
      this.knobAnimationDuration = knobAnimationDuration;

      this.createKnobCanvas();
   },

   createKnobCanvas: function() {
      this.knobCanvas = document.createElement('canvas');
      this.knobContext = this.knobCanvas.getContext('2d');

      this.knobCanvas.style.position = "absolute";
      this.knobCanvas.style.marginLeft = "0px";
      this.knobCanvas.style.marginTop = "1px";
      this.knobCanvas.style.zIndex = "1";
      this.knobCanvas.style.cursor = "crosshair";
      ...

   },
   ...
};

initializeConstants() 方法为所有滑块常量创建变量,包括没有指定值时 initializeStrokeAndFillStyles() 使用的描边和填充样式默认值。

中最有趣的方法是 initializeKnob(),在调用 createKnobCanvas() 为滑块旋钮创建一个单独画布之前,需要设置一些变量。createKnobCanvas() 创建了一个画布元素并设置其样式属性,使画布位于封闭画布的左上方。

现在,您已经了解了如何初始化滑轨和旋钮画布,接下来让我们来看看如何将它们用于绘制滑块。

绘制滑块

滑块组件的 draw()erase() 方法如 所示。

绘制和擦除滑块
COREHTML5.Slider.prototype = {
   ...

   erase: function() {
      this.railContext.clearRect(
         this.left - this.knobRadius, 0 - this.knobRadius,
         this.railCanvas.width  + 4*this.knobRadius,
         this.railCanvas.height + 3*this.knobRadius);

      this.knobContext.clearRect(0, 0, this.knobCanvas.width,
                                       this.knobCanvas.height);
   },

   draw: function (percent) {
      this.drawRail();
      this.drawKnob(percent ? percent : this.knobPercent );
   },
};

erase() 方法可以擦除这两个滑块画布:滑轨画布和旋钮画布。相反地,draw() 方法用于绘制滑轨和旋钮。您可以将旋钮的 percent(滑块的值)传递给 draw() 方法,也可以不带参数调用,在该情况下使用滑块的已有值。

绘制滑轨

在 中,滑块组件传递了两个参数来绘制滑轨。

绘制滑轨
COREHTML5.Slider.prototype = {
   ...
   drawRail: function () {
      var radius = (this.bottom - this.top) / 2;

      this.railContext.save();
      
      this.railContext.shadowColor   = this.SHADOW_COLOR;
      this.railContext.shadowOffsetX = this.SHADOW_OFFSET_X;
      this.railContext.shadowOffsetY = this.SHADOW_OFFSET_Y;
      this.railContext.shadowBlur = this.SHADOW_BLUR;

      this.railContext.beginPath();
      this.railContext.moveTo(this.left + radius, this.top);
      this.railContext.arcTo(this.right, this.top, this.right, this.bottom, radius);
      this.railContext.arcTo(this.right, this.bottom, this.left, this.bottom, radius);
      this.railContext.arcTo(this.left, this.bottom, this.left, this.top, radius);
      this.railContext.arcTo(this.left, this.top, this.right, this.top, radius);
      this.railContext.closePath();

      this.railContext.fillStyle = this.fillStyle;
      this.railContext.fill();
      this.railContext.shadowColor = undefined;
      this.railContext.restore();

      this.overlayRailGradient();

      this.railContext.restore();
   },

   overlayRailGradient: function () {
      var gradient =
         this.railContext.createLinearGradient(this.left, this.top,
                                           this.left, this.bottom);

      gradient.addColorStop(0,    'rgba(255,255,255,0.4)');
      gradient.addColorStop(0.2,  'rgba(255,255,255,0.6)');
      gradient.addColorStop(0.25, 'rgba(255,255,255,0.7)');
      gradient.addColorStop(0.3,  'rgba(255,255,255,0.9)');
      gradient.addColorStop(0.40, 'rgba(255,255,255,0.7)');
      gradient.addColorStop(0.45, 'rgba(255,255,255,0.6)');
      gradient.addColorStop(0.60, 'rgba(255,255,255,0.4)');
      gradient.addColorStop(1,    'rgba(255,255,255,0.1)');

      this.railContext.fillStyle = gradient;
      this.railContext.fill();

      this.railContext.lineWidth = 0.4;
      this.railContext.strokeStyle = this.strokeStyle;
      this.railContext.stroke();
   },
   ...
};

首先,滑块的 drawRail() 方法使用了纯色填充滑轨,如 所示。

滑块基底
绘制滑块基底
绘制滑块基底

接着,drawRail() 叠加了一个白色渐变,如 所示。

滑块叠加
绘制滑块叠加
绘制滑块叠加

如 所示,为滑轨增加了一定深度,使其看起来好像有一道光从顶部照射下来。

滑块合成
绘制滑块基底和叠加
绘制滑块基底和叠加

滑块的 overlayRailGradient() 方法使用 HTML5 Canvas 的 createLinearGradient() 方法来创建渐变。接着,overlayRailGradient() 沿着渐变线逐点添加起止颜色。每个起止颜色都是纯白色,带有不同程度的透明度。最后,overlayRailGradient() 将滑块填充为渐变色,然后描绘轮廓。

绘制旋钮

滑块在单个画布中绘制的旋钮如 所示。

旋钮画布
滑块旋钮的屏幕截图显示滑块颜色(rgba(255,255,0,0.5))和绘制该旋钮的简略版函数:createKnobCanvas()。此函数使用以下代码创建了画布元素:this.knobCanvas = document.createElement('canvas')。
滑块旋钮的屏幕截图显示滑块颜色(rgba(255,255,0,0.5))和绘制该旋钮的简略版函数:createKnobCanvas()。此函数使用以下代码创建了画布元素:this.knobCanvas = document.createElement('canvas')。

查看 ,滑块使用了一个 document.createElement() 调用来创建旋钮画布。滑块的 fillKnob()strokeKnob() 方法(如 所示)是在该画布中绘制的。

绘制旋钮
COREHTML5.Slider.prototype = {
   ...

   drawKnob: function (percent) {
      if (percent < 0) percent = 0;
      if (percent > 1) percent = 1;

      this.knobPercent = percent;
      this.moveKnob(this.knobPercentToPosition(percent));
      this.fillKnob();
      this.strokeKnob();
   },
   
   fillKnob: function () {
      this.knobContext.shadowColor   = this.KNOB_SHADOW_COLOR;
      this.knobContext.shadowOffsetX = this.KNOB_SHADOW_OFFSET_X;
      this.knobContext.shadowOffsetY = this.KNOB_SHADOW_OFFSET_Y;
      this.knobContext.shadowBlur    = this.KNOB_SHADOW_BLUR;

      this.knobContext.beginPath();

      this.knobContext.arc(this.knobCanvas.width/2, this.knobCanvas.height/2,
                           this.knobCanvas.width/2-2, 0, Math.PI*2, false);

      this.knobContext.clip();

      this.knobContext.fillStyle = this.KNOB_FILL_STYLE;
      this.knobContext.fill();
   },

   strokeKnob: function () {
      this.knobContext.lineWidth = 1;
      this.knobContext.strokeStyle = this.KNOB_STROKE_STYLE;
      this.knobContext.stroke();
   },
   ...
};

drawKnob() 方法带有 percent 参数,该参数代表滑块的位置。该方法设置滑块的值并以此移动旋钮,随后填充和描绘旋钮。

fillKnob() 方法使用半透明黄色填充旋钮,这使得底层的滑轨可以隐约显示,让旋钮看起来像是在发光。strokeKnob() 方法使用纯色描绘旋钮轮廓。

拖拽旋钮

拖拽旋钮的滑块代码如 所示。

拖拽旋钮
COREHTML5.Slider.prototype = {
   ...
   
   addMouseListeners: function () {
      var slider = this; // Let event handlers access this object

      this.knobCanvas.addEventListener('mousedown', function(e) {
         slider.draggingKnob = true;
         e.preventDefault();
      };
      
      this.knobCanvas.addEventListener('mousemove', function(e) {
         var mouse = null,
             percent = null;

         e.preventDefault();

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

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

      this.knobCanvas.addEventListener('mouseup', function(e) {
         e.preventDefault();
         slider.draggingKnob = false;
      }; 
   }, 

   windowToCanvas: function(x, y) {
      var bbox = this.railCanvas.getBoundingClientRect();

      return {
         x: x - bbox.left * (this.railCanvas.width  / bbox.width),
         y: y - bbox.top  * (this.railCanvas.height / bbox.height)
      };
   },

   knobPositionToPercent: function(position) {
      var railWidth = this.right - this.left - 2*this.knobRadius;
          percent = (position - this.left - this.knobRadius) / railWidth;

      percent = percent > 1.0 ? 1.0 : percent;
      percent = percent < 0 ? 0 : percent;

      return percent;
   },
   ...
};

回忆一下 ,滑块的构造函数调用了 所示的 。该方法向旋钮画布添加了鼠标按下、鼠标移动和鼠标释放事件句柄,用它们来管理旋钮画布的拖拽。两个帮助函数方法(windowToCanvas()knobPositionToPercent())使得这些事件句柄易于理解。

结束语

通过本文的简述,相信您现在已经了解如何实现特殊的 HTML5 组件。本 系列文章 的下一篇文章将继续介绍滑块组件,向您展示如何支持更改监听器,如何通过编程方式使用 CSS 转换来激活滑块的旋钮,以及如何将滑块组件添加到任意的 DOM 树。下期见!


下载资源


相关主题


评论

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development
ArticleID=862132
ArticleTitle=HTML5 组件: 特殊组件,第 1 部分
publish-date=04162013