寫在前面
問:什么是Scoped CSS規(guī)范?
Scoped CSS規(guī)范是Web組件產(chǎn)生不污染其他組件,也不被其他組件污染的CSS規(guī)范。
面對(duì)組件化的普及,組件的復(fù)用很普遍的需求,然而CSS相互污染是經(jīng)常遇見的問題,建立規(guī)范讓開發(fā)者放心使用各種組件,甚至跨生態(tài)的組件是很有必要的一件事情。
目前業(yè)界的一些方案
方案一:
如果用webpack的話,可以參考css-loader的這個(gè)功能:
一段hash + 組件名,這個(gè)可能兼顧了辨識(shí)度 + 命名污染的問題。
方案二:
用webpack和scss,less寫成模塊化css就可以一定程度避免CSS污染,不能完全避免
方案三:樣式規(guī)范上,使用與組件同名的嵌套命名空間
如果只用自己的生態(tài)可以這么搞,但是有的時(shí)候會(huì)引入第三方生態(tài),第三方和自己的命名空間一樣還是很有可能,比如scroller插件,社區(qū)里也有很多scroller插件loading uplader插件等等。
現(xiàn)有方案的局限性
這里還是會(huì)有污染的情況,因?yàn)椋?/p>
模塊化的粒度是大于等于組件化粒度,意思就是一個(gè)模塊可能有多個(gè)組件
非less和sass項(xiàng)目下的組件怎么保證
難以保證不污染第三方組件
難以保證不被第三方組件污染
同名組件的問題
組件在第三方項(xiàng)目使用的問題
組件自身生態(tài)閉環(huán)的問題
所以得出:
用意念或者規(guī)范約定不然注入程序自動(dòng)化避免沖突
好處:
能保證不污染別的組件并且不被被的組件污染可以更放心的復(fù)用
Scoped CSS規(guī)范是運(yùn)行時(shí)產(chǎn)生唯一id~~ 永遠(yuǎn)不會(huì)css碰撞
返回的這個(gè)id那個(gè)指定給組件的頂層div就行,實(shí)施簡(jiǎn)單
如果把這個(gè)過(guò)程放在構(gòu)建過(guò)程就是工程問題。但是組件單獨(dú)抽離出來(lái)給第三方用,其實(shí)就是組件本身的問題。總之要保證:
不污染第三方的項(xiàng)目或組件
不被第三組件或項(xiàng)目污染(由于是層疊樣式,這個(gè)無(wú)法完全保證)
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實(shí)施
var id = scoper("h1 {\ color:red;\ /*color: #0079ff;*/\ }\ \ /* h2 {\ color:green\ }*/");
scoper返回的id,在組件的JS里面賦給包裹的DOM便可以。這里詳細(xì)說(shuō)下生成id的過(guò)程:
function generateID() { var id = ("scoped"+ Math.random()).replace("0.",""); if(document.getElementById(id)){ return generateID(); }else { return id; }}
通過(guò)Math.random得到隨機(jī)數(shù)并經(jīng)過(guò)處理,然后通過(guò)document.getElementById去查詢頁(yè)面上有沒有同名ID,有的話則繼續(xù)重新生成,沒有的話就使用當(dāng)前id。這里需要特別注意的是,比如一些彈出層插件,display hide的時(shí)候有的組件是直接從body里面移除,所以這就帶來(lái)了CSS碰撞的可能性,所以這里Scoped CSS 規(guī)范強(qiáng)行約定:后插入的HTML,一定要經(jīng)過(guò)scoper過(guò)程重新生成唯一id。
最后,Scoped CSS規(guī)范已經(jīng)在AlloyTouch插件里開始實(shí)施,并打算推廣開來(lái)。
你有什么好的想法可以讓跨生態(tài)跨項(xiàng)目跨技術(shù)棧的組件復(fù)用更加愜意,可以交流交流。