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 ofHTML
tags, special tags, and scripts that define theDHTML
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>
: definesHTC
, this tag is the parent element of other definitions.<PUBLIC:PROPERTY NAME=”pName” GET=”getMethod” PUT=”putMethod” />
: Define the attributes ofHTC
. The three definitions in it represent the attribute name, read attribute, and method called byHTC
when setting attribute.<PUBLIC:METHOD NAME=”mName” />
: defines the method ofHTC
, andNAME
defines the method name.<PUBLIC:EVENT NAME=”eName” ID=”eId” />
: defines the event ofHTC
,NAME
defines the event name, andID
is an optional attribute that uniquely identifies this event inHTC
.<PUBLID:ATTACH EVENT=”sEvent” ONEVENT=”doEvent” />
: Defines the corresponding method passed by the browser to theHTC
event, whereEVENT
is the event passed in by the browser, andONEVENT
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
<!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:
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:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。