回顾

经过昨天的优化处理(【前端优化之拆分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. MySQL 优化之 MRR (Multi-Range Read:二级索引合并回表)

    MySQL5.6中引入了MRR,专门来优化:二级索引的范围扫描并且需要回表的情况.它的原理是,将多个需要回表的二级索引根据主键进行排序,然后一起回表,将原来的回表时进行的随机IO,转变成顺序IO.文档 ...

  2. 技术笔记:Indy的TIdSMTP改造,解决发送Html和主题截断问题

    使用Indy来发邮件坑不少啊,只不过有比没有好吧,使用delphi6这种老工具没办法,只能使用了新一点的Indy版本9,公司限制... 1.邮件包含TIdText和TIdAttachment时会出现T ...

  3. XSS 前端防火墙 —— 天衣无缝的防护

    上一篇讲解了钩子程序的攻防实战,并实现了一套对框架页的监控方案,将防护作用到所有子页面. 到目前为止,我们防护的深度已经差不多,但广度还有所欠缺. 例如,我们的属性钩子只考虑了 setAttribut ...

  4. EasyPR--开发详解(5)颜色定位与偏斜扭转

    本篇文章介绍EasyPR里新的定位功能:颜色定位与偏斜扭正.希望这篇文档可以帮助开发者与使用者更好的理解EasyPR的设计思想. 让我们先看一下示例图片,这幅图片中的车牌通过颜色的定位法进行定位并从偏 ...

  5. 使用xUnit,EF,Effort和ABP进行单元测试(C#)

    返回总目录<一步一步使用ABP框架搭建正式项目系列教程> 本篇目录 介绍 创建测试项目 准备测试基类 创建第一个测试 测试异常 在测试中使用仓储 测试异步方法 小结 介绍 在这篇博客中,我 ...

  6. .NET 基础一步步一幕幕[面向对象前言]

    面向对象前言 2017年的第一篇博文,好久不写博文了,赶紧补上,感觉在以前的<.NET 基础一步步一幕幕>系列博客中,简短的小知识点已经介绍的差不多的(PS:如果还有别的基础知识点我没有介 ...

  7. MailKit---获取邮件

    MailKit是一个免费开源的邮箱类库,简单来说MailKit帮我们封装了有关邮箱的一些帮助类,提供方法让我们更容易使用邮箱的Smtp,Imap等邮箱协议. 现在的邮箱基本上都使用smtp协议从邮件服 ...

  8. 数据库中树形列表(以easyui的tree为例)

    构造一棵easyui前台框架的一个树形列表为例后台框架是spring MVC+JPA. 先看一下数据库是怎么建的,怎么存放的数据 下面是实体类 /** * 部门类 用户所属部门(这里的部门是一个相对抽 ...

  9. javaEE设计模式——门面模式

    1.本节内容 门面模式的意图介绍 门面模式带来的好处 门面模式的应用场景 实现模式的3中方式:POJO.无状态与有状态回话Bean门面 有状态与无状态回话Bean门面的重要差别 关于门面模式使用的警告 ...

  10. Java异常总结

    异常就是在程序中可能要发生的未知错误,java机制中异常分为2大类:Exception和Error. 对异常的处理方式有2种,一是将异常通过关键字throws抛出,二是将异常进行try catch处理 ...