5

foreword

In today's front-end ecosystem, React , Angular and Vue three-pointers of the world. Although the positioning of these three frameworks is different, they have one core commonality, that is, they provide the ability to be componentized. W3C also has a related draft of Web Component , which is also to provide componentization capabilities. Today we're going to talk about what componentization is and why it's so important.

text

In fact, the idea of componentization is a very natural extension of front-end technology. If you have used HTML , I believe you must have had the idea of "if only I could define a label". Although HTML provides more than one hundred tags, they can only achieve some very basic functions.

The goal of HTML itself is standardized semantics. Since it is standardized, there is a certain conflict with the custom tag name. Therefore, from the earliest appearance of the front end in 2005, until now in 2022, we have not waited for the function of custom tags, and it is still in the state of Draft .

However, the demand for front-end componentization has always existed, and engineers have proposed many componentized solutions in the long history.

ExtJS

Ext JS is a popular JavaScript framework that provides rich UI for building Web applications with cross-browser capabilities. Let's take a look at its component definition:

MainPanel = function() {
  this.preview = new Ext.Panel({
    id: "preview",
    region: "south"
    // ...
  });
  MainPanel.superclass.constructor.call(this, {
    id: "main-tabs",
    activeTab: 0,
    region: "center"
    // ...
  });

  this.gsm = this.grid.getSelectionModel();

  this.gsm.on(
    "rowselect", function(sm, index, record) {
      // ...
    }, this, { buffer: 250 }
  );

  this.grid.store.on("beforeload", this.preview.clear, this.preview);
  this.grid.store.on("load", this.gsm.selectFirstRow, this.gsm);

  this.grid.on("rowdbclick", this.openTab, this);
};

Ext.extend(MainPanel, Ext.TabPanel, {
  loadFeed: function(feed) {
    // ...
  },
  // ...
  movePreview: function(m, pressed) {
    // ...
  }
});

You can see that ExtJS the component as a function container, accepting the component configuration parameters options , append to the specified DOM . This is a system that completely uses JS to implement components. It defines a strict inheritance relationship, as well as the life cycle of initialization, rendering, and destruction. This scheme well supports the front-end architecture of ExtJS .

https://www.w3cschool.cn/extjs/extjs_overview.html

HTML Component

Students who have been engaged in front-end for a long time will know one thing, that is HTC ( HTML Components ), this thing has a very similar name to the popular Web Components , but they are two different things. Their ideas have many similarities, but the former has Yesterday's yellow flower, the latter is just in the ascendant, what caused this gap between them?

Because only IE supported HTC in mainstream browsers, many people subconsciously think it is not standard, but in fact it also has standard documents, and there are still links now, pay attention to its time!

http://www.w3.org/TR/NOTE-HTMLComponents

The definition of MSDN online in HTC is as follows:

HTML Components (HTCs) provide a mechanism to implement components in script as Dynamic HTML (DHTML) behaviors. Saved with an .htc extension, an HTC is an HTML file that contains script and a set of HTC-specific elements that define the component.
( HTC is a component of HTML tags, special tags, and scripts that define the DHTML feature.)

As a component, it also has properties, methods, and events. The following is a brief description of how they are defined:

  • <PUBLIC:COMPONENT></PUBLIC:COMPONENT> : defines HTC , this tag is the parent element of other definitions.
  • <PUBLIC:PROPERTY NAME=”pName” GET=”getMethod” PUT=”putMethod” /> : Define the attributes of HTC . The three definitions in it represent the attribute name, read attribute, and method called by HTC when setting attribute.
  • <PUBLIC:METHOD NAME=”mName” /> : defines the method of HTC , and NAME defines the method name.
  • <PUBLIC:EVENT NAME=”eName” ID=”eId” /> : defines the event of HTC , NAME defines the event name, and ID is an optional attribute that uniquely identifies this event in HTC .
  • <PUBLID:ATTACH EVENT=”sEvent” ONEVENT=”doEvent” /> : Defines the corresponding method passed by the browser to the HTC event, where EVENT is the event passed in by the browser, and ONEVENT is the method to process the event.

Let's see what it can mainly do?

It can be introduced into a HTML page in two ways, either by being appended to the element as an "action", using CSS to introduce , or by as a "component", extending the tag system HTML of .

