原文鏈接: "https://github.com/AlloyTeam/AlloyTouch/wiki/Scoped CSS" 寫在前面 問:什麼是Scoped CSS規範? Scoped CSS規範是Web組件產生不污染其他組件,也不被其他組件污染的CSS規範。 面對組件化的普及,組件的復用很普遍 ...
原文鏈接:https://github.com/AlloyTeam/AlloyTouch/wiki/Scoped-CSS
寫在前面
問:什麼是Scoped CSS規範?
Scoped CSS規範是Web組件產生不污染其他組件,也不被其他組件污染的CSS規範。
面對組件化的普及,組件的復用很普遍的需求,然而CSS相互污染是經常遇見的問題,建立規範讓開發者放心使用各種組件,甚至跨生態的組件是很有必要的一件事情。
目前業界的一些方案
方案一:
如果用webpack的話,可以參考css-loader的這個功能:
一段hash + 組件名,這個可能兼顧了辨識度 + 命名污染的問題。
方案二:
用webpack和scss,less寫成模塊化css就可以一定程度避免CSS污染,不能完全避免
方案三:樣式規範上,使用與組件同名的嵌套命名空間
如果只用自己的生態可以這麼搞,但是有的時候會引入第三方生態,第三方和自己的命名空間一樣還是很有可能,比如scroller插件,社區里也有很多scroller插件loading uplader插件等等。
現有方案的局限性
這裡還是會有污染的情況,因為:
- 模塊化的粒度是大於等於組件化粒度,意思就是一個模塊可能有多個組件
- 非less和sass項目下的組件怎麼保證
- 難以保證不污染第三方組件
- 難以保證不被第三方組件污染
- 同名組件的問題
- 組件在第三方項目使用的問題
- 組件自身生態閉環的問題
所以得出:
用意念或者規範約定不然註入程式自動化避免衝突
好處:
- 能保證不污染別的組件並且不被被的組件污染可以更放心的復用
- Scoped CSS規範是運行時產生唯一id~~ 永遠不會css碰撞
- 返回的這個id那個指定給組件的頂層div就行,實施簡單
如果把這個過程放在構建過程就是工程問題。但是組件單獨抽離出來給第三方用,其實就是組件本身的問題。總之要保證:
- 不污染第三方的項目或組件
- 不被第三組件或項目污染(由於是層疊樣式,這個無法完全保證)
Scoped CSS代碼
;(function () {
function scoper(css) {
var id = generateID();
var prefix = "#" + id;
css = css.replace(/\/\*[\s\S]*?\*\//g, '');
var re = new RegExp("([^\r\n,{}]+)(,(?=[^}]*{)|\s*{)", "g");
css = css.replace(re, function(g0, g1, g2) {
if (g1.match(/^\s*(@media|@keyframes|to|from|@font-face)/)) {
return g1 + g2;
}
if (g1.match(/:scope/)) {
g1 = g1.replace(/([^\s]*):scope/, function(h0, h1) {
if (h1 === "") {
return "> *";
} else {
return "> " + h1;
}
});
}
g1 = g1.replace(/^(\s*)/, "$1" + prefix + " ");
return g1 + g2;
});
addStyle(css,id+"-style");
return id;
}
function generateID() {
var id = ("scoped"+ Math.random()).replace("0.","");
if(document.getElementById(id)){
return generateID();
}else {
return id;
}
}
var isIE = (function () {
var undef,
v = 3,
div = document.createElement('div'),
all = div.getElementsByTagName('i');
while (
div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->',
all[0]
);
return v > 4 ? v : undef;
}());
function addStyle(cssText, id) {
var d = document,
someThingStyles = d.createElement('style');
d.getElementsByTagName('head')[0].appendChild(someThingStyles);
someThingStyles.setAttribute('type', 'text/css');
someThingStyles.setAttribute('id', id);
if (isIE) {
someThingStyles.styleSheet.cssText = cssText;
} else {
someThingStyles.textContent = cssText;
}
}
window.scoper = scoper;
})();
Scoped CSS實施
var id = scoper("h1 {\
color:red;\
/*color: #0079ff;*/\
}\
\
/* h2 {\
color:green\
}*/");
scoper返回的id,在組件的JS裡面賦給包裹的DOM便可以。這裡詳細說下生成id的過程:
function generateID() {
var id = ("scoped"+ Math.random()).replace("0.","");
if(document.getElementById(id)){
return generateID();
}else {
return id;
}
}
通過Math.random得到隨機數並經過處理,然後通過document.getElementById去查詢頁面上有沒有同名ID,有的話則繼續重新生成,沒有的話就使用當前id。這裡需要特別註意的是,比如一些彈出層插件,display hide的時候有的組件是直接從body裡面移除,所以這就帶來了CSS碰撞的可能性,所以這裡Scoped CSS 規範強行約定:後插入的HTML,一定要經過scoper過程重新生成唯一id。
最後,Scoped CSS規範已經在AlloyTouch插件里開始實施,並打算推廣開來。
你有什麼好的想法可以讓跨生態跨項目跨技術棧的組件復用更加愜意,可以交流交流。