Skip to content

semlinker/lite-embed

Repository files navigation

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 等。

通过扩展本项目 services.ts 服务类的匹配规则,开发者可以方便的支持其它支持 iframe 方式嵌入的站点,除此之外基于 services.ts 服务类,也可以让富文本编辑器支持自动解析剪贴板中的网址,自动嵌入所匹配的站点。

快速上手

1、引入 lite-embed 脚本库

<script src="../dist/lite-embed.umd.js"></script>

2、设置内嵌的第三方站点的链接地址

<!-- Youku --> <h2>v.youku.com</h2> <lite-embed src="https://v.youku.com/v_show/id_XNDM4NDc2NTUzNg==.html?spm=a2ha1.12675304.m_7182_c_14738.d_1&s=edca0b0e0fff464ea306&scm=20140719.rcmd.7182.show_edca0b0e0fff464ea306" height="200"> </lite-embed> <!-- QQ --> <h2>v.qq.com</h2> <lite-embed src="https://v.qq.com/x/cover/mzc0020095v1n4t/q0032kben9j.html" height="200"> </lite-embed> <!-- Bilibili --> <h2>www.bilibili.com</h2> <lite-embed src="https://www.bilibili.com/video/av76507858?spm_id_from=333.851.b_7265706f7274466972737431.12" height="200"> </lite-embed> <!-- Youtube --> <h2>www.youtube.com</h2> <lite-embed src="https://www.youtube.com/watch?v=c5GAS_PMXDs" height="200"></lite-embed> <!-- Vimeo --> <h2>vimeo.com</h2> <lite-embed src="https://vimeo.com/265045525" height="200"> </lite-embed> <!-- Codepen --> <h2>codepen.io</h2> <lite-embed src="https://codepen.io/aaroniker/pen/ZEEWoKj" height="200"> </lite-embed>

Lite-embed 原理解析

Lite-embed 组件通常以下的手段来提升 iframe 内嵌站点的加载体验:

  • 在悬停(或点击)视频封面或海报时,预热(可能)要使用的 TCP 连接。
  • 在点击视频封面或海报时,才开始动态加载 iframe。

备注:在点击视频封面或海报时,才开始动态加载 iframe,也带来一定的问题,即需要二次点击才可以正常播放嵌入的视频。

构造函数

class LiteEmbed extends HTMLElement { static preconnected: boolean private src: string private height: number private posterUrl: string private embedOption: EmbedOption | null 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()) } }

自定义元素可以定义特殊生命周期钩子,以便在其存续的特定时间内运行代码。 这称为自定义元素响应。目前支持的生命周期钩子如下:

名称 调用时机
constructor 创建或升级元素的一个实例。用于初始化状态、设置事件侦听器或创建 Shadow DOM。参见规范,了解可在 constructor 中完成的操作的相关限制。
connectedCallback 元素每次插入到 DOM 时都会调用。用于运行安装代码,例如获取资源或渲染。一般来说,您应将工作延迟至合适时机执行。
disconnectedCallback 元素每次从 DOM 中移除时都会调用。用于运行清理代码(例如移除事件侦听器等)。
attributeChangedCallback(attrName, oldVal, newVal) 属性添加、移除、更新或替换。解析器创建元素时,或者升级时,也会调用它来获取初始值。Note:observedAttributes 属性中列出的特性才会收到此回调。
adoptedCallback() 自定义元素被移入新的 document(例如,有人调用了 document.adoptNode(el))。

addPrefetch 方法

该方法用于动态添加 link 标签并设置相应的 rel 属性来实现预加载或预链接:

static addPrefetch(kind: string, url: string, as?: string) { 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) }

在实际开发中可以通过设置 link 标签 rel 属性来提升网页的渲染速度(有兼容性问题),常见的类型如下:

  • prefetch:提示浏览器提前加载链接的资源,因为它可能会被用户请求。建议浏览器提前获取链接的资源,因为它很可能会被用户请求。 从 Firefox 44 开始,考虑了 crossorigin 属性的值,从而可以进行匿名预取。

  • preconnect:向浏览器提供提示,建议浏览器提前打开与链接网站的连接,而不会泄露任何私人信息或下载任何内容,以便在跟随链接时可以更快地获取链接内容。

  • preload:告诉浏览器下载资源,因为在当前导航期间稍后将需要该资源。

  • prerender:建议浏览器事先获取链接的资源,并建议将预取的内容显示在屏幕外,以便在需要时可以将其快速呈现给用户。

  • dns-prefetch:提示浏览器该资源需要在用户点击链接之前进行 DNS 查询和协议握手。

若需了解完整的链接类型,可以访问 MDN - Link Type

warmConnections

该方法用于预热链接:

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

addIframe

该方法用于动态添加 iframe 元素:

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

services 服务类

该服务类中定义了所支持站点的匹配规则和 Matcher 匹配工具类:

Rule 接口定义

export interface Rule { regex: RegExp // 内嵌站点正则表达式 embedUrl: string // 内嵌站点的url地址 html: string // 内嵌站点的iframe url模板 height: number // 高度 width?: number // 宽度 preconnects: string[] // 预链接的地址 id?: (ids: string[]) => string // 视频或资源id处理器 }

Rules 规则定义

export const RULES: Rules = { codepen: { regex: /https?:\/\/codepen\.io\/([^\/\?\&]*)\/pen\/([^\/\?\&]*)/, embedUrl: 'https://codepen.io/<%= remote_id %>?height=300&theme-id=0&default-tab=css,result&embed-version=2', html: `<iframe scrolling='no' frameborder='no' allowtransparency='true' allowfullscreen='true' style='width: 100%;' height="{{HEIGHT}}" src="{{SRC}}"></iframe>`, height: 300, id: ids => ids.join('/embed/'), preconnects: [''] }, 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'] }, }

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 { 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 } }

About

No description or website provided.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published