Behaviors provide a means for script encapsulation and code reuse

Behaviors make it easy to add interactive effects as encapsulated components that can be reused across multiple pages. For example, consider the effect of implementing onmouseover highlight in Internet Explorer 4.0 , which is easy to achieve on a page by using CSS rules, and the ability to dynamically change styles. In Internet Explorer 4.0 , implementing onmouseover highlighting on list elements li can dynamically change li element style using the onmouseover and onmouseout events:

<HEAD>
<STYLE>
.HILITE
{ color:red;letter-spacing:2; }
</STYLE>
</HEAD>

<BODY>
<UL>
<LI onmouseover="this.className='HILITE'"
    onmouseout ="this.className=''">HTML Authoring</LI>
</UL>
</BODY>

Starting with Internet Explorer 5 , this effect can be achieved with the DHTML behavior. When the DHTML behavior is applied to li element, this behavior extends the default behavior of a list item, changing its color when the user moves the mouse over it.

The following example implements a behavior in the form of the HTML component ( HTC ) file, which is included in the hilite.htc file, to implement a mouse-over highlighting effect. Use the CSS behavior attribute to apply the behavior to the element li . The above code might look like this in Internet Explorer 5 and later:

// hilite.htc
<HTML xmlns:PUBLIC="urn:HTMLComponent">
// <ATTACH> 元素定义了浏览器传给HTC事件的相应方法,其中EVENT是浏览器传入的事件,ONEVENT是处理事件的方法
<PUBLIC:ATTACH EVENT="onmouseover" ONEVENT="Hilite()" />
<PUBLIC:ATTACH EVENT="onmouseout"  ONEVENT="Restore()" />
<SCRIPT LANGUAGE="JScript">
var normalColor;

function Hilite()
{
   if (event.srcElement == element)
   {
     normalColor = style.color;
     runtimeStyle.color  = "red";
     runtimeStyle.cursor = "hand";
   }
}

function Restore()
{
   if (event.srcElement == element)
   {
      runtimeStyle.color  = normalColor;
      runtimeStyle.cursor = "";
   }
}
</SCRIPT>

Attach DHTML behavior to elements on the page via the CSS behavior attribute

<HEAD>
<STYLE>
   LI {behavior:url(hilite.htc)}
</STYLE>
</HEAD>

<BODY>
<UL>
  <LI>HTML Authoring</LI>
</UL>
</BODY>

HTC custom marker

We often see this effect on some web pages: the user clicks a button, the text is displayed, and when the button is clicked again, the text disappears, but the browser does not refresh. Below I use HTC to achieve this simple effect. The programming idea is as follows: use HTC simulate a switch, which has two states of ”on” and ”off” (read/write attribute status ); the user can set the text displayed by the switch in these two states (set attributes turnOffText and turnOnText ); When the user clicks the switch, the state of the switch is reversed, and an event ( onStatusChanged ) is triggered to notify the user that the user can write code to respond to this event; the HTC also defines a method ( reverseStatus ) to invert the state of the switch. Here is the code for this HTC :

<!—switch.htc定义 -->  
<PUBLIC:COMPONENT TAGNAME="Switch">  
    <!--属性定义-->  
    <PUBLIC:PROPERTY NAME="turnOnText" PUT="setTurnOnText" VALUE="Turn on" />  
    <PUBLIC:PROPERTY NAME="turnOffText" PUT="setTurnOffText" VALUE="Turn off" />  
    <PUBLIC:PROPERTY NAME="status" GET="getStatus" PUT="setStatus" VALUE="on" />  
  
    <!--定义事件-->  
    <PUBLIC:EVENT NAME="onStatusChanged" ID="changedEvent" />  
  
    <!--定义方法-->  
    <PUBLIC:METHOD NAME="reverseStatus" />  
  
    <!--关联客户端事件-->  
    <PUBLIC:ATTACH EVENT="oncontentready" ONEVENT="initialize()"/>  
    <PUBLIC:ATTACH EVENT="onclick" ONEVENT="expandCollapse()"/>  
  
</PUBLIC:COMPONENT>  
  
