{"id":18508823,"url":"https://github.com/semlinker/lite-embed","last_synced_at":"2025-05-14T09:37:26.435Z","repository":{"id":56239895,"uuid":"224360937","full_name":"semlinker/lite-embed","owner":"semlinker","description":null,"archived":false,"fork":false,"pushed_at":"2020-11-18T22:31:32.000Z","size":293,"stargazers_count":3,"open_issues_count":1,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-17T02:24:22.696Z","etag":null,"topics":["codepen-embed","youku-embed","youtube-embed"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/semlinker.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-11-27T06:31:26.000Z","updated_at":"2023-03-23T14:28:37.000Z","dependencies_parsed_at":"2022-08-15T15:11:18.703Z","dependency_job_id":null,"html_url":"https://github.com/semlinker/lite-embed","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semlinker%2Flite-embed","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semlinker%2Flite-embed/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semlinker%2Flite-embed/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semlinker%2Flite-embed/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/semlinker","download_url":"https://codeload.github.com/semlinker/lite-embed/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254112660,"owners_count":22016821,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["codepen-embed","youku-embed","youtube-embed"],"created_at":"2024-11-06T15:15:31.468Z","updated_at":"2025-05-14T09:37:26.408Z","avatar_url":"https://github.com/semlinker.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Lite-embed\n\n### 简介\n\n本项目的灵感来源于 paulirish 大佬的 [lite-youtube-embed](https://github.com/paulirish/lite-youtube-embed) 项目：\n\n\u003e Provide videos with a supercharged focus on visual performance. This custom element renders just like the real thing but approximately 224X faster.\n\u003e\n\u003e **提供具有视觉效果的视频。这个自定义元素的渲染方式与真实的效果一样，但是速度提高了约 224 倍。**\n\nLite-embed 是基于 customElements Web Components 标准开发的组件，支持以 iframe 方式快速地嵌入第三方站点，如 **Bilibili**、**Youku**、**QQ**、**Youtube**、**Vimeo** 和 **Codepen** 等。\n\n通过扩展本项目 **services.ts** 服务类的匹配规则，开发者可以方便的支持其它支持 iframe 方式嵌入的站点，除此之外基于  **services.ts**  服务类，也可以让富文本编辑器支持自动解析剪贴板中的网址，自动嵌入所匹配的站点。\n\n#### 快速上手\n\n1、引入 `lite-embed` 脚本库\n\n```html\n\u003cscript src=\"../dist/lite-embed.umd.js\"\u003e\u003c/script\u003e\n```\n\n2、设置内嵌的第三方站点的链接地址\n\n```html\n\u003c!-- Youku --\u003e\n\u003ch2\u003ev.youku.com\u003c/h2\u003e\n\u003clite-embed\n    src=\"https://v.youku.com/v_show/id_XNDM4NDc2NTUzNg==.html?spm=a2ha1.12675304.m_7182_c_14738.d_1\u0026s=edca0b0e0fff464ea306\u0026scm=20140719.rcmd.7182.show_edca0b0e0fff464ea306\"\n    height=\"200\"\u003e\n\u003c/lite-embed\u003e\n\n\u003c!-- QQ --\u003e\n\u003ch2\u003ev.qq.com\u003c/h2\u003e\n\u003clite-embed\n    src=\"https://v.qq.com/x/cover/mzc0020095v1n4t/q0032kben9j.html\"\n    height=\"200\"\u003e\n\u003c/lite-embed\u003e\n\n\u003c!--  Bilibili --\u003e\n\u003ch2\u003ewww.bilibili.com\u003c/h2\u003e\n\u003clite-embed\n    src=\"https://www.bilibili.com/video/av76507858?spm_id_from=333.851.b_7265706f7274466972737431.12\"\n    height=\"200\"\u003e\n\u003c/lite-embed\u003e\n\n\u003c!--  Youtube --\u003e\n\u003ch2\u003ewww.youtube.com\u003c/h2\u003e\n\u003clite-embed src=\"https://www.youtube.com/watch?v=c5GAS_PMXDs\" height=\"200\"\u003e\u003c/lite-embed\u003e\n\n\u003c!--  Vimeo --\u003e\n\u003ch2\u003evimeo.com\u003c/h2\u003e\n\u003clite-embed src=\"https://vimeo.com/265045525\" height=\"200\"\u003e \u003c/lite-embed\u003e\n\n\u003c!--  Codepen --\u003e\n\u003ch2\u003ecodepen.io\u003c/h2\u003e\n\u003clite-embed src=\"https://codepen.io/aaroniker/pen/ZEEWoKj\" height=\"200\"\u003e \u003c/lite-embed\u003e\n```\n\n### Lite-embed 原理解析\n\nLite-embed 组件通常以下的手段来提升 iframe 内嵌站点的加载体验：\n\n- 在悬停（或点击）视频封面或海报时，预热（可能）要使用的 TCP 连接。\n- 在点击视频封面或海报时，才开始动态加载 iframe。\n\n\u003e 备注：在点击视频封面或海报时，才开始动态加载 iframe，也带来一定的问题，即需要二次点击才可以正常播放嵌入的视频。\n\n#### 构造函数\n\n```typescript\nclass LiteEmbed extends HTMLElement {\n  static preconnected: boolean\n  private src: string\n  private height: number\n  private posterUrl: string\n  private embedOption: EmbedOption | null\n  \n  constructor() {\n    super()\n    this.src = this.getAttribute('src') || ''\n    this.height = Number(this.getAttribute('height'))\n    this.posterUrl =\n      this.getAttribute('poster-url') || 'https://i.ytimg.com/vi/ogfYd705cRs/hqdefault.jpg'\n    this.embedOption = Matcher.matches(this.src)\n    LiteEmbed.addPrefetch('preload', this.posterUrl, 'image')\n  }\n}\n```\n\n#### 生命周期钩子\n\n```typescript\nconnectedCallback() {\n    if (this.embedOption != null) {\n      // 设置背景图片\n      this.style.backgroundImage = `url(\"${this.posterUrl}\")`\n      this.style.height = this.getAttribute('height') || this.embedOption.height.toString()\n\n      // 创建播放按钮\n      const playBtn = document.createElement('div')\n      playBtn.classList.add('lte-playbtn')\n      this.appendChild(playBtn)\n\n      // 悬停（或点击）时，预热（可能）要使用的TCP连接。\n  \t\t// once: true 表示listener在添加之后最多只调用一次。如果是true， \n      // listener会在其被调用之后自动移除。\n      this.addEventListener(\n        'pointerover',\n        () =\u003e LiteEmbed.warmConnections(this.embedOption!.preconnects),\n        { once: true }\n      )\n      // 一旦用户点击，添加实际的iframe\n      this.addEventListener('click', e =\u003e this.addIframe())\n    }\n}\n```\n\n自定义元素可以定义特殊生命周期钩子，以便在其存续的特定时间内运行代码。 这称为**自定义元素响应**。目前支持的生命周期钩子如下：\n\n| 名称                                                 | 调用时机                                                     |\n| ---------------------------------------------------- | ------------------------------------------------------------ |\n| `constructor`                                        | 创建或升级元素的一个实例。用于初始化状态、设置事件侦听器或创建 Shadow DOM。参见规范，了解可在 `constructor` 中完成的操作的相关限制。 |\n| `connectedCallback`                                  | 元素每次插入到 DOM 时都会调用。用于运行安装代码，例如获取资源或渲染。一般来说，您应将工作延迟至合适时机执行。 |\n| `disconnectedCallback`                               | 元素每次从 DOM 中移除时都会调用。用于运行清理代码（例如移除事件侦听器等）。 |\n| `attributeChangedCallback(attrName, oldVal, newVal)` | 属性添加、移除、更新或替换。解析器创建元素时，或者升级时，也会调用它来获取初始值。**Note:** 仅 `observedAttributes` 属性中列出的特性才会收到此回调。 |\n| `adoptedCallback()`                                  | 自定义元素被移入新的 `document`（例如，有人调用了 `document.adoptNode(el)`）。 |\n\n#### addPrefetch 方法\n\n该方法用于动态添加 link 标签并设置相应的 rel 属性来实现预加载或预链接：\n\n```javascript\nstatic addPrefetch(kind: string, url: string, as?: string) {\n    const linkElem = document.createElement('link')\n    linkElem.rel = kind\n    linkElem.href = url\n    if (as) {\n      (linkElem as any).as = as\n    }\n    linkElem.crossOrigin = 'true'\n    document.head.appendChild(linkElem)\n}\n```\n\n在实际开发中可以通过设置 link 标签 rel 属性来提升网页的渲染速度（有兼容性问题），常见的类型如下：\n\n- prefetch：提示浏览器提前加载链接的资源，因为它可能会被用户请求。建议浏览器提前获取链接的资源，因为它很可能会被用户请求。 从 Firefox 44 开始，考虑了 `crossorigin` 属性的值，从而可以进行匿名预取。\n\n- preconnect：向浏览器提供提示，建议浏览器提前打开与链接网站的连接，而不会泄露任何私人信息或下载任何内容，以便在跟随链接时可以更快地获取链接内容。\n\n- preload：告诉浏览器下载资源，因为在当前导航期间稍后将需要该资源。\n\n- prerender：建议浏览器事先获取链接的资源，并建议将预取的内容显示在屏幕外，以便在需要时可以将其快速呈现给用户。\n\n- dns-prefetch：提示浏览器该资源需要在用户点击链接之前进行 DNS 查询和协议握手。\n\n\u003e  若需了解完整的链接类型，可以访问 [MDN - Link Type](https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types)。\n\n#### warmConnections\n\n该方法用于预热链接：\n\n```typescript\nstatic warmConnections(preconnects: string[]) {\n    if (LiteEmbed.preconnected) return\n    preconnects.forEach(preconnect =\u003e LiteEmbed.addPrefetch('preconnect', preconnect))\n    LiteEmbed.preconnected = true\n}\n```\n\n#### addIframe\n\n该方法用于动态添加 iframe 元素：\n\n```typescript\naddIframe() {\n    if (this.embedOption != null) {\n      const finalEmbedOption = {\n        ...this.embedOption,\n        ...{ height: this.height, src: this.embedOption.embed }\n      }\n      const iframeHTML = this.embedOption.html.replace(\n        /\\{\\{(\\w*)\\}\\}/g,\n        (m: string, key: string) =\u003e {\n          return (finalEmbedOption as any)[key.toLowerCase()]\n        }\n      )\n      this.insertAdjacentHTML('beforeend', iframeHTML)\n      this.classList.add('lyt-activated')\n    }\n}\n```\n\n#### services 服务类\n\n该服务类中定义了所支持站点的匹配规则和 Matcher 匹配工具类：\n\n**Rule 接口定义**\n\n```typescript\nexport interface Rule {\n  regex: RegExp // 内嵌站点正则表达式\n  embedUrl: string // 内嵌站点的url地址\n  html: string // 内嵌站点的iframe url模板\n  height: number // 高度\n  width?: number // 宽度\n  preconnects: string[] // 预链接的地址\n  id?: (ids: string[]) =\u003e string // 视频或资源id处理器\n}\n```\n\n**Rules 规则定义**\n\n```typescript\nexport const RULES: Rules = {\n  codepen: {\n    regex: /https?:\\/\\/codepen\\.io\\/([^\\/\\?\\\u0026]*)\\/pen\\/([^\\/\\?\\\u0026]*)/,\n    embedUrl:\n      'https://codepen.io/\u003c%= remote_id %\u003e?height=300\u0026theme-id=0\u0026default-tab=css,result\u0026embed-version=2',\n    html: `\u003ciframe scrolling='no' frameborder='no' allowtransparency='true' allowfullscreen='true' style='width: 100%;' height=\"{{HEIGHT}}\" src=\"{{SRC}}\"\u003e\u003c/iframe\u003e`,\n    height: 300,\n    id: ids =\u003e ids.join('/embed/'),\n    preconnects: ['']\n  },\n  bilibili: {\n    regex: /https?:\\/\\/www\\.bilibili\\.com\\/video\\/av([^?]+)?.+/,\n    embedUrl: 'https://player.bilibili.com/player.html?aid=\u003c%= remote_id %\u003e\u0026page=1',\n    html: `\u003ciframe scrolling='no' frameborder='no' allowtransparency='true' allowfullscreen='true' style='width: 100%;' height=\"{{HEIGHT}}\" src=\"{{SRC}}\"\u003e\u003c/iframe\u003e`,\n    height: 498,\n    preconnects: ['https://player.bilibili.com', 'https://api.bilibili.com', 'https://s1.hdslb.com']\n  },\n}\n```\n\n**Matcher 类**\n\n```typescript\nexport default class Matcher {\n  static matches(url: string): EmbedOption | null {\n    if (!url) return null\n    let result = null\n    for (let site of Object.keys(RULES)) {\n      if ((result = Matcher.match(site, url)) != null) {\n        return result\n      }\n    }\n    return result\n  }\n\n  static match(site: string, url: string): EmbedOption | null {\n    const { regex, embedUrl, html, height, id = defaultIdsHandler, preconnects } = RULES[site]\n    const matches: RegExpExecArray | null = regex.exec(url)\n    if (matches != null) {\n      const result = matches.slice(1)\n      const embed = embedUrl.replace(/\u003c\\%\\= remote\\_id \\%\\\u003e/g, id(result))\n      return {\n        site,\n        source: url,\n        height,\n        embed,\n        preconnects,\n        html\n      }\n    }\n    return null\n  }\n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsemlinker%2Flite-embed","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsemlinker%2Flite-embed","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsemlinker%2Flite-embed/lists"}