+ 前端

使用 Web Component 创建 SVG 图标加载组件
2025-01-15

Web Component 是浏览器的一个标准,通过该标准提供的 API,我们能够在 HTML 中我们能够将布局设计与代码逻辑结合,从而创造出一种全新元素,然后像使用原生元素一样使用它们。如果我们对一些重复使用的布局进行封装,那这就像在前端工程化中的所使用的组件一样。

Web Component

Web Component 十一个浏览器标准,它允许你创建可重用的预定制元素,并且将功能封装在布局代码之外,然后在 HTML 文档中使用它们。

Web Component 可以定义三部分内容

名称 别名 说明
Custom element 自定义元素 允许你定义 custom elements 及其行为,然后可以在你的用户界面中按照需要使用它们。
Shadow DOM 影子 DOM 用于将封装的 影子DOM 树附加到预定义元素(与主文档 DOM 分开呈现)并控制其关联的功能。通过这种方式,可以保持元素的功能私有,这样就可以防止与文档的其他部分发生冲突。
HTML template HTML 模板 <template><slot> 元素使你可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础容器被多次重用。

Web Component 通常由类定义,包含有布局以及其关联代码,并且这个类需要继承 HTMLElement,然后通过 customElements.define() 方法加入文档。

此外如果使用 Shadow DOM,那么需要通过 this.attachShadow()Element.attachShadow() 方法将 Shadow DOM 加入到布局中。

对于 <template><slot> 定义的布局模板,使用的预定义元素实例将共用同一个模板元素,因此在加入布局前,我们需要用通过 Element.cloneNode(true) 方法将模板的拷贝加入布局,防止因为操作原有模板造成的布局冲突。

优雅封装 Web Component 组件

Web Component 需要通过 <script> 加载到主文档中才能进行使用,为了实现统一注册 Web Component 组件,我们可以利用 ESModule 的特性将组件载入。所以我们可以同时创建一个 component/CustomElement.js 和 component/index.js 文件。

文件目录

然后在 componet/CustomElement.js 声明一个自定义元素类继承 HTMLElement,并且将其导出给外部使用。

class CustomElement extends HTMLElement {
    constructor() {
        super();
        ...
    }
}

export { CustomElement };

接着在 component/index.js 中将 CustomElement 注册到文档中

import { CustomElement } from "./CustomElement.js";

customElements.define("custom-element", CustomElement);

最后只需要将 component/index.js 通过 <script type="module"> 引入主文档,就能像在全局使用预定义组件了。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Web Component</title>
    <script type="module" src="component/index.js"></script>
</head>
<body>
    <custome-element></custome-element>
</body>
</html>

封装 SVG 组件

SVG 自动加载组件的主要思路是通过文件名称加载 SVG 文件,通过请求得到 SVG 代码,然后嵌入到组件的 Shadow DOM 中。SVG 的代码将会被折叠在 Shadow DOM 中,从而不影响主文档的查阅。

为了实现 SVG 文件的自动载入,我们需要定义一个 SvgIcon 类,并且指定一个图片路径的前缀,用于拼接请求路径。

class SvgIcon extends HTMLElement {
    basePath = "/assets/img/svg";

    constructor() {
        super();
        ...
    }
}

export { SvgIcon };

接下来,我们需要在 SvgIcon 类中增加 SVG 的加载和渲染逻辑,我们将其分别定义在 loader()render() 方法中。

方法 说明
loader() 异步方法,用于加载 SVG 图标。它首先获取 name 属性的值,这个值对应 SVG 文件的名称。然后构建 SVG 文件的路径,并使用 XMLHttpRequest 发起 GET 请求来获取 SVG 文件的内容
render() 负责将加载的 SVG 数据插入到组件中。它首先创建一个 template 元素,并将 SVG 数据设置为其内容

虽然我们指定本地获取 SVG 文件,但在浏览器中实际上是使用请求来完成的,因此为了保持兼容性,我们需要使用 XMLHttpRequest 来完成请求。

class SvgIcon extends HTMLElement {
    basePath = "/assets/img/svg";

    constructor() {
        super();
        this.loader();
    }

    async loader() {
        const svgName = this.getAttribute("name");

        const svgPath = `${this.basePath}/${svgName}.svg`;

        const xhr = new XMLHttpRequest();
        xhr.open("GET", svgPath, true);
        xhr.onload = () => {
            if (xhr.status !== 200) {
                return;
            }
            //xhr.responseText
        };
        xhr.send();
    }
}

export { SvgIcon };

最后,我们通过 this.render() 方法将 SVG 加入到组件中。并且我们可以兼容一些样式定义,例如 width、height 和 color 等,并将这些样式附加到 Shadow DOM 中的 SVG 上。

class SvgIcon extends HTMLElement {
    basePath = "/assets/img/svg";

    constructor() {
        super();
        this.loader();
    }

    async loader() {
        const svgName = this.getAttribute("name");

        const svgPath = `${this.basePath}/${svgName}.svg`;

        const xhr = new XMLHttpRequest();
        xhr.open("GET", svgPath, true);
        xhr.onload = () => {
            if (xhr.status !== 200) {
                return;
            }

            this.render(xhr.responseText);
        };
        xhr.send();
    }

    render(svgData) {
        const template = document.createElement("template");
        template.innerHTML = svgData;

        const width = this.getAttribute("width") || "100%";
        const height = this.getAttribute("height") || "100%";
        const color = this.getAttribute("color") || "currentColor";
        const style = this.getAttribute("style") ?? "";

        const svgElement = template.content.querySelector("svg");
        svgElement.setAttribute("style", style);
        Object.assign(svgElement.style, { width, height, color });

        this.attachShadow({ mode: "closed" }).appendChild(template.content.cloneNode(true));
    }
}

export { SvgIcon };

至此,我们完成了 SVG 组件的定义,只需要在主文档中引入即可使用。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Web Component</title>
    <script type="module" src="component/index.js"></script>
</head>
<body>
    <svg-icon name="tags" width="16" height="16" color="#000000" style="margin: 5px;"></svg-icon>
    ...
</body>
</html>

界面显示效果 SVG Web Component

结语

通过使用 Web Component 封装 SVG 组件,我得以将博客中的 SVG 代码从代码中分离出来。也许这才是原生项目的新方向,比起熟悉的前端工程化编写方式,我还是更喜欢这种化繁为简的代码编写方式,因为这样并不需要考虑引入额外的库所带来的损耗。

总之,我推荐任何人都尝试一下这种原生特性带来的变革,一定会有不错的收获。

本文作者: Exisi 本文链接: https://exi.ink/前端/2025/01/15/使用-web-component-创建-svg-图标加载组件 版权声明:本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 4.0 许可协议。