<!-- htc脚本 -->  
<script language="javascript">  
    var sTurnOnText;    //关闭状态所显示的文本  
    var sTurnOffText;   //开启状态所显示的文本  
    var sStatus;    //开关状态  
    var innerHTML   //使用开关时包含在开关中的HTML     
  
    //设置开关关闭状态所显示的文本  
    function setTurnOnText(value)  
    {  
        sTurnOnText = value;  
    }   
  
    //设置开关开启状态所显示的文本  
    function setTurnOffText(value)  
    {  
        sTurnOffText = value;  
    }     
  
    //设置开关状态  
    function setStatus(value)  
    {  
        sStatus = value;  
    }   
  
     //读取开关状态  
    function getStatus()  
    {  
        return sStatus;  
    }     
  
    //反向开关的状态  
    function reverseStatus()  
    {  
        sStatus = (sStatus == "on") ? "off" : "on";  
    }  
  
    //获取htc控制界面html文本  
    function getTitle()  
    {  
        var text = (sStatus == "on") ? sTurnOffText : sTurnOnText;  
        text = "<div id='innerDiv'>" + text + "</div>";  
        return text;  
  
    }  
  
    //htc初始化代码  
    function initialize()  
    {  
        //back up innerHTML  
        innerHTML = element.innerHTML;  
        element.innerHTML = (sStatus == "on") ? getTitle() + innerHTML : getTitle();  
    }   
  
    //响应用户鼠标事件的方法  
    function expandCollapse()  
    {  
         reverseStatus();  
         //触发事件  
         var oEvent = createEventObject();  
         changedEvent.fire(oEvent);    
         var srcElem = element.document.parentWindow.event.srcElement;  
         if(srcElem.id == "innerDiv")  
         {  
              element.innerHTML = (sStatus == "on") ? getTitle() + innerHTML : getTitle();  
         }  
    }  
</script>  

html page introduces custom tags

<!--learnhtc.html-->  
<html xmlns:frogone><!--定义一个新的命名空间-->  
<head>  
    <!--告诉浏览器命名空间是由哪个HTC实现的-->  
    <?IMPORT namespace="frogone" implementation="switch.htc">  
</head>  
<body>  
   <!--设置开关的各个属性及内部包含的内容-->  
   <frogone:Switch id="mySwitch"  
                    TurnOffText="off"  
                    TurnOnText="on"  
                    status="off"  
                    onStatusChanged="confirmChange()">  
        <div id="dBody">文本内容...... </div>  
    </frogone:Switch>  
</body>  
<script language="javascript">  
    //相应开关事件  
    function confirmChange()  
    {  
        if(!confirm("是否改变开关状态?"))  
            mySwitch.reverseStatus();
    }  
</script>  
</html>

This technology provides event binding and attributes, method definitions, and some life cycle-related events. It should be said that it is already a relatively complete componentized solution. But we can see the result later, it didn't make it to the standard and disappeared silently. From our perspective today, it can be said to be born out of time.

How to define a component

ExtJS based on object-oriented ideas, and designs components as function containers, with strict inheritance relationship and component life cycle hooks. HTC utilizes a script encapsulation mechanism built into the IE browser to separate the behavior from the document structure, and introduce advanced custom behavior ( behavior ) to the HTML page through similar styles or custom logos. From the historical attempts at componentization, how should we define a component?

First of all, it should be clear what problem is the idea of componentization to solve? It goes without saying that the most direct purpose of componentization is to reuse and improve development efficiency. As a component, it should meet the following conditions:

  • Encapsulation: The component shields the internal details, and the user of the component can only care about the properties, events and methods of the component.
  • Decoupling: The component itself isolates changes, and component developers and business developers can develop and test independently according to the components' conventions.
  • Reuse: The component will be used as a multiplexing unit in multiple places.
  • Abstract: Components provide a unified model for describing UI through properties, events, methods and other infrastructures, reducing the mental cost of user learning.

Next, let's dive into the specific technical details and take a look at the basic idea of componentization. First of all, the most basic semantic tags can be regarded as components, which can be directly mounted to the corresponding elements through DOM API :

var element = document.createElement('div')
document.getElementById('container').appendChild(element)

But in fact, our components can't be so simple, and the components that cover many scenarios are more complicated. The engineer thought of defining the component as the Function or Class container in the native JS (actually, Object is also an idea, such as Vue ), because in the In the JavaScript grammar, they naturally provide a closed space, such as:

