时间:2023-06-13 06:33:02 | 来源:网站运营
时间:2023-06-13 06:33:02 来源:网站运营
【翻译】创建可重用的 HTML 模板:本文翻译自 Crafting Reusable HTML Templates | CSS-Tricks在上篇文章中,我们从高层次讨论了 Web 组件规范(自定义元素,影子 DOM,以及 HTML 模板)。在本文以及接下来的三篇文章中,我们将测试这些技术,并对它们进行更详细的研究,看看如何在现代生成中使用它们。为此,我们将从零开始构建一个自定义模态对话框,看看各种技术是如何结合到一起的。
作者:Caleb Williams
发布日期:2019-03-19
更新日期:2019-03-22
文章系列:
第一部分:Web 组件介绍(也就是本文)
第二部分:创建可重用的 HTML 模板
第三部分:从零开始创建自定义元素
第四部分:用影子 DOM 封装样式和结构
第五部分:Web 组件的高级工具
<template>
元素。在本系列第一篇文章中,我们将模板元素定义为“HTML 中直到调用才会渲染的用户自定义模板”。换句话说,模板是被浏览器忽略直到被告知的 HTML 代码。<template>
就是 HTML 元素,因此包含内容的模板的最简单形式是:<template> <h1>Hello world</h1></template>
在浏览器中执行上面这段代码只会看到一个空白屏幕,因为浏览器不会呈现模板元素的内容。这种机制非常强大,因为它允许我们定义内容(或内容结构)并保存以备将来使用——而不是在 JavaScript 中编写 HTML 代码。const template = document.querySelector('template');const node = document.importNode(template.content, true);document.body.appendChild(node);
真正的魔法发生在 document.importNode
方法中。该方法将创建模板内容的一个副本,并准备将其插入到另一个文档(或文档片段)中。函数的第一个参数获取模板的内容,第二参数告诉浏览器对元素的 DOM 子树(即其所有子树)进行深度赋值。template.content
,但这样做会将移除元素的内容并以后将其添加到文档的 body 中。任何 DOM 节点都只能在一个位置连接,所以后续使用模板的内容将导致一个空的文档片段(本质上是空值),因为内容先前已经被移动。使用 documnet.importNode
方法允许我们在多个位置重用相同的模板内容。document.body
中,并呈现给用户。这最终允许我们做一些有趣的事情,比如为用户(或我们程序的使用者)提供创建内容的模板,类似于下面的 demo,我们在上一篇文章中提到过:<template id="book-template"> <li><span class="title"></span> — <span class="author"></span></li></template><template id="book-template-2"> <li><span class="author"></span>'s classic novel <span class="title"></span></li></template><ul id="books"></ul><fieldset id="templates"> <legend>Choose template</legend> <label> <input type="radio" name="template" value="book-template" checked> Template One </label> <label> <input type="radio" name="template" value="book-template-2"> Template Two </label></fieldset>label { display: block; margin-bottom: 0.5rem;}'use strict';const books = [ { title: 'The Great Gatsby', author: 'F. Scott Fitzgerald' }, { title: 'A Farewell to Arms', author: 'Ernest Hemingway' }, { title: 'Catch 22', author: 'Joseph Heller' }];function appendBooks(templateId) { const booksList = document.getElementById('books'); const fragment = document.getElementById(templateId); // Clear out the content from the ul booksList.innerHTML = ''; // Loop over the books and modify the given template books.forEach(book => { // Create an instance of the template content const instance = document.importNode(fragment.content, true); // Add relevant content to the template instance.querySelector('.title').innerHTML = book.title; instance.querySelector('.author').innerHTML = book.author; // Append the instance ot the DOM booksList.appendChild(instance); }); }document.getElementById('templates').addEventListener('change', (event) => appendBooks(event.target.value));appendBooks('book-template');
在本例中,我们提供了两个模板来呈现相同的内容——作者和他们所写的书籍。当表单更改时,我们选择呈现与该值关联的模板。我们最终会使用这种技术创建一个自定义元素,该元素将使用稍后定义的模板。<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
,一个全局的 CSS 选择器目标时按钮的 ID,以及一个简单的事件监听器,它将提醒元素的 click 事件。document.inportNode
附加内容,并且我们有一个主要包含 HTML 的模板,可以在页面之间移动。'use strict';const template = document.getElementById('template');document.body.appendChild( document.importNode(template.content, true));
<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">✖️</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>
这段代码将作为我们对话框的基础。简单的分析一下,我们有一个全局关闭按钮、一个标题、一些内容。 我们还添加了一些行为来直观地切换对话框(虽然目前还不可用)。#launch-dialog { background: tomato; border-radius: 4px; color: #fff; font-family: Helvetica, Arial, sans-serif; padding: 0.5rem 1rem; position: static;}const template = document.getElementById('dialog-template');document.body.appendChild( document.importNode(template.content, true));
结果:文章系列:
第一部分:Web 组件介绍(也就是本文)
第二部分:创建可重用的 HTML 模板
第三部分:从零开始创建自定义元素
第四部分:用影子 DOM 封装样式和结构
第五部分:Web 组件的高级工具
Laxman March 19, 2019
我最近不在模板里的<style>
标签中直接写样式了,而是使用<style>@import '/path/to/component.css';</style>
。
这样,我可以轻松的维护样式,并且还可以从中包含其他样式,比如您想要添加的普通全局样式,它们不能直接穿影子 DOM。
Caleb Williams March 20, 2019
Hey Laxman,这是一个完全合法的策略,如果你绝对确定你知道引用样式的路径,这种策略非常有意义。 然而,对于本文示例,我希望确保涵盖最基本的内容,而不深入了解导入样式的最佳方式(这点将在影子 DOM 文章中详细介绍)。
Glenn March 19, 2019
模板中的样式和脚本元素是否以任何方式限定了作用范围?或者他们只是作为一个整体附加到整个文档中,前者遵循样式的常规级联规则,后者插入到相同的 JavaScript 命名空间中?如果没有作用域限制,那脚本中的函数将在每次使用模板时复制一次,并且每次都会覆盖上一个副本的名称?而样式将在级联中不停堆叠?这两种机制都不会使用多实例模板中的样式和脚本有啥吸引力。
Caleb Williams March 20, 2019
Hey Glenn,多谢提问。不对,模板中的样式和脚本不受范围限制,所以使用这种使用元素的方式并不是一个很好的策略。模板节点实际上更多用于 HTML,而不是样式或脚本,本文我仅仅是想证明它们可以以这种方式使用,尽管这种方式不一定好。在后续的两篇文章中,我们将讨论如何利用自定义元素和影子 DOM 来进一步优化此代码。
Konstantin March 20, 2019
不错的系列。我真的很喜欢你可以尽可能简短地解释 Web 组件,但又不遗漏主要内容。但有件事很困扰我。您使用document.importNode()
而不是Node.cloneNode()
(例如,fragment.content.cloneNode(true);
)有什么特殊原因吗?
请务必回复。
Caleb Williams March 20, 2019
这两者之间并没有太大的区别。如果我没记错的话,我认为如果文档不同,使用cloneNode
将隐含地采用节点(它们可能是模板节点是文档片段)。所以在本例中,document.importNode
更显示。
Andrew April 20, 2019
我在前端开发中遇到一个小问题 & 我正在使用 HTML/CSS 以及 bootstrap 框架构建自己的网站。我想做的是将我的主页元素(包括顶部导航栏、页脚等)设置为模板,并在每个页面上调用它们。Web 组件是实现这一点的好方法吗?或者是否存在一种更简单/更少步骤的方法实现这一点(要用 JavaScript 也没关系)?
关键词:模板,创建,翻译