回顾

经过昨天的优化处理(【前端优化之拆分CSS】前端三剑客的分分合合),我们在UI一块做了几个关键动作:

① CSS入UI

② CSS作为组件的一个节点而存在,并且会被“格式化”,即选择器带id前缀,形成的组件如图所示:

这样做基本可以规避css污染的问题,解决绝大多数问题,但是更优的方案总是存在,比如web components中的shadow dom!

javascript的组件基本是不可重用的,几个核心原因是:

① 组件实例与实例之间的html、css、Javascript很容易互相污染(id污染、class污染、js变量污染......)
② 一个组件依赖于HTML、CSS、Javascript,而三者之间是分离的,而组件内部控制于js,更改后外部可能出问题
通过昨天的处理,我们将一个组件所用到的全部合到了一起,却又分离成了三个文件:

① ui.js
② ui.html
③ ui.css

这种处理一方面透露着解耦的思想,另一方面体现着解依赖的想法,在这个基础上想引入shadow dom技术,变得非常轻易。

什么是shadow dom

shadow dom是一种浏览器行为,他允许在document文档中渲染时插入一个独立的dom子树,但这个dom树与主dom树完全分离的,不会互相影响。
从一张图来看:

shadow dom事实上也是一个文档碎片,我们甚至可以将之作为jQuery包装对象处理:

存在在shadow dom中的元素是不可被选择器找到的,比如这种做法会徒劳无功:

$('沙箱中的一个元素') => []

另一个比较重要的差别是,外部为组件定义的事件,比如click事件的e.target便只能是组件div了,也就是这个组件事实上只有一层,一个标签,内部的结构不会被暴露!

引入框架

原来我们的组件是这样的结构:

 <div id="ui-view-16" style="">
<div class="cm-num-adjust">
<div class="cm-num-adjust">
<span class="cm-adjust-minus js_num_minus disabled "></span><span class="cm-adjust-view js_cur_num "
contenteditable="true">1个</span> <span class="cm-adjust-plus js_num_plus "></span>
</div>
</div>
</div>

框架会主动创建一个包裹层,包裹层内才是组件dom,经过昨天的处理,组件变成了这样:

 <!--组件生成的包裹层-->
<div id="wrapper" >
<!--组件格式化后的样式-->
<style>
#wrapper { ......}
</style> <!--组件真实的dom结构-->
<div></div>
<div>

如果这里我们使用shadow dom技术的话,整个结构会变成这样:

 <div id="wrapper">
#shadow-root
<style></style>
<div>
</div>
<div>

组件自动创建的dom包裹层,里面神马都没有了,因为事件代理是进不去的,所以开启shadow dom方式的组件需要将事件绑定至shadow节点

当然,并不是所有浏览器都支持shadow dom技术,当此之时,也不是所有的shadow dom都合适;所以UI基类需要做一个开关,最大限度的避免生产风险,而又能引入新的技术

 //与模板对应的css文件,默认不存在,需要各个组件复写
this.uiStyle = null; //保存样式格式化结束的字符串
// this.formateStyle = null; //保存shadow dom的引用,用于事件代理
8 this.shadowDom = null;
9 this.shadowStyle = null;
10 this.shadowRoot = null;
11
12 //框架统一开关,是否开启shadow dom
13 this.openShadowDom = true; // this.openShadowDom = false; //不支持创建接口便关闭,也许有其它因素导致,这个后期已接口放出
if (!this.wrapper[0].createShadowRoot) {
this.openShadowDom = false;
}

基类会多出几个属性处理,shadow逻辑,然后在创建UI dom节点时候需要进行特殊处理

 createRoot: function (html) {

   this.$el = $('<div class="view" style="display: none; " id="' + this.id + '"></div>');
var style = this.getInlineStyle(); //如果存在shadow dom接口,并且框架开启了shadow dom
if (this.openShadowDom) {
//在框架创建的子元素层面创建沙箱
this.shadowRoot = $(this.$el[0].createShadowRoot()); this.shadowDom = $('<div class="js_shadow_root">' + html + '</div>');
this.shadowStyle = $(style); //开启shadow dom情况下,组件需要被包裹起来
this.shadowRoot.append(this.shadowStyle);
this.shadowRoot.append(this.shadowDom); } else { this.$el.html(style + html);
}
},

