Web Components 学习 01 认识 Web Components、一些组件库使用

什么是 Web Components

开发项目的时候为什么不手写原生 JS,而是要用现如今非常流行的前端框架,原因有很多,例如:

  • 良好的生态
  • 数据驱动试图
  • 模块化
  • 组件化

Web Components 就是为了解决“组件化”而诞生的,它是浏览器原生支持的组件化,不依赖任何库、依赖和打包工具就可以在浏览器中运行。

Vue、React 的组件化并不是真正的组件化,虽然写代码时写的是确实的组件化代码,但是编译后就不再是组件化了。

例如用 Vue + ElementUI 开发的应用,ElementUI 的组件都是 el 开头的,如 <el-button>,但编译后显示在页面上的就不再是 <el-button> 标签了。

这有点类似于 CSS 预处理器(如 Sass、Less),那些在开发阶段编译的变量(如 $color: red;)其实并不是真正的变量,而是伪变量。在编译过后就没有变量的概念了,所以很难和 JS 通信。

例如有一个需求,在页面上给用户提供一个输入框,用户输入什么颜色(如 red#ff00000),网站就会变成相应颜色的主题色,可是在我们获取到用户输入后,却没有变法将它们赋值给 Sass 变量上去。因为 Sass 代码在编译后已经变成了 CSS 代码,没有 Sass 变量了,例如 color: $color; 编译为 color: red;

所以此时就需要一个浏览器原生就支持的,不需要编译就能够运行的变量,于是 CSS 变量就出现了(--color: red;color: var(--color)),可以非常方便地与 JS 进行通信,因为它是浏览器级别地原生变量。

同理,框架的组件化也不是真正的标准,每家都用自己的组件化标准,这就导致了生态的分裂,而且这些框架的组件化也都是靠编译才能实现的,并且非常依赖于这个框架,是一种共生的关系,就像使用 Vue 时,后缀以 .vue 结尾的文件,根本没有办法在浏览器中运行,必须下载 Node、Webpack、vue-loader 等工具进行打包,但还是无法在脱离 Vue 这个框架的安装包的情况下进行运行。

通常来说,浏览器厂商会吸收一些前端非常流行框架之中的可取之处,然后推动其成为标准,并在浏览器中原生实现这些功能,最经典的莫过于 jQuery 的 $() 选择器。

“都 21 世纪了,还提 jQuery?”

尽管这几年风生水起的 Vue 和 React 加剧了 jQuery 的没落,但全世界仍有超过 6600 万个网站在使用 jQuery,同时 jQuery 也给业界留下了产生深远影响的遗产,W3C 就仿照 $() 函数实现了 querySelector()querySelectorAll() 方法。

而讽刺的是,也正是这两个原生方法的出现,大大加快了 jQuery 的没落,因为它们取代了 jQuery 最常用的功能之一:快捷的选择 DOM 元素。

那么浏览器原生支持的组件化会取代现在所流行的库或框架么?

还记得当 document.querySelector 最开始被广泛的被浏览器支持并且结束了无处不在的JQuery。这最终给我们提供了一个原生的方法,虽然JQuery已经提供了很久。我觉得这同样将会发生在像Angular和React这的前端框架身上。

这些框架可以帮助我们去做一些做不到的事情,比如创建可以复用的前端组件,但是这样需要付出复杂度、专属语法、性能消耗的代价。 但是这些将会得到改变。

现代浏览器的API已经更新到你不需要使用一个框架就可以去创建一个可服用的组件。Custom Element和Shadow DOM都可以让你去创造可复用的组件。

最早在2011年,Web Components就已经是一个只需要使用HTML、CSS、JavaScript就可以创建可复用的组件被介绍给大家。这也意味着你可以不使用类似React和Angular的框架就可以创造组件。甚至,这些组件可以无缝的接入到这些框架中。

—— 《Web Component可以取代你的前端框架吗?[译文]》

那么事实真的是这样么,其实不然。

Web Components 与如今非常流行的 MVVM 框架是一种共存的关系,而不是一种互斥的关系,就像 Sass 变量和 CSS 变量,两者可以非常完美的互补,而不是说用了 CSS 变量就不能用 Sass 变量。

再者来说,我们用那些 MVVM 框架也并不仅仅只是为了它们的组件化功能,虽然组件化是其中非常重要的一项功能,但是还有页面路由、数据绑定、模块化、CSS 预处理器、虚拟 DOM、Diff 算法,以及各种庞大的生态等功能。

Web Components 要解决的仅仅只是组件化的这么一项功能。

React 和 Web Components 为了解决不同的问题而生。Web Components 为可复用组件提供了强大的封装,而 React 则提供了声明式的解决方案,使 DOM 与数据保持同步。两者旨在互补。作为开发人员,可以自由选择在 Web Components 中使用 React,或者在 React 中使用 Web Components,或者两者共存。

—— 《Web Components – React》

我们认为 Vue 和 Web Components 主要是互补的技术。Vue 为使用和创建定制元素提供了出色的支持。无论你是将自定义元素集成到现有的 Vue 应用程序中,还是使用 Vue 来构建和分发自定义元素都很方便。

—— 《Vue 与 Web Components | Vue.js》

从 Vue 和 React 官网可以看到它们都对 Web Components 有很好的支持。

可以在 Custom Elements Everywhere 查看“自定义元素”(Web Components 的功能)在各个框架中的互操作性得分。

Making sure frameworks and custom elements can be BFFs

翻译:确保框架和自定义元素可以成为永远的最好的朋友(Best Friend Forever)。

—— Custom Elements Everywhere

既然 WebComponents 不会取代前端框架,那为什么还要学习?

一方面,浏览器原生支持的组件化我们肯定是需要了解的,这是为了跟上时代的步伐。

另一方面,那些流行框架想要顺应时代的趋势,不被大浪淘沙的淘汰掉,也必须要符合浏览器的标准,才能够在前端领域成为常青树。

而且我们无法保证现在流行的框架在几年后依然流行,当初用 jQuery 的大部分人也觉得有生之年它不会被替代。

Web Components 最重要的一点,就是组件化的概念其实是相通的,并不是一个全新的很复杂的概念,所以如果你用过 MVVM 框架,Web Components 其实是非常易于理解的,并不像学习一门全新框架那么耗费精力。而且如果你用过 Vue,那么 Web Components 简直就是手到擒来,因为尤雨溪在创建 Vue 的时候就大量参考了 Web Components 的语法,导致 Web Components 现在的写法有一部分和 Vue 长得几乎一摸一样。

Web Components 还有一个难能可贵的一点,就是它并不是一门单一的技术,它总共有三种规范:

  • HTML Template:HTML 模板
  • Custom Elements:自定义元素
  • Shadow DOM:影子 DOM

第四个规范 HTML Imports 已被弃用,原因是该规范从未在任何浏览器中实现,已经被 ES Modules 取代。

参考:

其中有 Custom Elements 和 Shadow DOM 都是可以在不依赖 Web Components 的情况下用在其他地方上去的,也就是说只要你的想象力足够丰富,脑洞够大,就可以利用它们把 DOM 玩出花来。

下面挑选了市面上既好玩,颜值又高的组件库来体验以下 Web Components:

css-doodle

官方网站:css-doodle.com

<!DOCTYPE html>
<html lang="en">
<head>
  <title>css-doodle 静态效果</title>
  <script src="https://unpkg.com/css-doodle"></script>
  <style>
    html, body {
      
      
      height: 100%;
      margin: 0;
      overflow: hidden;
    }
  </style>
</head>
<body>
  <css-doodle>
    :doodle {
      @grid: 20 / 100vmax;
      background: #12152f;
    }

    ::after {
      content: '\@hex(@rand(0x2500, 0x257f))';
      font-size: 5vmax;
      color: hsla(@rand(360), 70%, 70%, @rand(.9));
    }
  </css-doodle>
</body>
</html>

<!DOCTYPE html>
<html lang="en">
<head>
  <title>css-doodle 动态效果</title>
  <script src="https://unpkg.com/css-doodle"></script>
  <style>
    html, body {
      
      
      height: 100%;
      margin: 0;
      overflow: hidden;
      background:  #011627;
      display: grid;
      place-items: center;
    }
  </style>
</head>
<body>
  <css-doodle>
    :doodle {
      @grid: 1x1x100 / 100vmax;
      animation: r 23s linear infinite;
    }

    @size: 100% 50%;
    position: absolute;
    top: 25%;
    transform: rotate(@r(360deg));
    perspective: @r(100px, 200px);

    ::after {
      content: '';
      position: absolute;
      @size: @r(.5vmin, 5vmin);
      color: @p(#fdffc2, #2ec4b6, #e71d36, #ff9f1c);
      background: currentColor;
      box-shadow: @m2(0 0 1.2vmin currentColor);
      animation: cycle @r(2s) linear infinite;
      --trans: scaleX(@r(1, 5)) translateZ(@r(10vmin, 20vmin));
      transform: rotateY(0) @var(--trans);
    }
    :empty::after { display: none; }

    @keyframes cycle {
      to {
        transform: rotateY(@p(-1turn, 1turn)) @var(--trans)
      }
    }

    @keyframes r {
      to {
        transform: rotate(1turn)
      }
    }
  </css-doodle>
</body>
</html>

<!DOCTYPE html>
<html lang="en">
<head>
  <title>css-doodle 水波效果</title>
  <script src="https://unpkg.com/css-doodle"></script>
  <style>
    html, body {
      
      
      height: 100%;
      margin: 0;
      overflow: hidden;
      background:  radial-gradient(#459dc1, #070729);
      display: grid;
      place-items: center;
    }
  </style>
</head>
<body>
  <!-- 这个效果比较复杂,只能运行在 Google 内核的浏览器,Firefox 或 Safari 看不到效果 -->
  <css-doodle>
    :doodle {
      @grid: 80x1 / 100vw 100vh;
      @min-size: 100px;
      filter: url(#filter);
      animation: r 23s linear infinite;
    }

    @size: 100% 50%;
    position: absolute;
    top: 25%;
    transform: rotate(@r(360deg));
    perspective: 130px;

    ::after {
      content: '';
      position: absolute;
      @size: @r(10px);
      background: #fff;
      box-shadow: @m3(0 0 calc(.5vmin + 5px) #fff);
      animation: cycle @r(2s, 8s) linear infinite;
      animation-delay: -@r(100s);
      --trans: scaleX(@r(.1, 5)) translateZ(105px);
      transform: rotateY(0) @var(--trans);
    }

    @keyframes cycle {
      to {
        transform: rotateY(@p(-1turn, 1turn)) @var(--trans)
      }
    }

    @keyframes r {
      to {
        transform: rotate(@p(-1turn, 1turn))
      }
    }
  </css-doodle>

  <svg style="width: 0;height: 0">
    <filter id="filter">
      <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur"></feGaussianBlur>
      <feColorMatrix in="blur" mode="matrix" values="
          1 0 0 0 0
          0 1 0 0 0
          0 0 1 0 0
          0 0 0 18 -7
        "
        result="goo"
      ></feColorMatrix>
      <feBlend in="SourceGraphic" in2="goo"></feBlend>
    </filter>
  </svg>
</body>
</html>

css-doodle 是用 Web Components 技术做出来的优秀案例,不过由于在 css-doodle 中写的代码并不是 Web Components 的语法,而是 css-doodle 通过 Web Components 技术拿到代码内容,然后用正则表达式解析出来的,属于它的自创语法。开发者可以自行去官网学习这些与 Web Components 无关的语法。

css-doodle 适合做背景,虽然 css-doodle 很炫,但它依然需要开发者自己写样式,而且与我们平时开发时所需要的组件(按钮、轮播图、文本输入框等)相差较大。

fancy-components 就是一款可以提供这些常用组件,可以用于日常开发的组件库。

fancy-components

官方网站:https://fancy-components.gitee.io/home-page/

<!DOCTYPE html>
<html lang="en">
<head>
  <title>fancy-components</title>
  <style>
    * {
      
       padding: 0; margin: 0; }
    html, body {
      
       height: 100%; }
    body {
      
      
      display: grid;
      place-items: center;
      background: #3f2766;
    }
    fc-3d-btn {
      
      
      --color: #6e50a6;
      --shadow-color: rgba(255, 255, 255, .4);
      --inset-shadow-color: #315;
      --inset-shadow-color-active: rgba(49, 23, 7, .9);
      --cover-color: rgba(0, 0, 0, .4);
    }
  </style>
</head>
<body>
  <div>
    <!-- html 只支持小写标签,不支持驼峰命名法 -->
    <fc-input white placeholder="Username"></fc-input>
    <br />
    <fc-input white disabled value="fancy components" placeholder="Username"></fc-input>
    <br />
    <fc-btn>fancy-components</fc-btn>
    <br />
    <fc-warp-btn></fc-warp-btn>
    <br />
    <fc-3d-btn></fc-3d-btn>
    <br />
    <fc-underline-btn></fc-underline-btn>
    <br />
    <fc-pixel-btn></fc-pixel-btn>
    <br />
    <fc-parentheses-btn></fc-parentheses-btn>
    <br />
    <fc-round-btn></fc-round-btn>
    <br />
    <fc-arrow-btn></fc-arrow-btn>
    <br />
    <fc-bubbles click>
      <fc-parentheses-btn>撒花</fc-parentheses-btn>
    </fc-bubbles>
  </div>

  <script type="module">
    import {
      
       FcTypingInput } from 'http://unpkg.com/fancy-components'
    import {
      
       FcDblWarpBtn } from 'http://unpkg.com/fancy-components'
    import {
      
       FcWarpBtn } from 'http://unpkg.com/fancy-components'
    import {
      
       Fc3DBtn } from 'http://unpkg.com/fancy-components'
    import {
      
       FcUnderlineBtn } from 'http://unpkg.com/fancy-components'
    import {
      
       FcPixelBtn } from 'http://unpkg.com/fancy-components'
    import {
      
       FcParenthesesBtn } from 'http://unpkg.com/fancy-components'
    import {
      
       FcRoundBtn } from 'http://unpkg.com/fancy-components'
    import {
      
       FcArrowBtn } from 'http://unpkg.com/fancy-components'
    import {
      
       FcBubbles } from 'http://unpkg.com/fancy-components'

    // 注册组件
    // 可以传递一个重命名组件名的字符串,必须是小写,且用 `-` 连接
    // 不传参数默认组件名就是 fc-typing-input
    new FcTypingInput('fc-input')
    new FcDblWarpBtn('fc-btn')
    new FcWarpBtn()
    new Fc3DBtn()
    new FcUnderlineBtn()
    new FcPixelBtn()
    new FcParenthesesBtn()
    new FcRoundBtn()
    new FcArrowBtn()
    new FcBubbles()
  </script>
</body>
</html>

在脚手架中使用 Web Components 组件库

Vue 2 中使用

npm i -g @vue/cli
vue create vue2-app
cd vue2-app
npm i fancy-components
npm run serve
// src\main.js
import Vue from 'vue'
import App from './App.vue'
import {
    
     FcBubbles } from 'fancy-components'

// 禁用 no-new 校验规则
/* eslint-disable no-new */
new FcBubbles()

Vue.config.productionTip = false

new Vue({
    
    
  render: h => h(App)
}).$mount('#app')

<!-- src\App.vue -->
<template>
  <div id="app">
    <fc-bubbles click><img alt="Vue logo" src="./assets/logo.png"></fc-bubbles>
    <!-- <FcBubbles click><img alt="Vue logo" src="./assets/logo.png"></FcBubbles> -->
  </div>
</template>
...

Web Components 原生组件的地位和 HTML 标签的地位是相同的,大写的驼峰命名组件会被当做 Vue 组件,原生组件要和 HTML 标签一样,不要写成驼峰命名。React 框架中也一样。

Vue CLI 旧版本中使用 Web Components 控制台可能会发出警告,原因是 Vue 将 原生组件当作 Vue 组件去判断,警告组件没有注册,解决办法就是配置 ignoredElements 让 Vue 忽略原生组件:

Vue.config.ignoredElements = [
 // 正则匹配
 /^fc-/,
 // 或字符串
 'css-coodle'
]

Vue 3 中使用

vue create vue3-app
cd vue3-app
npm i fancy-components
npm run serve
// src\main.js
import {
    
     createApp } from 'vue'
import App from './App.vue'
import {
    
     FcBubbles } from 'fancy-components'

/* eslint-disable no-new */
new FcBubbles()

createApp(App).mount('#app')

<!-- src\App.vue -->
<template>
  <fc-bubbles click><img alt="Vue logo" src="./assets/logo.png"></fc-bubbles>
  <HelloWorld msg="Welcome to Your Vue.js App"/>
</template>

使用上如 Vue2 一样,但实际上会报错:

[Vue warn]: Failed to resolve component: fc-bubbles
If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement.

原因与 Vue CLI 旧版本创建的 Vue2 应用一样,解决办法依然是配置忽略原生组件(自定义元素),参考:Vue 与 Web Components

// vue.config.js
module.exports = {
    
    
  chainWebpack: config => {
    
    
    config.module
      .rule('vue')
      .use('vue-loader')
      .tap(options => ({
    
    
        ...options,
        compilerOptions: {
    
    
          // 将所有 fc- 开头的标签名都视为自定义元素
          isCustomElement: tag => tag.startsWith('fc-')
        }
      }))
  }
}

重启应用,警告已经不见了,但是点击仍然没有生效,打开 Element 面板发现组件的 click 属性并没有添加上,而其他属性如 click1 可以添加,这可能是因为 Vue3 认为 click 是一个不能直接添加的关键字,测试发现只需将 click 改成大写 Click 即可添加上。

<fc-bubbles Click><img alt="Vue logo" src="./assets/logo.png"></fc-bubbles>

在 Vite 中使用 Web Components 组件库

# npm 6.x
npm create vite@latest vite-vue-app
√ Select a framework: » vue
√ Select a variant: » vue

cd vite-vue-app
npm install
npm i fancy-components
npm run dev
// src/main.js
import {
    
     createApp } from 'vue'
import App from './App.vue'
import {
    
     FcBubbles } from 'fancy-components'

new FcBubbles()

createApp(App).mount('#app')

<!-- src\App.vue -->
<!-- 注意 Click 大写 -->
<fc-bubbles Click><img alt="Vue logo" src="./assets/logo.png" /></fc-bubbles>

还要配置忽略的自定义元素:

// vite.config.js
import {
    
     defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
    
    
  plugins: [
    vue({
    
    
      template: {
    
    
        compilerOptions: {
    
    
          // 将所有 fc- 开头的标签名都视为自定义元素
          isCustomElement: tag => tag.startsWith('fc-')
        }
      }
    })
  ]
})

不需要重启即可生效。

猜你喜欢

转载自blog.csdn.net/u012961419/article/details/125310277