function MyComponent(){
    this.prop1;
    this.method1;
    ……
}

However, it has become a problem to mount it. Ordinary JS objects cannot be used for appendChild , so front-end engineers have two ideas. The first is to reverse it and design a appendTo method to let the component mount itself to the DOM tree up.

function MyComponent(){
    this.root = document.createElement("div");
    this.appendTo = function(node){
        node.appendChild(this._root)
    }
}

The second is more interesting, is to let the component return a DOM element directly, and attach methods and custom attributes to this element:

function MyComponent(){
    var _root = document.createElement("div");
    root.prop1 // = ...
    root.method1 = function(){
    /*....*/
    }
    return root;
}

document.getElementById("container").appendChild(new MyComponent());

Next, we design a carousel component based on the above ideas, which can automatically play pictures

Picture1.gif

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .carousel, .carousel > img {
      width: 500px;
      height: 300px;
    }

    .carousel {
      display: flex;
      overflow: hidden;
    }

    .carousel > img {
      transition: transform ease 0.5s;
    }
  </style>
</head>
<body>
  <script>
    let d = [
      {
          img: "https://static001.geekbang.org/resource/image/bb/21/bb38fb7c1073eaee1755f81131f11d21.jpg",
          url: "https://time.geekbang.org",
          title: "蓝猫"
      },
      {
          img: "https://static001.geekbang.org/resource/image/1b/21/1b809d9a2bdf3ecc481322d7c9223c21.jpg",
          url: "https://time.geekbang.org",
          title: "橘猫"
      },
      {
          img: "https://static001.geekbang.org/resource/image/b6/4f/b6d65b2f12646a9fd6b8cb2b020d754f.jpg",
          url: "https://time.geekbang.org",
          title: "橘猫加白"
      },
      {
          img: "https://static001.geekbang.org/resource/image/73/e4/730ea9c393def7975deceb48b3eb6fe4.jpg",
          url: "https://time.geekbang.org",
          title: "猫"
      }
    ];

    class Carousel {
      constructor(data) {
        this._root = document.createElement('div');
        this._root.classList = ['carousel']
        this.children = [];
        for (const d of data) {
          const img = document.createElement('img');
          img.src = d.img;
          this._root.appendChild(img);
          this.children.push(img);
        }

        let i = 0;
        let current = i
        setInterval(() => {
          for (const child of this.children) {
            child.style.zIndex = '0';
          }
          // 计算下一张图片的下标
          let next = (i + 1) % this.children.length;

          const currentElement = this.children[current];
          const nextElement = this.children[next];

          // 下一张图片的zIndex应该大于当前图片的zIndex
          currentElement.style.zIndex = '1';
          nextElement.style.zIndex = '2';
          // 禁止添加的动画过渡样式
          currentElement.style.transition = 'none';
          nextElement.style.transition = 'none';
          console.log('current', current, next)
          // 每次初始化当前图片和下一张图片的位置
          currentElement.style.transform = `translate3d(${-100 * current}%, 0 , 0)`;
          nextElement.style.transform = `translate3d(${100 - 100 * next}%, 0 , 0)`;

          // 浏览器刷新频率是每秒60帧,所以这里需要延迟到浏览器下次重绘更新下一帧动画
          setTimeout(() => {
            // 启动添加的动画过渡样式
            currentElement.style.transition = '';
            nextElement.style.transition = '';
            // 当前图片退出,下一张图片进来
            currentElement.style.transform = `translate3d(${-100 -100 * current}% 0 , 0)`;
            nextElement.style.transform = `translate3d(${-100 * next}%, 0 , 0)`;
          }, 1000 / 60);
            
          // 或者使用window.requestAnimationFrame,当然这个比较难理解,容易出错,使用setTimeout也是可以的
          // window.requestAnimationFrame(() => {
          //   window.requestAnimationFrame(() => {
          //   // 启动添加的动画过渡样式
          //   currentElement.style.transition = '';
          //   nextElement.style.transition = '';
          //   // 当前图片退出,下一张图片进来
          //   currentElement.style.transform = `translate3d(${-100 -100 * current}% 0 , 0)`;
          //   nextElement.style.transform = `translate3d(${-100 * next}%, 0 , 0)`;
          //   })
          // })

          current = next;
          i++;
        }, 3000);

        // 追加 
        this.appendTo = function(node){
          node.appendChild(this._root)
        }
      }
    }

    new Carousel(d).appendTo(document.body);
  </script>
