当今前端编程中,利用语义化的 HTML 结合 CSS 来完一个组件并不是一件难事,这也意味着无论在 React、Vue 中都可以插入,不过它俩不是今天的主角,接下来我将用一个例子来介绍如何封装一个完整的原生 HTML 的 Web Components 组件,让我们开始吧!
HTML结构
首先我们来了解下 HTML 中的 <details>
元素,它可以用于创建一个小部件,其中包含仅在小部件处于“打开”状态时才可见的附加信息,<details>
元素内可以包含的内容没有任何限制。
默认情况下,元素创建的小部件<details>
处于“关闭”状态(open
标签可使其打开)。通过单击小部件在“打开”和“关闭”状态之间切换,以显示或隐藏标签中包含的附加信息,内部标签 <summary>
元素则可为该部件提供概要。
一个简单的例子如下:
1 2 3 4
| <details> <summary> 不能说的秘密 </summary> 藏的这么深,可还是被你发现了 </details>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| details { border: 1px solid #aaa; border-radius: 4px; padding: .5em .5em 0; }
summary { font-weight: bold; margin: -.5em -.5em 0; padding: .5em; }
details[open] { padding: .5em; }
details[open] summary { border-bottom: 1px solid #aaa; margin-bottom: .5em; }
|
使用语义化 HTML 的优点:页面内容结构更清晰,方便开发者阅读,更利于浏览器的理解与加载,搜索引擎解析与SEO优化。
添加亿点样式
原生元素默认的样式很简陋,因此我们需要为其定制一下样式,这块内容我们简单带过,只讲解关键部分,样式内容有省略,具体可以在文末的码上掘金中看到呈现效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| .ContentWarning > summary { position: relative; list-style: none; user-select: none; cursor: pointer; --stripe-color: rgb(0 0 0 / 0.1); background-image: repeating-linear-gradient(45deg, transparent, transparent 0.5em, var(--stripe-color) 0.5em, var(--stripe-color) 1em); }
.ContentWarning>summary: hover, .ContentWarning>summary: focus { --stripe-color: rgb(150 0 0 / 0.1); }
|
封装模板
现在我们来把它封装成一个完整的组件,这需要先将 HTML 编写在模板 template
当中,并设置一个 id
,如下所示:
1 2 3 4 5 6 7 8
| <template id="warning-card"> <details class="ContentWarning"> <summary> <strong>⚠️ 注意:</strong> 以下为隐藏内容 </summary> <slot name="desc"> 藏的这么深,可还是被你发现了 </slot> </details> </template>
|
熟悉 Vue
的小伙伴应该很容易理解上面的代码,结构很相似,不过网页不会直接渲染它包裹的内容。此外我们还对此模板设置了一个插槽 slot
,后面会讲到它的作用。
定义组件
有了上面封装好的模板,我们就需要在 JS 中定义成可用组件来让其能够被使用,调用 window
下的 customElements.define
方法,第一个参数是传入组件名称,我们定义组件名为: warning-card ,第二个参数传入一个继承了 HTMLElement
的类,在其构造方法当中获取并克隆一个新的 HTML
节点,它会通过 appendChild
渲染到页面当中。
1 2 3 4 5 6 7 8 9
| window.customElements.define('warning-card', class extends HTMLElement { constructor() { super(); var templateElem = document.getElementById('warning-card'); var content = templateElem.content.cloneNode(true); this.appendChild(content); } })
|
接着我们就可以在页面中把它当作组件那样使用了:
1
| <warning-card> </warning-card>
|
插槽与传参
回头看看上面我们模板中设置的插槽 slot
,此时还是没有生效的,我们需要稍微改写一下构造函数中的渲染方式,将 web 组件定义为一个 Shadow DOM,这样构造的是一个可以将标记结构、样式和行为隐藏起来,并与页面上的其他代码相隔离,保证不同的部分不会混在一起的独立元素,并在最后使用 Node.cloneNode()
方法添加了模板的拷贝到 Shadow 的根结点上。
1 2 3 4 5 6 7 8
| window.customElements.define('warning-card', class extends HTMLElement { constructor() { super(); var template = document.getElementById('warning-card').content; this.attachShadow({ mode: 'open' }).appendChild(template.cloneNode(true)); } })
|
现在我们尝试使用下组件,往其内容添加一个图片,指向名为 desc
的 slot
插槽中:
1 2 3
| <warning-card> <img slot="desc" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ba825ffee78c4a1b9c0232e5d2f1d048~tplv-k3u1fbpfcp-watermark.image?" /> </warning-card>
|
这时你会发现,图片插入到 details
元素的隐藏区域当中了,slot
已经成功生效,但是样式却消失了,这时因为组件已经被完全隔离,我们需要将样式作用在其内部才会生效。
1 2 3 4 5 6 7 8 9 10 11 12
| <template id="warning-card"> <style> </style> <details class="ContentWarning"> <summary> <strong>⚠️ 注意:</strong> </summary> <slot name="desc">THE DESCRIPTION</slot> </details> </template>
|
这样组件就正常了:
除了定制模板中的插槽,我们也可以通过 HTML
标签属性来实现一些简单的传参,例如在 summary
标签中显示一个标题:
1 2
| <warning-card title="前方高能"> </warning-card>
|
我们只需要在模板中定义好这个标题的位置:
1 2 3 4 5 6 7 8
| <template id="warning-card"> <details class="ContentWarning"> <summary> <strong>⚠️ 注意:</strong> <span id="title"></span> </summary> </details> </template>
|
最后在构造函数中我们通过 document
的原生方法写入模板中就可以了:
1 2 3 4 5 6 7 8 9 10
| window.customElements.define('warning-card', class extends HTMLElement { constructor() { super(); var template = document.getElementById('warning-card').content; template.querySelector('#title').innerText = this.getAttribute('title'); this.attachShadow({ mode: 'open' }).appendChild(template.cloneNode(true)); } })
|
结束
至此,我们通过一个简单的原生组件学习了如何编写 Web Components,可以在此代码片段中查看具体源码。
以上就是文章的全部内容,希望对你有所帮助!如果觉得文章写的不错,可以点赞收藏,也欢迎关注,我会持续更新更多前端有用的知识与实用技巧,我是茶无味de一天,希望与你共同成长~