前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >customElements 实战之 Lite-embed

customElements 实战之 Lite-embed

作者头像
阿宝哥
发布2019-11-29 00:51:53
1.5K0
发布2019-11-29 00:51:53
举报
文章被收录于专栏:全栈修仙之路全栈修仙之路

一、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 方式快速地嵌入第三方站点,如 BilibiliYoukuQQYoutubeVimeoCodepen 等。

通过扩展 Lite-embed 项目中 services.ts 服务类的匹配规则,开发者可以方便地内嵌其它支持 iframe 方式嵌入的站点,除此之外基于 services.ts 服务类,也可以让富文本编辑器支持自动解析剪贴板中的网址,自动以 iframe 的方式嵌入所指定的内容。这里我们以 B 站的某个视频为例,它的原始地址是:

代码语言:javascript
复制
https://www.bilibili.com/video/av53834726?spm_id_from=333.851.b_62696c695f7265706f72745f616
e696d65.73

其对应的 iframe 内嵌代码如下:

代码语言:javascript
复制
<iframe src="//player.bilibili.com/player.html?aid=53834726&cid=94168196&page=1" 
   scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe>

当用户需要嵌入上述网址对应的视频时,一般需要手动点击视频下方的分享链接,然后复制上述的 iframe 内嵌代码,再添加到目标页面中。Lite-embed 所实现的功能之一就是实现自动解析,即根据设置的地址,按照一定的匹配规则,最终生成对应的 iframe 内嵌代码。对于上述的需求,Lite-embed 使用起来也很简单,具体如下:

代码语言:javascript
复制
<!--  Bilibili -->
<h2>www.bilibili.com</h2>
<lite-embed src="https://www.bilibili.com/video/av53834726?
   spm_id_from=333.851.b_62696c695f7265706f72745f616e696d65.73" height="200">
</lite-embed>

当然如果只是实现上述功能的话,那么 Lite-embed 并没有多大的意义。Lite-embed 除了实现自动解析功能之外,还实现了在悬停视频封面或海报时,预热(可能)要使用的 TCP 连接和 iframe 内嵌网页懒加载的功能。

二、Lite-embed 开发实战

2.1 实现自动解析

前面我们已经简单介绍了 Lite-embed 的功能,下面我们来介绍一下如何一步步实现 Lite-embed 组件。首先我们先来定义 LiteEmbed 类,该类继承于 HTMLElement 类,在 LiteEmbed 类中除了前面示例中使用的 src 和 height 属性之外,我们还定义了 posterUrl、prefetchUrlSet 和 embedOption 属性。

代码语言:javascript
复制
class LiteEmbed extends HTMLElement {
  static prefetchUrlSet = new Set() // 预取URL链接集合
  private src: string // 内嵌网页的url地址
  private height: number // 高度
  private posterUrl: string // 封面url地址
  private embedOption: EmbedOption | null // 内嵌站点的配置信息
}

embedOption 属性的类型是 EmbedOption,它用于表示内嵌站点的配置信息,EmbedOption 接口定义:

代码语言:javascript
复制
export interface EmbedOption {
  site: string
  height: number
  source: string
  embed: string
  html: string
  preconnects: string[]
}

接着我们来介绍如何实现自动解析,要实现自动解析的前提是原始 url 地址和 iframe 内嵌地址这两个地址之间存在一定的映射规则。以 B 站为例,它们之间的映射规则如下:

bilibili-url-mapping
bilibili-url-mapping

通过观察上图可知原始 url 地址上的 av 字符串之后的序列号对应 iframe src 地址中 aId 参数的值。所以我们可以利用正则表达式来实现地址的映射,具体如下:

代码语言:javascript
复制
bilibili: {
  regex: /https?:\/\/www\.bilibili\.com\/video\/av([^?]+)?.+/,
  embedUrl: 'https://player.bilibili.com/player.html?aid=<%= remote_id %>&page=1',
  html: `<iframe scrolling='no' frameborder='no' allowtransparency='true' 
   allowfullscreen='true' style='width: 100%;' height="{{HEIGHT}}" src="{{SRC}}"></iframe>`,
  height: 498,
  preconnects: ['https://player.bilibili.com', 'https://api.bilibili.com', 
   'https://s1.hdslb.com']
},

上面除了定义了地址映射相关的 regex、embedUrl 和 html 三个属性之外,我们还定义了 height 和 preconnects 属性,分别表示 iframe 的默认高度和预链接地址列表。除了 B 站之外,目前 Lite-embed 还支持 YoukuQQYoutubeVimeoCodepen 等站点,为了统一处理映射规则并方便后期扩展,我们来新增一个 Matcher 类,具体代码如下:

Matcher 类

代码语言:javascript
复制
export default class Matcher {
  static matches(url: string): EmbedOption | null {
    if (!url) return null
    let result = null
    for (let site of Object.keys(RULES)) {
      if ((result = Matcher.match(site, url)) != null) {
        return result
      }
    }
    return result
  }

  static match(site: string, url: string): EmbedOption | null {
    // const defaultIdsHandler = (ids: string[]) => ids.shift()!
    const { regex, embedUrl, html, height, id = defaultIdsHandler, preconnects } = 
      RULES[site]
    const matches: RegExpExecArray | null = regex.exec(url)
    if (matches != null) {
      const result = matches.slice(1)
      const embed = embedUrl.replace(/<\%\= remote\_id \%\>/g, id(result))
      return {
        site,
        source: url,
        height,
        embed,
        preconnects,
        html
      }
    }
    return null
  }
}

在 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 方法,该方法用于实现预加载或预链接,具体实现如下:

代码语言:javascript
复制
static addPrefetch(kind: string, url: string, as?: string) {
    if (LiteEmbed.prefetchUrlSet.has(url)) return // 避免创建重复的link元素
    const linkElem = document.createElement('link')
    linkElem.rel = kind
    linkElem.href = url
    if (as) {
      (linkElem as any).as = as
    }
    linkElem.crossOrigin = 'true'
    document.head.appendChild(linkElem)
    LiteEmbed.prefetchUrlSet.add(url)
}

接着我们来介绍另一个知识点 —— 自定义元素的生命周期钩子。自定义元素可以定义特殊生命周期钩子,以便在其存续的特定时间内运行代码。 这称为自定义元素响应。目前自定义元素支持的生命周期钩子如下:

名称

调用时机

constructor

创建或升级元素的一个实例。用于初始化状态、设置事件侦听器或创建 Shadow DOM。参见规范,了解可在 constructor 中完成的操作的相关限制。

connectedCallback

元素每次插入到 DOM 时都会调用。用于运行安装代码,例如获取资源或渲染。一般来说,您应将工作延迟至合适时机执行。

disconnectedCallback

元素每次从 DOM 中移除时都会调用。用于运行清理代码(例如移除事件侦听器等)。

attributeChangedCallback(attrName, oldVal, newVal)

属性添加、移除、更新或替换。解析器创建元素时,或者升级时,也会调用它来获取初始值。Note: 仅 observedAttributes 属性中列出的特性才会收到此回调。

adoptedCallback()

自定义元素被移入新的 document(例如,有人调用了 document.adoptNode(el))。

下面我们将使用 constructor 和 connectedCallback 钩子,在 constructor 钩子中完成 LiteEmbed 类相关属性的初始化,在 connectedCallback 钩子中完成播放按钮的创建和设置相关的事件监听,相关的处理逻辑比较简单,我们直接上代码:

构造函数

代码语言:javascript
复制
class LiteEmbed extends HTMLElement {  
  constructor() {
    super()
    this.src = this.getAttribute('src') || ''
    this.height = Number(this.getAttribute('height'))
    this.posterUrl =
      this.getAttribute('poster-url') || 'https://i.ytimg.com/vi/ogfYd705cRs/hqdefault.jpg'
    this.embedOption = Matcher.matches(this.src)
    LiteEmbed.addPrefetch('preload', this.posterUrl, 'image')
  }
}

生命周期钩子

代码语言:javascript
复制
connectedCallback() {
    if (this.embedOption != null) {
      // 设置背景图片
      this.style.backgroundImage = `url("${this.posterUrl}")`
      this.style.height = this.getAttribute('height') || this.embedOption.height.toString()

      // 创建播放按钮
      const playBtn = document.createElement('div')
      playBtn.classList.add('lte-playbtn')
      this.appendChild(playBtn)

      // 鼠标悬停时,预热(可能)要使用的TCP连接。
  		// once: true 表示listener在添加之后最多只调用一次。如果是true, 
      // listener会在其被调用之后自动移除。
      this.addEventListener(
        'pointerover',
        () => LiteEmbed.warmConnections(this.embedOption!.preconnects),
        { once: true }
      )
      // 一旦用户点击,添加实际的iframe
      this.addEventListener('click', e => this.addIframe())
    }
}

在 connectedCallback 方法中,我们监听 pointerover 事件,在该事件触发后,我们调用 warmConnections 方法提前预热可能要使用的 TCP 链接,warmConnections 方法内部的逻辑也简单就是遍历预设的 preconnects 数组,然后动态创建 link 标签,相关的代码如下:

代码语言:javascript
复制
static warmConnections(preconnects: string[]) {
    preconnects.forEach(preconnect =>
      LiteEmbed.addPrefetch('preconnect', preconnect)
    )
}
2.3 懒加载 iframe 内嵌网页

Lite-embed 组件要实现的最后一个功能就是懒加载 iframe 内嵌网页,即当用户点击海报或播放按钮的时候,才创建 iframe 元素进而开始加载内嵌网页。这里我们通过定义一个 addIframe 方法来实现该功能:

代码语言:javascript
复制
addIframe() {
    if (this.embedOption != null) {
      const finalEmbedOption = {
        ...this.embedOption,
        ...{ height: this.height, src: this.embedOption.embed }
      }
      const iframeHTML = this.embedOption.html.replace(
        /\{\{(\w*)\}\}/g,
        (m: string, key: string) => {
          return (finalEmbedOption as any)[key.toLowerCase()]
        }
      )
      this.insertAdjacentHTML('beforeend', iframeHTML)
      this.classList.add('lyt-activated')
    }
}

至此 Lite-embed 的所有功能已经介绍完了,就差最后一步即定义 lite-embed 元素,代码很简单一行就搞定了:

代码语言:javascript
复制
customElements.define('lite-embed', LiteEmbed)

三、总结

本文详细介绍了如何利用 customElements Web Components 规范来开发 Lite-embed 组件,该组件虽然带了一些好处,比如提高嵌入页面的加载速度,但同时也存在一些问题,比如在点击视频封面或海报时,才开始动态加载 iframe,会造成需要二次点击才能正常播放嵌入的视频。对 Lite-embed 组件感兴趣的小伙伴可以访问 lite-embed,具体的项目地址如下:

https://github.com/semlinker/lite-embed

四、参考资源


欢迎小伙伴们订阅前端全栈修仙之路,及时阅读 Angular、TypeScript、Node.js/Java和Spring技术栈最新文章。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019/11/27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、Lite-embed 简介
  • 二、Lite-embed 开发实战
    • 2.1 实现自动解析
      • 2.2 预热 TCP 链接
        • 2.3 懒加载 iframe 内嵌网页
        • 三、总结
        • 四、参考资源
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档