</body>
</html>

Effect:

amimation.gif

Above we have implemented a simple carousel map, next we try to apply it to JSX

// index.js
const ele = <div id="root" name="container">
  <Carousel data={d}></Carousel>
  <span>a</span>
  <span>b</span>
  <span>c</span>
</div>

document.body.appendChild(ele);
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .carousel, .carousel > img {
      width: 500px;
      height: 300px;
    }

    .carousel {
      display: flex;
      overflow: hidden;
    }

    .carousel > img {
      transition: transform ease 0.5s;
    }
  </style>
</head>
<body>
  <script src="./index.js"></script>
</body>
</html>

But directly importing the js script with JSX syntax into html as above will report an error, because the browser does not support JSX , so what should we do?

We need to compile JSX into js first, and then import it into html . At this time, Babel comes in handy. The @babel/plugin-transform-react-jsx plugin can help us compile js to JSX

// 编译前
const ele = <div id="root" name="container">
  <Carousel data={d}></Carousel>
  <span>a</span>
  <span>b</span>
  <span>c</span>
</div>

// 编译后
var ele = React.createElement("div", {id: "root", name: "container"}, 
    React.createElement(Carousel, {data: d}), 
    React.createElement("span", null, "a"), 
    React.createElement("span", null, "b"), 
    React.createElement("span", null, "c")
);

The compiled element will be created with React.createElement by default. In addition to supporting the basic html label, the createElement method also supports custom function components and class components, but the problem is that our Carousel component is not the function component and class component in React , just The default configuration parameter of @babel/plugin-transform-react-jsx pragma uses React.createElement replace the function used to compile the JSX expression, and also allows us to customize the function to do similar things to the React.createElement function. Let's implement it:

function createElement<P extends {}>(
    type: FunctionComponent<P>,
    props?: Attributes & P | null,
    ...children: ReactNode[]): FunctionComponentElement<P>;
function createElement<P extends {}>(
    type: ClassType<P, ClassicComponent<P, ComponentState>, ClassicComponentClass<P>>,
    props?: ClassAttributes<ClassicComponent<P, ComponentState>> & P | null,
    ...children: ReactNode[]): CElement<P, ClassicComponent<P, ComponentState>>;
function createElement<P extends {}, T extends Component<P, ComponentState>, C extends ComponentClass<P>>(
    type: ClassType<P, T, C>,
    props?: ClassAttributes<T> & P | null,
    ...children: ReactNode[]): CElement<P, T>;
function createElement<P extends {}>(
    type: FunctionComponent<P> | ComponentClass<P> | string,
    props?: Attributes & P | null,
    ...children: ReactNode[]): ReactElement<P>;

First, let's transform the Carousel component so that it can receive the injected data attribute. Here we use the setAttribute and attribute descriptor set store-value function to achieve

// index.js
class Carousel {
  constructor(data) {
    this._root = document.createElement('div');
    this._root.classList = ['carousel'];
    this.children = [];
  }

  set data(data) {
    this._root.innerHTML = '';

    for (const d of data) {
      const img = document.createElement('img');
      img.src = d.img;
      this._root.appendChild(img);
      this.children.push(img);
    }

    let i = 0;
    let current = i
    setInterval(() => {
      for (const child of this.children) {
        child.style.zIndex = '0';
      }
      let next = (i + 1) % this.children.length;

      const currentElement = this.children[current];
      const nextElement = this.children[next];

      currentElement.style.zIndex = '1';
      nextElement.style.zIndex = '2';
      currentElement.style.transition = 'none';
      nextElement.style.transition = 'none';
      currentElement.style.transform = `translate3d(${-100 * current}%, 0 , 0)`;
      nextElement.style.transform = `translate3d(${100 - 100 * next}%, 0 , 0)`;

      setTimeout(() => {
        currentElement.style.transition = '';
        nextElement.style.transition = '';
        currentElement.style.transform = `translate3d(${-100 -100 * current}% 0 , 0)`;
        nextElement.style.transform = `translate3d(${-100 * next}%, 0 , 0)`;
      }, 1000 / 60);

      current = next;
      i++;

    }, 3000);
  }

