什么是 Polymer?
Polymer 是一个名为 Web组件的新 web标准的垫片,它使我们能够创建完全自定义的 HTML元素。

我们每天都使用本机 web元素,例如 <input>
, <checkbox>
, <audio>
,等等。它们的数量是有限的,它们的设计和行为是由浏览器定义的,而不是由开发人员定义的。使用Web组件,我们现在可以创建一个自定义 <element>
并定义它的样式和行为。不幸的是,由于这个标准是非常新的,所以需要一些时间才能得到所有浏览器的支持。
幸运的是,现在有几个开放源码库正在积极开发,允许创建和使用自定义元素:
Polymer (Google)和 X-Tag (Mozilla)。今年夏天,在 I/O峰会期间,Polymer 团队推出了v1.0,并宣布它现在可以生产了。
什么是自定义元素?
比如说,你有一个博客,需要在你的一些帖子中添加音频(例如,音乐曲目或播客)。要做到这一点,最方便的方法是使用本机 <audio>
元素。它使用简单,不能与任何周围的代码冲突。只要粘贴 <audio controls src="track.mp3">
,就会得到一个 100%受控的结果,如下所示:

但如果你希望它看起来不一样呢?或者有一些额外的功能,比如显示时间和标题?最重要的是,您希望保持它的简单和模块化,就像本机解决方案一样。
比如说,我们想要建造这样的东西:

这就是 Web组件和 polymer发挥作用的地方。当我们有一个自定义元素时,我们可以像使用本机元素一样简单地使用它:
1 | <my-super-audio src="track.mp3"></my-super-audio> |
多酷啊?
另外,假设您可以将所有可重用的 UI元素封装到自定义 HTML标记中,并跨不同的应用程序使用它们。您可以确保应用程序的代码和自定义元素的代码之间不会有任何冲突。
怎么可能?
Shadow DOM
当您在任何网页上插入一个普通的 <input>
元素时,它看起来总是相同的,并且不会与您的代码发生冲突。但是 <input>
有内置样式、一些公共 APIs,并且它对单击事件作出反应,这意味着它有一些 JavaScript逻辑。你有没有想过这些原生元素是如何工作的?
可能不会,因为当您在浏览器开发工具中检查您的页面时,您所能看到的只有 <input>
而没有其他的 <scripts>
。那么 <input>
在哪里隐藏它的 CSS和 JS呢?
在 Shadow DOM.
去看它:
- 打开开发工具 (
Alt+Cmd+i
) 在 Chrome。 - 打开设置 (
Fn+F1
) 并允许 Show user agent shadow DOM。 - 重新载入 Chrome。
- 检查任何
<audio>
标记 ( right-click on input field -> Inspect )。
现在您应该可以看到它的隐藏代码 Shadow DOM.

