创建可复用的 HTML 模板

Avatar of Caleb Williams
Caleb Williams

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

在我们 上一篇文章 中,我们从高层次讨论了 Web Components 规范(自定义元素、Shadow DOM 和 HTML 模板)。在本文以及接下来的三篇文章中,我们将对这些技术进行测试,并更详细地检查它们,看看我们如何在当今的生产环境中使用它们。为此,我们将从头开始构建一个自定义模态对话框,以了解各种技术如何协同工作。

文章系列

  1. Web Components 简介
  2. 创建可复用的 HTML 模板(本文
  3. 从头开始创建自定义元素
  4. 使用 Shadow DOM 封装样式和结构
  5. Web Components 的高级工具

HTML 模板

Web Components 规范 中一个鲜为人知但功能强大的特性是 <template> 元素。在本系列的 第一篇文章 中,我们将模板元素定义为“HTML 中的用户定义模板,只有在被调用时才会呈现”。换句话说,模板是浏览器在被告知执行其他操作之前会忽略的 HTML。

然后可以以多种有趣的方式传递和重用这些模板。出于本文的目的,我们将研究如何为一个对话框创建模板,该对话框最终将用于自定义元素中。

定义我们的模板

听起来可能很简单,<template> 是一个 HTML 元素,因此带有内容的最基本形式的模板将是

<template>
  <h1>Hello world</h1>
</template>

在浏览器中运行此代码将导致屏幕为空白,因为浏览器不会渲染模板元素的内容。这变得非常强大,因为它允许我们定义内容(或内容结构)并将其保存以备后用——而不是在 JavaScript 中编写 HTML。

为了使用该模板,我们需要 JavaScript

const template = document.querySelector('template');
const node = document.importNode(template.content, true);
document.body.appendChild(node);

真正的魔力发生在 document.importNode 方法中。此函数将创建模板 content 的副本,并将其准备插入到另一个文档(或文档片段)中。函数的第一个参数获取模板的内容,第二个参数告诉浏览器对元素的 DOM 子树(即其所有子元素)进行深度复制。

我们可以直接使用 template.content,但这样做会从元素中删除内容并将其附加到文档的 body 中。任何 DOM 节点只能连接在一个位置,因此后续使用模板内容将导致空文档片段(本质上是空值),因为内容先前已被移动。使用 document.importNode 允许我们在多个位置重用同一模板内容的实例。

然后将该节点附加到 document.body 中,并为用户呈现。这最终使我们能够做一些有趣的事情,例如为我们的用户(或程序的使用者)提供创建内容的模板,类似于我们在 第一篇文章 中介绍的以下演示

查看 CodePen 上 Caleb Williams 的示例
模板示例
@calebdwilliams
CodePen 上。

在此示例中,我们提供了两个模板来呈现相同的内容——作者及其撰写的书籍。随着表单的变化,我们选择渲染与该值关联的模板。使用相同的技术,我们最终将能够创建一个自定义元素,该元素将使用稍后定义的模板。

模板的多功能性

关于模板的一个有趣的事情是,它们可以包含任何 HTML。包括脚本和样式元素。一个非常简单的例子是一个模板,它附加一个按钮,当我们点击它时会发出警报。

<button id="click-me">Log click event</button>

让我们对其进行样式设置

button {
  all: unset;
  background: tomato;
  border: 0;
  border-radius: 4px;
  color: white;
  font-family: Helvetica;
  font-size: 1.5rem;
  padding: .5rem 1rem;
}

…并使用一个非常简单的脚本调用它

const button = document.getElementById('click-me');
button.addEventListener('click', event => alert(event));

当然,我们可以使用 HTML 的 <style><script> 标签直接在模板中组合所有这些内容,而不是在单独的文件中

<template id="template">
  <script>
    const button = document.getElementById('click-me');
    button.addEventListener('click', event => alert(event));
  </script>
  <style>
    #click-me {
      all: unset;
      background: tomato;
      border: 0;
      border-radius: 4px;
      color: white;
      font-family: Helvetica;
      font-size: 1.5rem;
      padding: .5rem 1rem;
    }
  </style>
  <button id="click-me">Log click event</button>
</template>

一旦此元素附加到 DOM,我们将拥有一个新的 ID 为 #click-me 的按钮,一个针对按钮 ID 的全局 CSS 选择器,以及一个简单的事件监听器,它将发出元素的点击事件警报。

对于我们的脚本,我们只需使用 document.importNode 附加内容,我们就可以获得一个大部分包含的 HTML 模板,可以从一个页面移动到另一个页面。

查看 CodePen 上 Caleb Williams 的示例
带有脚本和样式的模板演示
@calebdwilliams
CodePen 上。

为我们的对话框创建模板

回到我们创建对话框元素的任务,我们想要定义模板的内容和样式。

<template id="one-dialog">
  <script>
    document.getElementById('launch-dialog').addEventListener('click', () => {
      const wrapper = document.querySelector('.wrapper');
      const closeButton = document.querySelector('button.close');
      const wasFocused = document.activeElement;
      wrapper.classList.add('open');
      closeButton.focus();
      closeButton.addEventListener('click', () => {
        wrapper.classList.remove('open');
        wasFocused.focus();
      });
    });
  </script>
  <style>
    .wrapper {
      opacity: 0;
      transition: visibility 0s, opacity 0.25s ease-in;
    }
    .wrapper:not(.open) {
      visibility: hidden;
    }
    .wrapper.open {
      align-items: center;
      display: flex;
      justify-content: center;
      height: 100vh;
      position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
      opacity: 1;
      visibility: visible;
    }
    .overlay {
      background: rgba(0, 0, 0, 0.8);
      height: 100%;
      position: fixed;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
      width: 100%;
    }
    .dialog {
      background: #ffffff;
      max-width: 600px;
      padding: 1rem;
      position: fixed;
    }
    button {
      all: unset;
      cursor: pointer;
      font-size: 1.25rem;
      position: absolute;
        top: 1rem;
        right: 1rem;
    }
    button:focus {
      border: 2px solid blue;
    }
  </style>
  <div class="wrapper">
  <div class="overlay"></div>
    <div class="dialog" role="dialog" aria-labelledby="title" aria-describedby="content">
      <button class="close" aria-label="Close">&#x2716;&#xfe0f;</button>
      <h1 id="title">Hello world</h1>
      <div id="content" class="content">
        <p>This is content in the body of our modal</p>
      </div>
    </div>
  </div>
</template>

此代码将作为我们对话框的基础。简要分解一下,我们有一个全局关闭按钮、一个标题和一些内容。我们还添加了一些行为来视觉上切换我们的对话框(尽管它尚不可访问)。不幸的是,样式和脚本内容没有作用域到我们的模板,而是应用于整个文档,导致当将多个模板实例添加到 DOM 时出现不太理想的行为。在下一篇文章中,我们将使用自定义元素并创建一个自己的元素,该元素实时使用此模板并封装元素的行为。

查看 CodePen 上 Caleb Williams 的示例
带有脚本的对话框模板
@calebdwilliams
CodePen 上。

文章系列

  1. Web Components 简介
  2. 创建可复用的 HTML 模板(本文
  3. 从头开始创建自定义元素
  4. 使用 Shadow DOM 封装样式和结构
  5. Web Components 的高级工具