在开启shadow dom功能的情况下,便会为根节点创建shadow root,将style节点与html节点装载进去,这个时候UI结构基本出来了,事件便绑定至shadow root即可,这里是全部代码:

 define([], function () {

   var getBiggerzIndex = (function () {
var index = 3000;
return function (level) {
return level + (++index);
};
})(); return _.inherit({
propertys: function () {
//模板状态
this.wrapper = $('body');
this.id = _.uniqueId('ui-view-'); this.template = ''; //与模板对应的css文件,默认不存在,需要各个组件复写
this.uiStyle = null; //保存样式格式化结束的字符串
// this.formateStyle = null; //保存shadow dom的引用,用于事件代理
this.shadowDom = null;
this.shadowStyle = null;
this.shadowRoot = null; //框架统一开关,是否开启shadow dom
this.openShadowDom = true; // this.openShadowDom = false; //不支持创建接口便关闭,也许有其它因素导致,这个后期已接口放出
if (!this.wrapper[0].createShadowRoot) {
this.openShadowDom = false;
} this.datamodel = {};
this.events = {}; //自定义事件
//此处需要注意mask 绑定事件前后问题,考虑scroll.radio插件类型的mask应用,考虑组件通信
this.eventArr = {}; //初始状态为实例化
this.status = 'init'; this.animateShowAction = null;
this.animateHideAction = null; // this.availableFn = function () { } }, on: function (type, fn, insert) {
if (!this.eventArr[type]) this.eventArr[type] = []; //头部插入
if (insert) {
this.eventArr[type].splice(0, 0, fn);
} else {
this.eventArr[type].push(fn);
}
}, off: function (type, fn) {
if (!this.eventArr[type]) return;
if (fn) {
this.eventArr[type] = _.without(this.eventArr[type], fn);
} else {
this.eventArr[type] = [];
}
}, trigger: function (type) {
var _slice = Array.prototype.slice;
var args = _slice.call(arguments, 1);
var events = this.eventArr;
var results = [], i, l; if (events[type]) {
for (i = 0, l = events[type].length; i < l; i++) {
results[results.length] = events[type][i].apply(this, args);
}
}
return results;
}, bindEvents: function () {
var events = this.events;
var el = this.$el;
if (this.openShadowDom) el = this.shadowRoot; if (!(events || (events = _.result(this, 'events')))) return this;
this.unBindEvents(); // 解析event参数的正则
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
var key, method, match, eventName, selector; // 做简单的字符串数据解析
for (key in events) {
method = events[key];
if (!_.isFunction(method)) method = this[events[key]];
if (!method) continue; match = key.match(delegateEventSplitter);
eventName = match[1], selector = match[2];
method = _.bind(method, this);
eventName += '.delegateUIEvents' + this.id; if (selector === '') {
el.on(eventName, method);
} else {
el.on(eventName, selector, method);
}
} return this;
}, unBindEvents: function () {
var el = this.$el;
if (this.openShadowDom) el = this.shadowRoot; el.off('.delegateUIEvents' + this.id);
return this;
}, createRoot: function (html) { this.$el = $('<div class="view" style="display: none; " id="' + this.id + '"></div>');
var style = this.getInlineStyle(); //如果存在shadow dom接口,并且框架开启了shadow dom
if (this.openShadowDom) {
//在框架创建的子元素层面创建沙箱
this.shadowRoot = $(this.$el[0].createShadowRoot()); this.shadowDom = $('<div class="js_shadow_root">' + html + '</div>');
this.shadowStyle = $(style); //开启shadow dom情况下,组件需要被包裹起来
this.shadowRoot.append(this.shadowStyle);
this.shadowRoot.append(this.shadowDom); } else { this.$el.html(style + html);
}
}, getInlineStyle: function () {
//如果不存在便不予理睬
if (!_.isString(this.uiStyle)) return null;
var style = this.uiStyle, uid = this.id; //在此处理shadow dom的样式,直接返回处理结束后的html字符串
if (!this.openShadowDom) {
//创建定制化的style字符串,会模拟一个沙箱,该组件样式不会对外影响,实现原理便是加上#id 前缀
style = style.replace(/(\s*)([^\{\}]+)\{/g, function (a, b, c) {
return b + c.replace(/([^,]+)/g, '#' + uid + ' $1') + '{';
});
} style = '<style >' + style + '</style>';
this.formateStyle = style;
return style;
}, render: function (callback) {
var data = this.getViewModel() || {}; var html = this.template;
if (!this.template) return '';
if (data) {
html = _.template(this.template)(data);
} typeof callback == 'function' && callback.call(this);
return html;
}, //刷新根据传入参数判断是否走onCreate事件
//这里原来的dom会被移除,事件会全部丢失 需要修复*****************************
refresh: function (needEvent) {
var html = '';
this.resetPropery();
//如果开启了沙箱便只能重新渲染了
if (needEvent) {
this.create();
} else {
html = this.render();
if (this.openShadowDom) {
//将解析后的style与html字符串装载进沙箱
//*************
this.shadowDom.html(html);
} else {
this.$el.html(this.formateStyle + html);
}
}
this.initElement();
if (this.status != 'hide') this.show();
this.trigger('onRefresh');
}, _isAddEvent: function (key) {
if (key == 'onCreate' || key == 'onPreShow' || key == 'onShow' || key == 'onRefresh' || key == 'onHide')
return true;
return false;
}, setOption: function (options) {
//这里可以写成switch,开始没有想到有这么多分支
for (var k in options) {
if (k == 'datamodel' || k == 'events') {
_.extend(this[k], options[k]);
continue;
} else if (this._isAddEvent(k)) {
this.on(k, options[k])
continue;
}
this[k] = options[k];
}
// _.extend(this, options);
}, initialize: function (opts) {
this.propertys();
this.setOption(opts);
this.resetPropery();
//添加系统级别事件
this.addEvent();
//开始创建dom
this.create();
this.addSysEvents(); this.initElement(); }, //内部重置event,加入全局控制类事件
addSysEvents: function () {
if (typeof this.availableFn != 'function') return;
this.removeSysEvents();
this.$el.on('click.system' + this.id, $.proxy(function (e) {
if (!this.availableFn()) {
e.preventDefault();
e.stopImmediatePropagation && e.stopImmediatePropagation();
}
}, this));
}, removeSysEvents: function () {
this.$el.off('.system' + this.id);
}, $: function (selector) {
return this.openShadowDom ? this.shadowDom.find(selector) : this.$el.find(selector);
}, //提供属性重置功能,对属性做检查
resetPropery: function () {
}, //各事件注册点,用于被继承
addEvent: function () {
}, create: function () {
this.trigger('onPreCreate');
this.createRoot(this.render()); this.status = 'create';
this.trigger('onCreate');
}, //实例化需要用到到dom元素
initElement: function () { }, show: function () {
if (!this.wrapper[0] || !this.$el[0]) return;
//如果包含就不要乱搞了
if (!$.contains(this.wrapper[0], this.$el[0])) {
this.wrapper.append(this.$el);
} this.trigger('onPreShow'); if (typeof this.animateShowAction == 'function')
this.animateShowAction.call(this, this.$el);
else
this.$el.show(); this.status = 'show';
this.bindEvents();
this.trigger('onShow');
}, hide: function () {
if (!this.$el || this.status !== 'show') return; this.trigger('onPreHide'); if (typeof this.animateHideAction == 'function')
this.animateHideAction.call(this, this.$el);
else
this.$el.hide(); this.status = 'hide';
this.unBindEvents();
this.removeSysEvents();
this.trigger('onHide');
}, destroy: function () {
this.status = 'destroy';
this.unBindEvents();
this.removeSysEvents();
this.$el.remove();
this.trigger('onDestroy');
delete this;
}, getViewModel: function () {
return this.datamodel;
}, setzIndexTop: function (el, level) {
if (!el) el = this.$el;
if (!level || level > 10) level = 0;
level = level * 1000;
el.css('z-index', getBiggerzIndex(level));
} }); });

基类代码改动结束,一旦开启shadow dom开关,每个组件便会走shadow逻辑,否则走原逻辑:

关闭接口的话,又变成了这个样子了:

引入shadow dom的意义

web components的提出,旨在解决UI重用的问题、解决相同功能接口各异的问题,大规模的用于生产似乎不太接地气,但是shadow dom技术对于webapp却是个好东西。

上文还只是在UI层面上应用shadow dom技术,webapp中每个view页面片如果可以应用shadow dom技术的话,各个View将不必考虑id重复污染、css样式污染、javascript变量污染,并且效率还比原来高多了,因为对于页面来说,他就仅仅是一个标签而已,如此一来,大规模的webapp的网站可能真的会到来了!

demo地址:http://yexiaochai.github.io/cssui/demo/debug.html#num

代码地址:https://github.com/yexiaochai/cssui/tree/gh-pages

博主正在学习web components技术,并且尝试将之用于项目,文中有误或者有不妥的地方请您提出

【shadow dom入UI】web components思想如何应用于实际项目的更多相关文章

  1. Web Components初探

    本文来自 mweb.baidu.com 做最好的无线WEB研发团队 是随着 Web 应用不断丰富,过度分离的设计也会带来可重用性上的问题.于是各家显神通,各种 UI 组件工具库层出不穷,煞有八仙过海之 ...

  2. 使用shadow dom封装web组件

    什么是shadow dom? 首先我们先来看看它长什么样子.在HTML5中,我们只用写如下简单的两行代码,就可以通过 <video> 标签来创建一个浏览器自带的视频播放器控件. <v ...

  3. 【Web技术】401- 在 React 中使用 Shadow DOM

    本文作者:houfeng 1. Shadow DOM 是什么 Shadow DOM 是什么?我们先来打开 Chrome 的 DevTool,并在 'Settings -> Preferences ...

  4. 【Web技术】400- 浅谈Shadow DOM

    编者按:本文作者:刘观宇,360 奇舞团高级前端工程师.技术经理,W3C CSS 工作组成员. 为什么会有Shadow DOM 你在实际的开发中很可能遇到过这样的需求:实现一个可以拖拽的滑块,以实现范 ...

  5. [Web Component] Allow External Styling of a Web Component's Shadow DOM

    The Shadow DOM protects your components from style conflicts. The same protection also makes it hard ...

  6. html fragment & html template & virtual DOM & web components

    html fragment & html template & virtual DOM https://developer.mozilla.org/en-US/docs/Web/API ...

  7. Polymer——Web Components的未来

    什么是polymer? polymer由谷歌的Palm webOS团队打造,并在2013 Google I/O大会上推出,旨在实现Web Components,用最少的代码,解除框架间的限制的UI 框 ...

  8. The state of Web Components

    Web Components have been on developers’ radars for quite some time now. They were first introduced b ...

  9. web Components 学习之路

    就目前而言,纯粹的Web Components在兼容性方面还有着较为长远的路,这里做个记录总结,以纪念自己最近关于Web Components的学习道路. 参考教材 JavaScript 标准参考教程 ...

随机推荐

  1. IT雇员及外包商选择:人品第一

    最近,苹果iOS操作系统和智能手机爆出了一个奇葩故障,在播放特定一段五秒钟的视频时能导致手机死机.唯一的解决办法是按住电源键和Home按键进行手机的重启. 第十八届中国国际高新技术成果交易会在深圳举办 ...

  2. qt5中信号和槽的新语法

    qt5中的连接 有下列几种方式可以连接到信号上 旧语法 qt5将继续支持旧的语法去连接,在QObject对象上定义信号和槽函数,及任何继承QObjec的对象(包含QWidget). connect(s ...

  3. Leetcode 笔记 99 - Recover Binary Search Tree

    题目链接:Recover Binary Search Tree | LeetCode OJ Two elements of a binary search tree (BST) are swapped ...

  4. ASP.NET MVC 系列随笔汇总[未完待续……]

    ASP.NET MVC 系列随笔汇总[未完待续……] 为了方便大家浏览所以整理一下,有的系列篇幅中不是很全面以后会慢慢的补全的. 学前篇之: ASP.NET MVC学前篇之扩展方法.链式编程 ASP. ...

  5. ASP.NET MVC Model绑定(二)

    ASP.NET MVC Model绑定(二) 前言 上篇对于Model绑定的简单演示想必大家对Model绑定的使用方式有一点的了解,那大家有没有想过Model绑定器是在什么时候执行的?又或是执行的过程 ...

  6. 模拟实现Spring中的注解装配

    本文原创,地址为http://www.cnblogs.com/fengzheng/p/5037359.html 在Spring中,XML文件中的bean配置是实现Spring IOC的核心配置文件,在 ...

  7. stanford corenlp自定义切词类

    stanford corenlp的中文切词有时不尽如意,那我们就需要实现一个自定义切词类,来完全满足我们的私人定制(加各种词典干预).上篇文章<IKAnalyzer>介绍了IKAnalyz ...

  8. 游戏编程系列[2]--游戏编程中RPC与OpLog协议的结合--序

    在系列[1]中,我们展示了RPC调用协议的定义以及演示,通过方法定义以及协议约定,进行了协议约定以及调用过程的约定.然而,实际上在游戏中,调用过程之后,需要传输相对多的数据给服务端. 常用场景,客户端 ...

  9. 如何优雅地使用Sublime Text

    Sublime Text:一款具有代码高亮.语法提示.自动完成且反应快速的编辑器软件,不仅具有华丽的界面,还支持插件扩展机制,用她来写代码,绝对是一种享受.相比于难于上手的Vim,浮肿沉重的Eclip ...

  10. Failure to find xxx in xxx was cached in the local repository, resolution will not be reattempted until the update interval of nexus has elapsed or updates are forced @ xxx

    问题: 在linux服务器上使用maven编译war时报错: 16:41:35 [FATAL] Non-resolvable parent POM for ***: Failure to find * ...