zoukankan      html  css  js  c++  java
  • 自定义元素–为你的HTML代码定义新元素

    注意:这篇文章介绍的 API 尚未完全标准化,并且仍在变动中,在项目中使用这些实验性 API 时请务必谨慎。

    引言

    现在的 web 严重缺乏表达能力。你只要瞧一眼“现代”的 web 应用,比如 GMail,就会明白我的意思:

    gmail-自定义元素

    堆砌 <div> 一点都不现代。然而可悲的是,这就是我们构建 web 应用的方式。在现有基础上我们不应该有更高的追求吗?

    您还可以参考以下HTML5/CSS3相关文章:
    使用CSS3开启GPU硬件加速提升网站动画渲染性能
    HTML5 WebSockets 基础使用教程
    关于HTML 5 canvas 的基础教程
    让所有IE支持HTML5的解决方案

    时髦的标记,行动起来!

    HTML 为我们提供了一个完美的文档组织工具,然而 HTML 规范定义的元素却很有限。

    假如 GMail 的标记不那么糟糕,而是像下面这样漂亮,那会怎样?

    <hangout-module>
      <hangout-chat from="Paul, Addy">
        <hangout-discussion>
          <hangout-message from="Paul" profile="profile.png" profile="118075919496626375791" datetime="2013-07-17T12:02">
            <p>Feelin' this Web Components thing.</p>
            <p>Heard of it?</p>
          </hangout-message>
        </hangout-discussion>
      </hangout-chat>
      <hangout-chat>...</hangout-chat>
    </hangout-module>

     

    查看演示:

     

    真是令人耳目一新!这个应用太合理了,既有意义,又容易理解。最妙的是,它是可维护的,只要查看声明结构就可以清楚地知道它的作用。

    自定义元素,救救我们!就指望你了!

    赶紧开始吧

    自定义元素允许开发者定义新的 HTML 元素类型。该规范只是 Web 组件模块提供的众多新 API 中的一个,但它也很可能是最重要的一个。没有自定义元素带来的以下特性,Web 组件都不会存在:

    • 定义新的 HTML/DOM 元素
    • 基于其他元素创建扩展元素
    • 给一个标签绑定一组自定义功能
    • 扩展已有 DOM 元素的 API

    注册新元素

    使用 document.registerElement() 可以创建一个自定义元素:

    var XFoo = document.registerElement('x-foo');
    document.body.appendChild(new XFoo());

    document.registerElement() 的第一个参数是元素的标签名。这个标签名必须包括一个连字符(-)。因此,诸如 <x-tags><my-element> 和 <my-awesome-app> 都是合法的标签名,而 <tabs> 和 <foo_bar> 则不是。这个限定使解析器能很容易地区分自定义元素和 HTML 规范定义的元素,同时确保了 HTML 增加新标签时的向前兼容。

    第二个参数是一个(可选的)对象,用于描述该元素的 prototype。在这里可以为元素添加自定义功能(例如:公开属性和方法)。稍后详述。

    自定义元素默认继承自 HTMLElement,因此上一个示例等同于:

    var XFoo = document.registerElement('x-foo'{
      prototype: Object.create(HTMLElement.prototype)
    });

    调用 document.registerElement('x-foo') 向浏览器注册了这个新元素,并返回一个可以用来创建 <x-foo> 元素实例的构造器。如果你不想使用构造器,也可以使用其他实例化元素的技术。

    提示:如果你不希望在 window 全局对象中创建元素构造器,还可以把它放进命名空间:

    var myapp {};
    myapp.XFoo = document.registerElement('x-foo');

    扩展原生元素

    假设平淡无奇的原生 <button> 元素不能满足你的需求,你想将其增强为一个“超级按钮”,可以通过创建一个继承 HTMLButtonElement.prototype 的新元素,来扩展 <button> 元素:

    var MegaButton = document.registerElement('mega-button'{
      prototype: Object.create(HTMLButtonElement.prototype)
    });

    提示:要创建扩展自元素 B 的元素 A,元素 A 必须继承元素 B 的 prototype。

    这类自定义元素被称为类型扩展自定义元素。它们以继承某个特定 HTMLElement 的方式表达了“元素 X 是一个 Y”。

    示例:

    <button is="mega-button">

    元素如何提升

    你有没有想过为什么 HTML 解析器对非标准标签不报错?比如,我们在页面中声明一个 <randomtag>,一切都很和谐。根据 HTML 规范的表述:

    非规范定义的元素必须使用 HTMLUnknownElement 接口。
    – HTML 规范

    <randomtag> 是非标准的,它会继承 HTMLUnknownElement

    对自定义元素来说,情况就不一样了。拥有合法元素名的自定义元素将继承 HTMLElement。你可以按 Ctrl+Shift+J(Mac 系统为 Cmd+Opt+J)打开控制台,运行下面这段代码,得到的结果将是 true:

    // “tabs”不是一个合法的自定义元素名
    document.createElement('tabs').__proto__ === HTMLUnknownElement.prototype
     
    // “x-tabs”是一个合法的自定义元素名
    document.createElement('x-tabs').__proto__ == HTMLElement.prototype

    注意:在不支持 document.registerElement() 的浏览器中,<x-tabs> 仍为 HTMLUnknownElement

    UNRESOLVED 元素
    由于自定义元素是通过脚本执行 document.registerElement() 注册的,因此 它们可能在元素定义被注册到浏览器之前就已经声明或创建过了。例如:你可以先在页面中声明 <x-tabs>,以后再调用 document.registerElement('x-tabs')

    在被提升到其定义之前,这些元素被称为 unresolved 元素。它们是拥有合法自定义元素名的 HTML 元素,只是还没有注册成为自定义元素。

    下面这个表格看起来更直观一些:

    类型继承自示例
    unresolved 元素 HTMLElement <x-tabs><my-element><my-awesome-app>
    未知元素 HTMLUnknownElement <tabs><foo_bar>

    把 unresolved 元素想象成尚处于中间状态,它们都是等待被浏览器提升的潜在候选者。浏览器说:“你具备一个新元素的全部特征,我保证会在赋予你定义的时候将你提升为一个元素”。

    实例化元素

    我们创建普通元素用到的一些技术也可以用于自定义元素。和所有标准定义的元素一样,自定义元素既可以在 HTML 中声明,也可以通过 JavaScript 在 DOM 中创建。

    实例化自定义标签

    声明元素:

    <x-foo></x-foo>

    在 JS 中创建 DOM:

    var xFoo = document.createElement('x-foo');
    xFoo.addEventListener('click'function(e{
      alert('Thanks!');
    });

    使用 new 操作符:

    var xFoo new XFoo();
    document.body.appendChild(xFoo);

    实例化类型扩展元素

    实例化类型扩展自定义元素的方法和自定义标签惊人地相似。

    声明元素:

    <!-- <button> “是一个”超级按钮 -->
    <button is="mega-button">

    在 JS 中创建 DOM:

    var megaButton = document.createElement('button''mega-button');
    // megaButton instanceof MegaButton === true

    看,这是接收第二个参数为 is="" 属性的 document.createElement() 重载版本。

    使用 new 操作符:

    var megaButton new MegaButton();
    document.body.appendChild(megaButton);

    现在,我们已经学习了如何使用 document.registerElement() 来向浏览器注册一个新标签。但这还不够,接下来我们要向新标签添加属性和方法。

    添加 JS 属性和方法

    自定义元素最强大的地方在于,你可以在元素定义中加入属性和方法,给元素绑定特定的功能。你可以把它想象成一种给你的元素创建公开 API 的方法。

    这里有一个完整的示例:

    var XFooProto = Object.create(HTMLElement.prototype);
     
    // 1. 为 x-foo 创建 foo() 方法
    XFooProto.foo function({
      alert('foo() called');
    };
     
    // 2. 定义一个只读的“bar”属性
    Object.defineProperty(XFooProto"bar"{value5});
     
    // 3. 注册 x-foo 的定义
    var XFoo = document.registerElement('x-foo'{prototype: XFooProto});
     
    // 4. 创建一个 x-foo 实例
    var xfoo = document.createElement('x-foo');
     
    // 5. 插入页面
    document.body.appendChild(xfoo);

    构造 prototype 的方法多种多样,如果你不喜欢上面这种方式,再看一个更简洁的例子:

    var XFoo = document.registerElement('x-foo'{
      prototype: Object.create(HTMLElement.prototype{
        bar{
          getfunction(return 5}
        },
        foo{
          valuefunction({
            alert('foo() called');
          }
        }
      })
    });

    以上两种方式,第一种使用了 ES5 的 Object.defineProperty,第二种则使用了 get/set

    生命周期回调方法

    元素可以定义特殊的方法,来注入其生存期内关键的时间点。这些方法各自有特定的名称和用途,它们被恰如其分地命名为生命周期回调:

    回调名称调用时间点
    createdCallback 创建元素实例
    attachedCallback 向文档插入实例
    detachedCallback 从文档中移除实例
    attributeChangedCallback(attrName, oldVal, newVal) 添加,移除,或修改一个属性

    所有生命周期回调都是可选的,你可以只在需要关注的时间点定义它们。例如:假设你有一个很复杂的元素,它会在 createdCallback() 打开一个 IndexedDB 连接。在将其从 DOM 移除时,detachedCallback() 会做一些必要的清理工作。注意:不要过于依赖这些生命周期方法(比如用户直接关闭浏览器标签),仅将其作为可能的优化点。

    另一个生命周期回调的例子是为元素设置默认的事件监听器:

    proto.createdCallback function({
      this.addEventListener('click'function(e{
        alert('Thanks!');
      });
    };

    如果你的元素太笨重,是不会有人用它的。生命周期回调可以帮你大忙!

    添加标记

    我们已经创建好 <x-foo> 并添加了 JavaScript API,但它还没有任何内容。不如我们给点 HTML 让它渲染?

    生命周期回调在这个时候就派上用场了。我们甚至可以用 createdCallback() 给一个元素赋予一些默认的 HTML:

    var XFooProto = Object.create(HTMLElement.prototype);
     
    XFooProto.createdCallback function({
      this.innerHTML "<b>I'm an x-foo-with-markup!</b>";
    };

    var XFoo = document.registerElement('x-foo-with-markup'{prototype: XFooProto});

    实例化这个标签并在 DevTools 中观察(右击,选择“审查元素”),可以看到如下结构:

    <x-foo-with-markup>
       <b>I'm an x-foo-with-markup!</b>
     </x-foo-with-markup>

    用 Shadow DOM 封装内部实现

    Shadow DOM 本身是一个封装内容的强大工具,配合使用自定义元素就更神奇了!

    Shadow DOM 为自定义元素提供了:

    1.一种隐藏内部实现的方法,从而将用户与血淋淋的实现细节隔离开。
    2.简单有效的样式隔离

    从 Shadow DOM 创建元素,跟创建一个渲染基础标记的元素非常类似,区别在于 createdCallback() 回调:

    var XFooProto = Object.create(HTMLElement.prototype);
     
    XFooProto.createdCallback function({
      // 1. 为元素附加一个 shadow root。
      var shadow this.createShadowRoot();
     
      // 2. 填入标记。
      shadow.innerHTML "<b>I'm in the element's Shadow DOM!</b>";
    };
     
    var XFoo = document.registerElement('x-foo-shadowdom'{prototype: XFooProto});

    我们并没有直接设置 <x-foo-shadowdom> 的 innerHTML,而是为其创建了一个用于填充标记的 Shadow Root。在 DevTools 设置中勾选“Show Shadow DOM”,你就会看到一个可以展开的 #shadow-root:

    <x-foo-shadowdom>
       #shadow-root
         <b>I'm in the element's Shadow DOM!</b>
     </x-foo-shadowdom>

    这就是 Shadow Root!

    从模板创建元素

    HTML 模板是另一组跟自定义元素完美融合的新 API。

    <template> 元素可用于声明 DOM 片段。它们可以被解析并在页面加载后插入,以及延迟到运行时才进行实例化。模板是声明自定义元素结构的理想方案。

    示例:注册一个由 <template> 和 Shadow DOM 创建的元素:

    <template id="sdtemplate">
      <style>
        p { color: orange; }
      </style>
      <p>I'm in Shadow DOM. My markup was stamped from a &lt;template&gt;.</p>
    </template>
     
    <script>
    var proto = Object.create(HTMLElement.prototype, {
      createdCallback: {
        value: function() {
          var t = document.querySelector('#sdtemplate');
          var clone = document.importNode(t.content, true);
          this.createShadowRoot().appendChild(clone);
        }
      }
    });
    document.registerElement('x-foo-from-template', {prototype: proto});
    </script>

    短短几行做了很多事情,我们挨个来看都发生了些什么:

    1. 我们在 HTML 中注册了一个新元素:<x-foo-from-template>
    2. 这个元素的 DOM 是从一个 <template> 创建的
    3. Shadow DOM 隐藏了该元素可怕的细节
    4. Shadow DOM 也对元素的样式进行了隔离(比如 {color: orange;} 不会把整个页面都搞成橙色)

    真不错!

    自定义元素增加样式

    和其他 HTML 标签一样,自定义元素也可以用选择器定义样式:

    <style>
      app-panel {
        display: flex;
      }
      [is="x-item"] {
        transition: opacity 400ms ease-in-out;
        opacity: 0.3;
        flex: 1;
        text-align: center;
        border-radius: 50%;
      }
      [is="x-item"]:hover {
        opacity: 1.0;
        background: rgb(255, 0, 255);
        color: white;
      }
      app-panel > [is="x-item"] {
        padding: 5px;
        list-style: none;
        margin: 0 7px;
      }
    </style>
     
    <app-panel>
      <li is="x-item">Do</li>
      <li is="x-item">Re</li>
      <li is="x-item">Mi</li>
    </app-panel>

     

    为使用 Shadow DOM 的元素增加样式

    有了 Shadow DOM 场面就热闹得多了,它可以极大增强自定义元素的能力。

    Shadow DOM 为元素增加了样式封装的特性。Shadow Root 中定义的样式不会暴露到宿主外部或对页面产生影响。对自定义元素来说,元素本身就是宿主。样式封装的属性也使得自定义元素能够为自己定义默认样式。

    Shadow DOM 的样式是一个很大的话题!如果你想更多地了解它,推荐你阅读我写的其他文章:

    使用 :unresolved 伪类避免无样式内容闪烁(FOUC)

    为了缓解无样式内容闪烁的影响,自定义元素规范提出了一个新的 CSS 伪类 :unresolved。在浏览器调用你的 createdCallback()(见生命周期回调方法一节)之前,这个伪类都可以匹配到 unresolved 元素。一旦产生调用,就意味着元素已经完成提升,成为它被定义的形态,该元素就不再是一个 unresolved 元素了。

    提示:Chrome 29 已经原生支持 CSS :unresolved 伪类。

    示例:注册后渐显的 <x-foo> 标签:

    x-foo {
      opacity1;
      transition: opacity 300ms;
    }
    x-foo:unresolved {
      opacity0;
    }

    请记住 :unresolved 伪类只能用于 unresolved 元素,而不能用于继承自 HTMLUnkownElement 的元素(见元素如何提升一节)。

    <style>
      /* 给所有 unresolved 元素添加边框 */
      :unresolved {
        border: 1px dashed red;
        display: inline-block;
      }
      /* unresolved 元素 x-panel 的文本内容为红色 */
      x-panel:unresolved {
        color: red;
      }
      /* 定义注册后的 x-panel 文本内容为绿色 */
      x-panel {
        color: green;
        display: block;
        padding: 5px;
        display: block;
      }
    </style>
     
    <panel>
      I'm black because :unresolved doesn't apply to "panel".
      It's not a valid custom element name.
    </panel>
     
    <x-panel>I'm red because I match x-panel:unresolved.</x-panel>

     

    了解更多 :unresolved 伪类的知识,请看 Polymer 文档《元素样式指南》

    浏览器支持

    Chrome 27 和 Firefox 23 都提供了对 document.registerElement() 的支持,不过之后规范又有一些演化。Chrome 31 将是第一个真正支持新规范的版本。

    提示:在 Chrome 31 中使用自定义元素,需要开启 about:flags 中的“实验性 web 平台特性(Experimental Web Platform features)”选项。

    在浏览器支持稳定之前,也有一些很好的兼容方案:

    HTMLElementElement 怎么了?

    一直关注标准的人都知道曾经有一个 <element> 标签。它非常好用,你只要像下面这样就可以声明式地注册一个新元素:

    <element name="my-element">
      ...
    </element>

    然而很不幸,在它的提升过程、边界案例,以及末日般的复杂场景中,需要处理大量的时序问题。<element> 因此被迫搁置。2013 年 8 月,Dimitri Glazkov 在 public-webapps 邮件组中宣告移除 <element>

    值得注意的是,Polymer 实现了以 <polymer-element> 的形式声明式地注册元素。这是怎么做到的?它用的正是 document.registerElement('polymer-element') 以及我在从模板创建元素一节介绍的技术。

    结语

    自定义元素为我们提供了一个工具,通过它我们可以扩展 HTML 的词汇,赋予它新的特性,并把不同的 web 平台连接在一起。结合其他新的基本平台,如 Shadow DOM 和 <template>,我们领略了 web 组件的宏伟蓝图。标记语言将再次变得很时髦!

    如果你对使用 web 组件感兴趣,建议你看看 Polymer,它已经有了你想要的一切。

    英文原文:《Custom Elements: defining new elements in HTML
    转载原文:http://forcefront.com/2013/custom-elements-defining-new-elements-in-html

  • 相关阅读:
    prev()方法使用的注意点
    JS 获取图片的高度
    渐变色
    JS获取时间
    监听鼠标上下滚动事件
    几种常见的边框样式
    左侧导航背景颜色随机变化
    apache开启gzip压缩
    dedecms在linux上安装提示没权限解决办法
    阿里云centos7.3安装lamp环境
  • 原文地址:https://www.cnblogs.com/zytrue/p/8494533.html
Copyright © 2011-2022 走看看