  setAttribute(name, value) {
    this[name] = value; // 这里统一attribute和properties,vue使用的是attribute
  }

  // 追加 
  appendTo = function(node){
    node.appendChild(this._root);
  }
}

When injecting data into the Carousel component, we trigger the setAttribute method of the component to mount the data to the component instance, and trigger set store value function to initialize the carousel component. So how to trigger the setAttribute method of the component when data is injected? This is what our custom conversion function does

// index.js
const create = (Class, properity, ...children) => {
  let element;
  if (typeof Class === 'string') {
    // 基本标签直接创建
    element = document.createElement(Class);
  } else {
    // 自定义组件实例化
    element = new Class;
  }

  // 注入到基本标签上的属性直接追加到元素的Attribute属性中,而注入到自定义组件的属性调用组件的setAttribute方法
  for (const p in properity) {
    element.setAttribute(p, properity[p]);  
  }

  // 处理子节点
  for(let child of children) {
    if (typeof child === 'string') {
      // 如果子节点是字符串,那就创建文本节点
      child = document.createTextNode(child);
    }
    // 如果子节点含有appendTo方法,则是我们自定义的Carousel组件,将子节点追加上当前节点上
    if (child.appendTo) {
      child.appendTo(element);
    } else {
      // html标签,也追加到当前节点上
      element.appendChild(child);
    }
  }
  return element;
}

Finally, we built index.js and imported it into html , and attached webpack configuration:

// webpack.config.js
const path = require('path')

module.exports = {
  entry: "./index.js",
  output: {
    path: path.resolve(__dirname, 'dist')
  },
  module: {
      rules: [
          {
              test:/\.js$/,
              use:{
                  loader: "babel-loader",
                  options: {
                      presets:["@babel/preset-env"],
                      plugins: [["@babel/plugin-transform-react-jsx", {pragma: "create"}]]
                  }
              }
          }
      ]
  },
  mode: "development"
}

Let's take a look at the script after the build

/*
 * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
 * This devtool is neither made for production nor for readable output files.
 * It uses "eval()" calls to create a separate source file in the browser devtools.
 * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
 * or disable the default devtool with "devtool: false".
 * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
 */
