在我的上一篇文章中,我们发现 Web 组件并不像看起来那么可怕。我们看一个超级简单的设置,并制作了一个僵尸约会服务配置文件,其中包含一个自定义的 <zombie-profile>
元素。我们对每个配置文件重用该元素,并使用 <slot>
元素用唯一信息填充每个元素。
这是它的所有内容。
这很酷而且很有趣(好吧,我反正玩得很开心…),但如果我们通过使其具有交互性来更进一步呢?我们的僵尸资料很棒,但为了让这成为一种有用的、后启示录式的约会体验,你会想,你知道,“喜欢”一个僵尸,甚至给他们发消息。这就是我们将在本文中要做的。我们将把滑动留给另一篇文章。(对僵尸来说,向左滑动是正确的事吗?)
文章系列
- Web 组件比你想象的更容易
- 交互式 Web 组件比你想象的更容易
(您现在的位置) - 在 WordPress 中使用 Web 组件比你想象的更容易
- 用 Web 组件增强内置元素“比”你想象的更容易
- 上下文感知 Web 组件比你想象的更容易
- Web 组件伪类和伪元素比你想象的更容易
本文假设您对 Web 组件有一定程度的了解。如果您是新手,完全没问题——上一篇文章 将为您提供所需的一切。继续。阅读它。我会等你的。*扭动拇指*准备好了吗?好的。
首先,更新原始版本
让我们暂停一秒钟(好吧,也许更长)并看一下 ::slotted()
伪元素。它是在上一篇文章发布后被我注意到的(谢谢,罗斯!),它解决了我在封装方面遇到的一些(虽然不是全部)问题。如果你还记得,我们在组件的 <template>
之外有一些 CSS 样式,在 <template>
内的 <style>
元素中有一些样式。<template>
中的样式是封装的,但外部的样式则不是。
但这就是 ::slotted
发挥作用的地方。我们在选择器中声明一个元素,如下所示
::slotted(img) {
width: 100%;
max-width: 300px;
height: auto;
margin: 0 1em 0 0;
}
现在,放置在任何插槽中的任何 <img>
元素都将被选中。这帮助很大!
但这并不能解决我们所有的封装问题。虽然我们可以直接选择插槽中的任何内容,但我们无法选择插槽中元素的任何后代。因此,如果我们有一个带有子元素的插槽——例如僵尸配置文件的兴趣部分——我们无法从 <style>
元素中选择它们。此外,虽然 ::slotted
具有 出色的浏览器支持,但某些事情(例如选择伪元素,例如 ::slotted(span)::after
)将在某些浏览器(您好,Chrome)中运行,但在其他浏览器(您好,Safari)中将无法运行。
因此,虽然它并不完美,但 ::slotted
确实提供了比我们之前更多的封装。以下是更新以反映这一点的约会服务
回到交互式 Web 组件!
首先我想做的是添加一点动画来增添趣味。让我们让我们的僵尸配置文件图片在加载时淡入并向上平移。
当我第一次尝试这样做时,我使用 img
和 ::slotted(img)
选择器来直接动画化图像。但我只得到了 Safari 支持。Chrome 和 Firefox 不会在插槽图像上运行动画,但默认图像动画运行良好。为了使它正常工作,我在一个带有 .pic
类的 div 中包装了插槽,并将动画应用于该 div。
.pic {
animation: picfadein 1s 1s ease-in forwards;
transform: translateY(20px);
opacity: 0;
}
@keyframes picfadein {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
“喜欢”僵尸
对那个可爱的僵尸说“喜欢”不是很好吗?我是说从用户的角度来看,当然。这似乎是至少在线约会服务应该具备的功能。
我们将添加一个复选框“按钮”,它在点击时会启动一个心脏动画。让我们将这段 HTML 添加到 .info
div 的顶部
<input type="checkbox" id="trigger"><label class="likebtn" for="trigger">Like</label>
这是一个我整理好的心脏 SVG。我们知道僵尸喜欢可怕的东西,所以他们的心脏将是令人眼花缭乱的黄绿色
<svg viewBox="0 0 160 135" class="heart" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2"><path d="M61 12V0H25v12H12v13H0v36h12v13h13v12h12v12h12v12h12v13h13v12h12v-12h13v-13h11V98h13V86h-1 13V74h12V61h12V25h-12V12h-12V0H98v12H85v13H74V12H61z" fill="#7aff00"/></svg>
以下是添加到模板的 <style>
元素中的重要 CSS 部分
#trigger:checked + .likebtn {
/* Checked state of the .likebtn. Flips the foreground/background color of the unchecked state. */
background-color: #960B0B;
color: #fff;
}
#trigger {
/* With the label attached to the input with the for attribute, clicking the label checks/unchecks the box, so we can remove the checkbox. */
display: none;
}
.heart {
/* Start the heart off so small it's nigh invisible */
transform: scale(0.0001);
}
@keyframes heartanim {
/* Heart animation */
0% { transform: scale(0.0001); }
50% { transform: scale(1); }
85%, 100% { transform: scale(0.4); }
}
#trigger:checked ~ .heart {
/* Checking the checkbox initiates the animation */
animation: 1s heartanim ease-in-out forwards;
}
那里几乎是标准的 HTML 和 CSS。没有什么花哨的或严格的 Web 组件风格。但是,嘿,它有效!而且由于它技术上是一个复选框,所以“不喜欢”一个僵尸和“喜欢”一个僵尸一样容易。
给僵尸发消息
如果你是一个准备与人交往的末世单身人士,并且看到一个性格和兴趣与你相符的僵尸,你可能想给他们发消息。(请记住,僵尸并不关心外表——他们只对你的大脑感兴趣。)
让我们在“喜欢”一个僵尸后显示一个消息按钮。喜欢按钮是一个复选框这一事实再次派上用场,因为我们可以使用它的选中状态来有条件地显示消息选项,使用 CSS。这是添加到心脏 SVG 正下方的 HTML。只要它是 #trigger
元素的同级元素并且位于其后面,它几乎可以放置在任何位置。
<button type="button" class="messagebtn">Message</button>
一旦 #trigger
复选框被选中,我们就可以将消息按钮显示出来
#trigger:checked ~ .messagebtn {
display: block;
}
到目前为止,我们做得很好,避免了复杂性,但我们需要在这里使用一点 JavaScript。如果我们点击消息按钮,我们希望能够给那个僵尸发消息,对吧?虽然我们可以将该 HTML 添加到我们的 <template>
中,但为了演示目的,让我们使用一些 JavaScript 动态创建它。
我最初(天真)的假设是我们只需将 <script>
元素添加到模板中,创建一个封装的脚本,然后继续我们的快乐旅程。是的,这行不通。在模板中实例化的任何变量都会被多次实例化,而且,JavaScript 对彼此无法区分的变量很反感。*对恼人的 JavaScript 挥舞拳头*
你可能会更聪明地说:“嘿,我们已经为这个元素创建了一个 JavaScript 构造函数,那么为什么不把 JavaScript 放进去呢?”好吧,你说得对,你比我聪明。
让我们这样做,并将 JavaScript 添加到构造函数中。我们将添加一个监听器,一旦点击,就会创建一个表单并显示它,用于发送消息。以下是现在的样子,构造函数,聪明人
customElements.define('zombie-profile',
class extends HTMLElement {
constructor() {
super();
let profile = document.getElementById('zprofiletemplate');
let myprofile = profile.content;
const shadowRoot = this.attachShadow({
mode: 'open'
}).appendChild(myprofile.cloneNode(true));
// The "new" code
// Grabbing the message button and the div wrapping the profile for later use
let msgbtn = this.shadowRoot.querySelector('.messagebtn'),
profileEl = this.shadowRoot.querySelector('.profile-wrapper');
// Adding the event listener
msgbtn.addEventListener('click', function (e) {
// Creating all the elements we'll need to build our form
let formEl = document.createElement('form'),
subjectEl = document.createElement('input'),
subjectlabel = document.createElement('label'),
contentEl = document.createElement('textarea'),
contentlabel = document.createElement('label'),
submitEl = document.createElement('input'),
closebtn = document.createElement('button');
// Setting up the form element. The action just goes to a page I built that spits what you submitted back at you
formEl.setAttribute('method', 'post');
formEl.setAttribute('action', 'https://johnrhea.com/undead-form-practice.php');
formEl.classList.add('hello');
// Setting up a close button so we can close the message if we get shy
closebtn.innerHTML = "x";
closebtn.addEventListener('click', function () {
formEl.remove();
});
// Setting up form fields and labels
subjectEl.setAttribute('type', 'text');
subjectEl.setAttribute('name', 'subj');
subjectlabel.setAttribute('for', 'subj');
subjectlabel.innerHTML = "Subject:";
contentEl.setAttribute('name', 'cntnt');
contentlabel.setAttribute('for', 'cntnt');
contentlabel.innerHTML = "Message:";
submitEl.setAttribute('type', 'submit');
submitEl.setAttribute('value', 'Send Message');
// Putting all the elments in the Form
formEl.appendChild(closebtn);
formEl.appendChild(subjectlabel);
formEl.appendChild(subjectEl);
formEl.appendChild(contentlabel);
formEl.appendChild(contentEl);
formEl.appendChild(submitEl);
// Putting the form on the page
profileEl.appendChild(formEl);
});
}
});
到目前为止,一切都好!
在我们结束之前,还有一件事需要解决。没有什么比第一次尴尬的介绍更糟糕的了,所以让我们为这些末世约会提供便利,将僵尸的名字添加到默认消息文本中。这对用户来说是一个很好的便利。
由于我们知道 <zombie-profile>
元素中的第一个 span 始终是僵尸的名字,我们可以获取它并将它的内容放到一个变量中。(如果你的实现不同,并且元素的顺序发生变化,你可能想使用一个类来确保你始终获得正确的那个。)
let zname = this.getElementsByTagName("span")[0].innerHTML;
然后在事件监听器中添加这个
contentEl.innerHTML = "Hi " + zname + ",\nI like your braaains...";
这并不难,是吗?现在我们知道交互式 Web 组件与僵尸约会场景一样不吓人……好吧,你知道我的意思。一旦你克服了理解 Web 组件结构的最初障碍,它就开始变得更有意义了。现在你已经掌握了交互式 Web 组件技能,让我们看看你能想出什么!还有什么其他类型的组件或交互可以使我们的僵尸约会服务更好?把它做出来,并在评论中分享。
我感谢这个系列,并且几周来一直在想我是否应该投资学习 Web 组件,这源于看到 Shoelace 框架的质量以及总体理念,即最好投资于 Web 平台和通用知识而不是特定框架。
但我仍然对“消息”示例持怀疑态度。通过完全使用 JS 附加表单来创建它,将成为说服我们使用 Web 组件的示例吗?
绝对不是。我的意图是演示如何轻松地将封装的 JavaScript 添加到您的 Web 组件中。“消息”示例适合我的现有 Web 组件。我不是要它作为您决定是否使用 Web 组件的决定性因素。Web 组件是一个很棒的工具,但并非适用于所有情况。
当然,我不会根据一个例子来决定任何事情。就像每个人一样,我都在学习中。抱歉,我的话太直白了……!
也许对我来说看起来不太理想的代码可以通过 Web 组件之上的层(例如 lit-html)来解决。我刚刚在这里发现了这个系列 here,我想研究一下,但它也有两年历史了,所以对于像这样新事物来说,我有点担心。
抱歉,我一定误解了你的意思。
我不熟悉 lit-html,但我确信我的代码可以优化。
我没有深入研究过那个系列,但快速浏览了一下,没有发现任何变化。
我们可以再发布一篇关于创建表格 Web 组件或列表(例如 UL)Web 组件的 Web 组件博客文章吗?
我在处理一些看似基础的东西时遇到了一些意外问题。
:-D
嗨,迈克尔,我不确定是否能围绕它构建一篇文章,但我乐意尝试提供帮助。请留意我的电子邮件。