创建自定义元素时,在 Shadow DOM中隐藏其样式、行为和标记。这可以确保没有任何东西可以从应用程序所在的 Light DOM中到达。
开始建立吧!
今天,我们将从上面的截图中构建一个自定义的 <my-super-audio>
元素。为了了解我们要去哪里,请查看这个开源 here。
注意:按照惯例,每个自定义元素必须在其名称中至少包含一个破折号。
安装依赖关系
确保在您的机器上安装了 Node, npm, git 和使用 Bower管理所有依赖项。若要安装它们,请打开“我的超级音频项目”文件夹并运行:
1 | bower install |
本地Web服务器
因为我们要使用HTML导入,所以我们需要使用本地服务器运行项目。如果你没有,种子项目有一个不错的选择内置。要使用它,请全局安装:
1 | npm install -g polyserve |
安装后,运行:
1 | polyserve |
这将在端口8080本地启动Web服务器。现在您可以在浏览器中看到默认的种子元素: localhost:8080/components/seed-element/demo
。
太棒了!现在我们可以继续进行编码了。
创造一个 Polymer element
首先,我们需要删除种子元素并创建我们自己的 my-super-audio 元素。
- 在您的项目的根文件夹中,将 seed-element.html重命名为 my-super-audio.html。
- 在 bower.json 文件中,将项目重命名为 my-super-audio。
- 打开 my-super-audio.html并用以下代码替换现有的
<dom-module>
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <dom-module id="my-super-audio"> <template> <p>{{author}}</p> </template> <script> Polymer({ is: 'my-super-audio', properties: { author: String } }); </script> </dom-module> |
通过创建一个 id与 Polymer.is
属性相同的 <dom-module>
标记,我们使用这个名称声明了一个自定义的基于 Polymer-based的元素。
现在,让我们在浏览器中测试它。为此,我们需要将新创建的元素放在Demo页面上。
首先,在 /demo文件夹中打开 index.html,并编辑 <link>
元素以导入 my-super-audio.html:
1 | <link rel="import" href="../my-super-audio.html"> |
现在,将元素放入 <body>
中,如下所示(将您项目的名称更改为 YOUR_NAME_HERE
):
1 2 3 | <body> <my-super-audio author="YOUR_NAME_HERE">Hello World!</my-super-audio> </body> |
在浏览器中打开演示页面: http://localhost:8080/components/my-super-audio/demo/
,你会看到… your name。
等等!而不是预期的 Hello World!我们以某种方式从元素的 Author属性中显示一个字符串。是怎么发生的?
这就是自定义元素的工作方式。只有 <dom-module>
中的代码才重要,而不是 Light DOM中的代码。因为在元素的 <template>
中,我们只显示与 polymer元素的 author属性绑定的 <p>
,这是唯一可见的数据。没别的了。
Polymer元素结构
正如您已经知道的,Web组件允许我们创建自己的自定义元素来封装标记 (HTML)、样式 (CSS)和行为 (JavaScript)。在 <dom-module>
中,我们有三个部分:
<template>
若要创建将呈现在内部的标记,请执行以下操作<my-super-audio>
;<style>
对此元素进行风格化;<script>
声明自定义的公共和私有属性、方法和其他逻辑。
注意: 从 Polymer v1.1,建议将
<style>
标记放置在<template>
中。
第一件事是:布局。
既然我们知道 My-Super-Audio元素的外观,那么让我们为它创建一个标记。我们的主要水平矩形内部将有三个部分:
1 2 3 4 5 6 7 | <template> <div id="wrapper"> <div id="left"><!-- ... --></div> <div><!-- ... --></div> <div id="right"><!-- ... --></div> </div> </template> |
为了正确定位它们,让我们使用 flex-box添加 <style>
标记和一些CSS规则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | <template> <!-- styling --> <style> :host { width: 100%; } #left, #right { height: 50px; width: 50px; position: relative; } #left { background-color: blueviolet; } /* Helpers */ .layout-horizontal { display: flex; -ms-flex-direction: row; -webkit-flex-direction: row; flex-direction: row; } .flex { -ms-flex: 1; -webkit-flex: 1; flex: 1; } .self-start { -ms-align-self: flex-start; -webkit-align-self: flex-start; align-self: flex-start; } .self-end { -ms-align-self: flex-end; -webkit-align-self: flex-end; align-self: flex-end; } </style> <!-- markup --> <div id="wrapper" class="layout-horizontal"> <div id="left" class="self-start"><!-- ... --></div> <div class="flex"><!-- ... --></div> <div id="right" class="self-end"><!-- ... --></div> </div> </template> |
如果您现在打开 Demo页面,您将不会看到多少,所以让我们显示一个标题,就像我们显示上面的作者名称一样。
首先,将属性名从作者编辑为 Polymer函数中的标题:
1 2 3 4 5 6 7 | Polymer({ is: 'my-super-audio', properties: { title: String } }); |
接下来,将其绑定到 <div>
中间的类 flex中:
1 2 3 4 5 6 | <div id="left" class="self-start"><!-- ... --></div> <div class="flex"> <!-- Title --> <div id="title" class="fit">{{ title }}</div> </div> <div id="right" class="self-end"><!-- ... --></div> |
在里面添加一些样式 <style>
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #title { position: absolute; color: blueviolet; font-size: 15px; text-align: center; line-height: 50px; z-index: 2; } #wrapper { position: relative; box-shadow: 0 1px 2px rgba(0, 0, 0, .3); cursor: pointer; } .fit { position: absolute; margin: auto; top: 0; right: 0; bottom: 0; left: 0; } |
最后,让我们在 Demo页面上编辑元素,传递标题而不是作者:
1 | <my-super-audio title="My Podcast #17"></my-super-audio> |
现在,重新加载演示页面,您将看到 Podcast #17 在一个漂亮的白色材质元素上。当鼠标悬停在元素上时,光标现在变成了指针。是时候播放音频了!
添加音频
为了播放一段曲目,我们将使用所有浏览器都可以使用的 Web Audio API。启动这个API最简单的方法是声明一个 <audio>
元素。
首先,我们添加一个新的公共属性来传递音频轨道的URL。
这是 my-super-audio.html:
1 2 3 4 5 6 | <div class="flex"> <!-- Title --> <div id="title">{{ title }}</div> <!-- Audio HTML5 element --> <audio id="audio" src="{{ src }}"></audio> </div> |
1 2 3 4 5 6 7 8 | Polymer({ is: 'my-super-audio', properties: { title: String, src: String } }); |
示例页面:
1 | <my-super-audio title="Podcast #17" src="track.mp3"></my-super-audio> |
注意:确保您指定了您最喜欢的轨道的路径,因为在开发和测试期间,您必须听它一百万次! 🙂
如果现在使用开发人员工具检查 Demo页面,您将看到音频元素在 <my-super-audio>
中可用,但是如何使用呢?
将单击事件绑定到自定义方法
我们希望在这里实现的是,当用户单击元素上的任何位置时,音频就会 开始/暂停 播放。
要实现此行为,我们需要侦听整个元素上的 click事件,并将其绑定到我们的自定义方法中,该方法将触发 <audio>
以启动 (或暂停) 播放。
对于诸如点击、鼠标切换等事件,Polymer为我们提供了方便的内置听者。要使用它,只需将 on-click="playPause"
属性添加到 #wrapper
div:
1 | <div id="wrapper" class="layout-horizontal" on-click="playPause"> |
现在,让我们通过添加一个自定义方法来扩展我们的 Polymer元素:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | Polymer({ is: 'my-super-audio', properties: { title: String, src: String }, playPause: function(e) { e.preventDefault(); var player = this; if ( player.$.audio.paused ) { player.$.audio.play(); } else { player.$.audio.pause(); } } }); |
我们在这里所做的:
- 通过引用
player.$.audio
(orthis.$.audio
),我们引用了id="audio"
的内部节点。这是一种从 Polymer中自动查找节点的工具,它允许我们访问经常使用的节点,而无需手动查询它们。 - play() 和 pause() 方法是 Audio Web API提供的本机方法。
现在,重新加载演示页面,并单击播放机,以听到您的轨道。
使用外部自定义元素
到目前为止,我们已经编写了自己的代码,但我们也可以用与本地元素相同的方式集成外部自定义元素。实际上,我们刚刚使用了本机 <audio>
元素。现在,让我们加入一些有用的 Polymer元素。
在这一点上,我们需要的是进度条来表示音频轨道的进展。让我们用 Paper-Progress 元素的 Polymer来实现这一点。
首先,让我们使用Bower作为依赖项安装它:
1 | bower install --save PolymerElements/paper-progress |
在这里,我们使用
--保存
标志,因此 Bower将此元素保存为bower.json
中的依赖项。这一点至关重要,因为希望在项目中使用<my-super-audio>
元素的用户需要在安装时安装所有依赖项。
要在安装后访问元素,我们需要导入它。在 my-super-audio.html 文件的顶部添加以下行,就在 polymer库导入之后:
1 | <link rel="import" href="../paper-progress/paper-progress.html"> |
现在,让我们将进度条添加到中间部分:
1 2 3 4 5 6 7 8 | <div class="flex"> <!-- Title --> <div id="title">{{ title }}</div> <!-- Audio HTML5 element --> <audio id="audio" src="{{ src }}"></audio> <!-- Paper progress bar --> <paper-progress id="progress"></paper-progress> </div> |
要修改 Paper-Progress元素的默认样式,我们使用它的自定义外部 CSS属性,这些属性具有特殊的前缀 (--paper-progress-active-color
)。对于每个 Polymer元素,它的作者可以公开一些样式选项,以便于定制。将以下代码添加到 <style>
:
1 2 3 4 5 6 7 | paper-progress { position: relative; width: 100%; --paper-progress-active-color: blueviolet; --paper-progress-height: 50px; --paper-progress-container-color: rgba(255, 255, 255, .75); } |
在 JS中,我们需要向 <audio>
元素添加两个事件侦听器。
- 当浏览器加载
<audio>
元数据时,第一个元数据将触发。此时我们知道轨道的持续时间,因此我们可以设置进度条的max属性。 - 第二个听者将在
<audio>
开始播放时启动。此时,我们启动进度计时器,每 120毫秒更新进度条状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | // Register event listeners listeners: { 'audio.loadedmetadata': '_onCanPlay', 'audio.playing': '_startProgressTimer' }, // When metadata is loaded and player can start playing _onCanPlay: function() { var player = this; player.$.progress.max = player.$.audio.duration * 1000; }, // Start the progress timer _startProgressTimer: function() { var player = this; player.timer = {}; if (player.timer.sliderUpdateInterval) { clearInterval(player.timer.sliderUpdateInterval); } player.timer.sliderUpdateInterval = setInterval( function(){ if ( player.$.audio.paused ) { clearInterval(player.timer.sliderUpdateInterval); } else { player.$.progress.value = player.$.audio.currentTime * 1000; player.currentTime = player.$.audio.currentTime; } }, 120); } |
看看演示页面!看起来不错,不是吗? 🙂
计算绑定
由于我们现在可以访问跟踪持续时间数据,让我们将其显示在播放器的 #right
部分。让我们创建持续时间属性,该属性将是一个数字:
1 2 3 4 5 6 7 8 9 10 11 12 | properties: { title: String, src: String, isPlaying: { type: Boolean, value: false }, duration: { type: Number, value: 0 } }, |
在内部添加以下标记 <template>
:
1 2 3 4 5 6 | <div id="right" class="self-end"> <!-- Duration --> <div id="duration" class="fit"> <span class="fit">{{ duration }}</span> </div> </div> |
内部造型 <style>
:
1 2 3 4 5 6 | #duration { text-align: center; line-height: 50px; font-size: 11px; color: blueviolet; } |
现在,我们需要在浏览器中加载 <audio>
元素元数据时更新工期属性。为此,让我们扩展输出 _onCanPlay 方法:
1 2 3 4 5 | _onCanPlay: function() { var player = this; player.$.progress.max = player.$.audio.duration * 1000; player.duration = player.$.audio.duration; }, |
好的,查看演示页面来验证它。我们现在可以看到 duration 时间,但这是一个很长的数字。
问题是 <audio>
元素提供了它的持续时间(以秒为单位),这对我们的用户来说并不能提供任何信息。那么,我们如何才能将秒转换成好看的 m:ss
格式呢? Computer bindings 到救援!
计算绑定类似于计算属性。让我们添加一个私有方法 _convertSecToMin:
1 2 3 4 5 6 7 8 9 | // to convert seconds to 'm:ss' format _convertSecToMin: function(seconds){ if (seconds === 0) { return ''; } var minutes = Math.floor(seconds / 60); var secondsToCalc = Math.floor(seconds % 60) + ''; return minutes + ':' + (secondsToCalc.length < 2 ? '0' + secondsToCalc : secondsToCalc); } |
并修改标记中的绑定:
1 2 3 4 | <!-- Duration --> <div id="duration" class="fit"> <span class="fit">{{ _convertSecToMin(duration) }}</span> </div> |
太棒了!现在看起来好多了。
数据绑定助手
我们要在播放器中实现的最后一件事是 #left
的图标,这些图标将根据当前的状态指示播放器是可以播放还是暂停。
首先,让我们将 Iron-Icons 保存为播放机的依赖项:
1 | bower install --save PolymerElements/iron-icon |
1 | bower install --save PolymerElements/iron-icons |
在 my-super-audio.html中,在 Paper-Progress之后导入这两个库:
1 2 | <link rel="import" href="../iron-icon/iron-icon.html"> <link rel="import" href="../iron-icons/av-icons.html"> |
添加以下标记和样式:
1 2 3 4 5 6 7 8 9 10 11 12 | <div id="left" class="self-start"> <!-- Icons --> <iron-icon id="play" class="fit" icon="av:play-circle-outline" hidden$="{{ isPlaying }}"></iron-icon> <iron-icon id="pause" class="fit" icon="av:pause-circle-outline" hidden$="{{ !isPlaying }}"></iron-icon> </div> |
1 2 3 4 | #play, #pause { color: #fff; } |
在高分子函数中,添加一个名为 isPlaying
的新属性来记录播放机的状态,并扩展 playPause
方法以在用户单击元素时切换该属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | Polymer({ is: 'my-super-audio', properties: { title: String, src: String, isPlaying: { type: Boolean, value: false } }, playPause: function(e){ e.preventDefault(); var player = this; if ( player.$.audio.paused ) { player.$.audio.play(); player.isPlaying = true; } else { player.$.audio.pause(); player.isPlaying = false; } } }); |
这一次我们声明了一个新的属性
isPlaying
作为一个对象, 其类型为Boolean
默认值为false
.
恭喜你!您已经创建了您的第一个自定义元素!

本教程是基于开放源码 Paper-Audio-Player 元素创建的。看看它,看看不同的 Polymer特性可以一起发挥。
链接
原文:https://scotch.io/tutorials/create-a-custom-audio-player-element-using-polymer