68

customElements 实战之 Lite-embed

 5 years ago
source link: https://semlinker.com/lite-embed/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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 站的某个视频为例,它的原始地址是:

https://www.bilibili.com/video/av53834726?spm_id_from=333.851.b_62696c695f7265706f72745f616
e696d65.73

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

<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 使用起来也很简单,具体如下:

<!--  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 属性。

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 接口定义:

export interface EmbedOption {
  site: string
  height: number
  source: string
  embed: string
  html: string
  preconnects: string[]
}

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

fA3Ufy6.jpg!web

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

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 类

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

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 钩子中完成播放按钮的创建和设置相关的事件监听,相关的处理逻辑比较简单,我们直接上代码:

构造函数

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')
  }
}

生命周期钩子

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 标签,相关的代码如下:

static warmConnections(preconnects: string[]) {
    preconnects.forEach(preconnect =>
      LiteEmbed.addPrefetch('preconnect', preconnect)
    )
}

2.3 懒加载 iframe 内嵌网页

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

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 元素,代码很简单一行就搞定了:

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技术栈最新文章。


Recommend

  • 41

    “We’re finally finished removing jQuery from https://t.co/r2QL2aHBfa frontend. What did we replace it with? No framework whatsoever: • querySelectorAll, • fetch for ajax, • delegated-events for event handling, • polyfills for standard...

  • 89
    • www.tuicool.com 5 years ago
    • Cache

    Firenvim, embed Neovim in your browser

    Firenvim Turn your browser into a Neovim client. How to use Just click on any textarea and it will be immedi...

  • 38

    customElements 实战之 Lite-embed创建了一个 “重学TypeScript” 的微信群,想加群的小伙伴,加我微信 “semlinker”,备注 “1” 。阿里...

  • 24
    • thephd.github.io 5 years ago
    • Cache

    std::embed - All the Details

    You didn’t think I’d just have an irony post and not get to all the juicy technical bits, right dear reader?Though, to be fair, the reddit...

  • 27

    The Ape Programming Language About Ape is an easy to use programming language and library written in C. It's an offspring of Monkey language (from

  • 17

    How to embed YouTube videos in Laravel: The easy way Monday, 21 Se...

  • 17
    • cwestblog.com 4 years ago
    • Cache

    Embed Office Documents On Your Site

    Embed Office Documents On Your Site – Chris West's BlogHave you ever wanted to display an Excel workbook or a Word document on your website? Of course, it is possible to save files as HTML but it just doesn’t have the same feel. Fortunately...

  • 11

    #embed was reviewed by Evolution Working Group (EWG) September 2nd, 2020 and good progress was made to get the paper in shape to get it into C++23. The resulting directive, however, is a lot less powerful than people might have w...

  • 7

    How to Embed an interactive Python interpreter console Published: Thursday 14th August 2014 There's a few things you can...

  • 11
    • channel9.msdn.com 4 years ago
    • Cache

    Embed

    Description Did you know you can take an existing web app experience and adapt it for Teams? Barnam Bora and Waldek Mastykarz will show you how you can display rich interactive web content within Microsoft Teams clie...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK