customElements 实战之 Lite-embed
本文于593天之前发表,文中内容可能已经过时。
创建了一个 “重学TypeScript” 的微信群,想加群的小伙伴,加我微信 “semlinker”,备注 “1” 。阿里、京东、腾讯的大佬都在群里等你哟。
一、Lite-embed 简介
Lite-embed 的灵感来源于 paulirish 大神的 lite-youtube-embed 项目:
Provide videos with a supercharged focus on visual performance. This custom element renders just like the real thing but approximately 224X faster.
提供具有视觉效果的视频。这个自定义元素的渲染方式与真实的效果一样,但是速度提高了约 224 倍。
Lite-embed 是基于 customElements Web Components 规范开发的组件,支持以 iframe 方式快速地嵌入第三方站点,如 Bilibili、Youku、QQ、Youtube、Vimeo 和 Codepen 等。
通过扩展 Lite-embed 项目中 services.ts 服务类的匹配规则,开发者可以方便地内嵌其它支持 iframe 方式嵌入的站点,除此之外基于 services.ts 服务类,也可以让富文本编辑器支持自动解析剪贴板中的网址,自动以 iframe 的方式嵌入所指定的内容。这里我们以 B 站的某个视频为例,它的原始地址是:
1 | https://www.bilibili.com/video/av53834726?spm_id_from=333.851.b_62696c695f7265706f72745f616 |
其对应的 iframe 内嵌代码如下:
1 | <iframe src="//player.bilibili.com/player.html?aid=53834726&cid=94168196&page=1" |
当用户需要嵌入上述网址对应的视频时,一般需要手动点击视频下方的分享链接,然后复制上述的 iframe 内嵌代码,再添加到目标页面中。Lite-embed 所实现的功能之一就是实现自动解析,即根据设置的地址,按照一定的匹配规则,最终生成对应的 iframe 内嵌代码。对于上述的需求,Lite-embed 使用起来也很简单,具体如下:
1 | <!-- Bilibili --> |
当然如果只是实现上述功能的话,那么 Lite-embed 并没有多大的意义。Lite-embed 除了实现自动解析功能之外,还实现了在悬停视频封面或海报时,预热(可能)要使用的 TCP 连接和 iframe 内嵌网页懒加载的功能。
二、Lite-embed 开发实战
2.1 实现自动解析
前面我们已经简单介绍了 Lite-embed 的功能,下面我们来介绍一下如何一步步实现 Lite-embed 组件。首先我们先来定义 LiteEmbed 类,该类继承于 HTMLElement 类,在 LiteEmbed 类中除了前面示例中使用的 src 和 height 属性之外,我们还定义了 posterUrl、prefetchUrlSet 和 embedOption 属性。
1 | class LiteEmbed extends HTMLElement { |
embedOption 属性的类型是 EmbedOption,它用于表示内嵌站点的配置信息,EmbedOption 接口定义:
1 | export interface EmbedOption { |
接着我们来介绍如何实现自动解析,要实现自动解析的前提是原始 url 地址和 iframe 内嵌地址这两个地址之间存在一定的映射规则。以 B 站为例,它们之间的映射规则如下:
通过观察上图可知原始 url 地址上的 av 字符串之后的序列号对应 iframe src 地址中 aId 参数的值。所以我们可以利用正则表达式来实现地址的映射,具体如下:
1 | bilibili: { |
上面除了定义了地址映射相关的 regex、embedUrl 和 html 三个属性之外,我们还定义了 height 和 preconnects 属性,分别表示 iframe 的默认高度和预链接地址列表。除了 B 站之外,目前 Lite-embed 还支持 Youku、QQ、Youtube、Vimeo 和 Codepen 等站点,为了统一处理映射规则并方便后期扩展,我们来新增一个 Matcher 类,具体代码如下:
Matcher 类
1 | export default class Matcher { |
在 Matcher 类中我们定义了两个静态方法,即 matches 和 match 方法。在 matches 方法内部会获取预设的规则,然后逐一进行地址匹配。而 match 方法内部实现的主要功能是地址的映射和参数的填充。介绍完自动解析的实现方式,接下来我们来介绍如何预热 TCP 链接。
2.2 预热 TCP 链接
在介绍如何预热 TCP 链接前,我们需要了解一些前置知识,如 HTML link 标签 rel 属性的一些特殊用途和自定义元素的生命周期钩子。
在实际开发中可以通过设置 link 标签 rel 属性来提升网页的渲染速度(有兼容性问题),常见的类型如下:
prefetch:提示浏览器提前加载链接的资源,因为它可能会被用户请求。建议浏览器提前获取链接的资源,因为它很可能会被用户请求。 从 Firefox 44 开始,考虑了
crossorigin
属性的值,从而可以进行匿名预取。preconnect:向浏览器提供提示,建议浏览器提前打开与链接网站的连接,而不会泄露任何私人信息或下载任何内容,以便在跟随链接时可以更快地获取链接内容。
preload:告诉浏览器下载资源,因为在当前导航期间稍后将需要该资源。
prerender:建议浏览器事先获取链接的资源,并建议将预取的内容显示在屏幕外,以便在需要时可以将其快速呈现给用户。
dns-prefetch:提示浏览器该资源需要在用户点击链接之前进行 DNS 查询和协议握手。
若需了解完整的链接类型,可以访问 MDN - Link Type。
为了支持动态添加 link 元素设置该元素对应的 rel 属性,我们来定义一个 addPrefetch 方法,该方法用于实现预加载或预链接,具体实现如下:
1 | static addPrefetch(kind: string, url: string, as?: string) { |
接着我们来介绍另一个知识点 —— 自定义元素的生命周期钩子。自定义元素可以定义特殊生命周期钩子,以便在其存续的特定时间内运行代码。 这称为自定义元素响应。目前自定义元素支持的生命周期钩子如下:
名称 | 调用时机 |
---|---|
constructor | 创建或升级元素的一个实例。用于初始化状态、设置事件侦听器或创建 Shadow DOM。参见规范,了解可在 constructor 中完成的操作的相关限制。 |
connectedCallback | 元素每次插入到 DOM 时都会调用。用于运行安装代码,例如获取资源或渲染。一般来说,您应将工作延迟至合适时机执行。 |
disconnectedCallback | 元素每次从 DOM 中移除时都会调用。用于运行清理代码(例如移除事件侦听器等)。 |
attributeChangedCallback(attrName, oldVal, newVal) | 属性添加、移除、更新或替换。解析器创建元素时,或者升级时,也会调用它来获取初始值。Note: 仅 observedAttributes 属性中列出的特性才会收到此回调。 |
adoptedCallback() | 自定义元素被移入新的 document (例如,有人调用了 document.adoptNode(el) )。 |
下面我们将使用 constructor 和 connectedCallback 钩子,在 constructor 钩子中完成 LiteEmbed 类相关属性的初始化,在 connectedCallback 钩子中完成播放按钮的创建和设置相关的事件监听,相关的处理逻辑比较简单,我们直接上代码:
构造函数
1 | class LiteEmbed extends HTMLElement { |
生命周期钩子
1 | connectedCallback() { |
在 connectedCallback 方法中,我们监听 pointerover
事件,在该事件触发后,我们调用 warmConnections 方法提前预热可能要使用的 TCP 链接,warmConnections 方法内部的逻辑也简单就是遍历预设的 preconnects 数组,然后动态创建 link 标签,相关的代码如下:
1 | static warmConnections(preconnects: string[]) { |
2.3 懒加载 iframe 内嵌网页
Lite-embed 组件要实现的最后一个功能就是懒加载 iframe 内嵌网页,即当用户点击海报或播放按钮的时候,才创建 iframe 元素进而开始加载内嵌网页。这里我们通过定义一个 addIframe 方法来实现该功能:
1 | addIframe() { |
至此 Lite-embed 的所有功能已经介绍完了,就差最后一步即定义 lite-embed 元素,代码很简单一行就搞定了:
1 | customElements.define('lite-embed', LiteEmbed) |
三、总结
本文详细介绍了如何利用 customElements Web Components 规范来开发 Lite-embed 组件,该组件虽然带了一些好处,比如提高嵌入页面的加载速度,但同时也存在一些问题,比如在点击视频封面或海报时,才开始动态加载 iframe,会造成需要二次点击才能正常播放嵌入的视频。对 Lite-embed 组件感兴趣的小伙伴可以访问 lite-embed,具体的项目地址如下:
四、参考资源
欢迎小伙伴们订阅全栈修仙之路,及时阅读 TypeScript、Node/Deno、Angular 技术栈最新文章。