/******/ (() => { // webpackBootstrap
/******/     var __webpack_modules__ = ({

/***/ "./index.js":
/*!******************!*\
  !*** ./index.js ***!
  \******************/
/***/ (() => {

eval("function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== \"undefined\" && o[Symbol.iterator] || o[\"@@iterator\"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === \"number\") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError(\"Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it[\"return\"] != null) it[\"return\"](); } finally { if (didErr) throw err; } } }; }\n\nfunction _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === \"string\") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === \"Object\" && o.constructor) n = o.constructor.name; if (n === \"Map\" || n === \"Set\") return Array.from(o); if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }\n\nfunction _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\n\nvar create = function create(Class, properity) {\n  var element;\n\n  if (typeof Class === 'string') {\n    element = document.createElement(Class);\n  } else {\n    element = new Class();\n  }\n\n  for (var p in properity) {\n    element.setAttribute(p, properity[p]);\n  }\n\n  for (var _len = arguments.length, children = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {\n    children[_key - 2] = arguments[_key];\n  }\n\n  for (var _i = 0, _children = children; _i < _children.length; _i++) {\n    var child = _children[_i];\n\n    if (typeof child === 'string') {\n      // 文本节点\n      child = document.createTextNode(child);\n    }\n\n    if (child.appendTo) {\n      // Carousel组件\n      child.appendTo(element);\n    } else {\n      // html标签\n      element.appendChild(child);\n    }\n  }\n\n  return element;\n};\n\nvar d = [{\n  img: \"https://static001.geekbang.org/resource/image/bb/21/bb38fb7c1073eaee1755f81131f11d21.jpg\",\n  url: \"https://time.geekbang.org\",\n  title: \"蓝猫\"\n}, {\n  img: \"https://static001.geekbang.org/resource/image/1b/21/1b809d9a2bdf3ecc481322d7c9223c21.jpg\",\n  url: \"https://time.geekbang.org\",\n  title: \"橘猫\"\n}, {\n  img: \"https://static001.geekbang.org/resource/image/b6/4f/b6d65b2f12646a9fd6b8cb2b020d754f.jpg\",\n  url: \"https://time.geekbang.org\",\n  title: \"橘猫加白\"\n}, {\n  img: \"https://static001.geekbang.org/resource/image/73/e4/730ea9c393def7975deceb48b3eb6fe4.jpg\",\n  url: \"https://time.geekbang.org\",\n  title: \"猫\"\n}];\n\nvar Carousel = /*#__PURE__*/function () {\n  function Carousel(data) {\n    _classCallCheck(this, Carousel);\n\n    _defineProperty(this, \"appendTo\", function (node) {\n      node.appendChild(this._root);\n    });\n\n    this._root = document.createElement('div');\n    this._root.classList = ['carousel'];\n    this.children = [];\n  }\n\n  _createClass(Carousel, [{\n    key: \"data\",\n    set: function set(data) {\n      var _this = this;\n\n      this._root.innerHTML = '';\n\n      var _iterator = _createForOfIteratorHelper(data),\n          _step;\n\n      try {\n        for (_iterator.s(); !(_step = _iterator.n()).done;) {\n          var _d = _step.value;\n          var img = document.createElement('img');\n          img.src = _d.img;\n\n          this._root.appendChild(img);\n\n          this.children.push(img);\n        }\n      } catch (err) {\n        _iterator.e(err);\n      } finally {\n        _iterator.f();\n      }\n\n      var i = 0;\n      var current = i;\n      setInterval(function () {\n        var _iterator2 = _createForOfIteratorHelper(_this.children),\n            _step2;\n\n        try {\n          for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {\n            var child = _step2.value;\n            child.style.zIndex = '0';\n          }\n        } catch (err) {\n          _iterator2.e(err);\n        } finally {\n          _iterator2.f();\n        }\n\n        var next = (i + 1) % _this.children.length;\n        var currentElement = _this.children[current];\n        var nextElement = _this.children[next];\n        currentElement.style.zIndex = '1';\n        nextElement.style.zIndex = '2';\n        currentElement.style.transition = 'none';\n        nextElement.style.transition = 'none';\n        currentElement.style.transform = \"translate3d(\".concat(-100 * current, \"%, 0 , 0)\");\n        nextElement.style.transform = \"translate3d(\".concat(100 - 100 * next, \"%, 0 , 0)\");\n        setTimeout(function () {\n          currentElement.style.transition = '';\n          nextElement.style.transition = '';\n          currentElement.style.transform = \"translate3d(\".concat(-100 - 100 * current, \"% 0 , 0)\");\n          nextElement.style.transform = \"translate3d(\".concat(-100 * next, \"%, 0 , 0)\");\n        }, 1000 / 60);\n        current = next;\n        i++;\n      }, 3000);\n    }\n  }, {\n    key: \"setAttribute\",\n    value: function setAttribute(name, value) {\n      this[name] = value; // 这里统一attribute和properties,vue使用的是attribute\n    } // 追加 \n\n  }]);\n\n  return Carousel;\n}();\n\nvar ele = create(\"div\", {\n  id: \"root\",\n  name: \"container\"\n}, create(Carousel, {\n  data: d\n}), create(\"span\", null, \"a\"), create(\"span\", null, \"b\"), create(\"span\", null, \"c\"));\ndocument.body.appendChild(ele);\n\n//# sourceURL=webpack://webpack-jsx/./index.js?");

/***/ })

/******/     });
/************************************************************************/
/******/     
/******/     // startup
/******/     // Load entry module and return exports
/******/     // This entry module can't be inlined because the eval devtool is used.
/******/     var __webpack_exports__ = {};
/******/     __webpack_modules__["./index.js"]();
/******/     
/******/ })()
;

Finally, the actual running effect is the same as before

To be continued!!!

refer to:

HTC(HTML Component) Getting

HTML Component


记得要微笑
1.9k 声望4.5k 粉丝

知不足而奋进,望远山而前行,卯足劲,不减热爱。