【JS 】讓瀏覽器支持<style scoped>

1. HTML style scoped 屬性

今天無意中發(fā)現(xiàn)居然還有這么一個神仙屬性 HTML style scoped 屬性
他的作用主要是將 style的作用域控制在他自身的父元素之內(nèi)冲茸。
例如:

<div>
  <style type="text/css" scoped>
    h1 {color:red;}
    p {color:blue;}
  </style>
  <h1>這個標題是紅色的</h1>
  <p>這個段落是藍色的斋竞。</p>
</div>

<h1>這個標題是黑色的</h1>
<p>這個段落是黑色的。</p>

以上代碼中style中的樣式只能作用于第一個div內(nèi)的元素适篙。
這樣一個功能應(yīng)用在目前公司老項目中非常nice(asp.net+mvc,導入部分頁就不會影響當前頁其他元素了)
但是可惜,除了火狐,其他瀏覽器都不支持V健!!


所以我決定自己實現(xiàn)一個

2. 思路

  1. 判斷瀏覽器是否支持scoped宾茂,
  2. 找到頁面中所有帶有scoped屬性的style元素瓷马,給其父元素添加一個屬性
  3. 修改style中的所有css選擇器,加上屬性前綴
  4. 監(jiān)聽dom變化跨晴,處理動態(tài)創(chuàng)建的<style scoped>

3. 編碼

1. 判斷瀏覽器是否支持scoped

if (document.createElement("STYLE").scoped !== undefined) {
    return;
}

2. 找到頁面中所有帶有scoped屬性的style元素欧聘,給其父元素添加一個屬性

[...document.getElementsByTagName('style')].forEach(x => updateStyle(x));
/**
 * 處理 <style> 標簽
 * @param style dom
 */
function updateStyle(style) {
    if (!style
        || style.parentElement == null
        || style.parentElement == document.head
        || style.parentElement == document.body
        || !style.hasAttribute("scoped")
        || style.scoped === true) {
        return;
    }
    style.scoped = true; // 防止重復處理
    const scope = ("_" + (Math.random() * 100000000).toString("36")).replace('.', '');
    style.parentElement.setAttribute(scope, '');
    // 處理css
}

3. 修改style中的所有css選擇器,加上屬性前綴

/**
 * 為選擇器添加范圍前綴scope
 * @param {String} selector css選擇器
 * @param {String} scope css范圍前綴
 * @returns 返回修改后的css選擇器
 */
function updateSelector(selector, scope) {
    return selector.split(",").map(x => x.replace(/((?<=^\s*)\s|^)([\S])/, "$1" + scope + " $2")).join(",");
}
/**
 * 將css代碼中所有選擇器都加上范圍前綴scope
 * @param {String} css css代碼
 * @param {String} scope css范圍前綴
 * @returns 返回修改后的css代碼
 */
function updateCss(css, scope) {
    // css = css.replace(/[\n\r\s]+/g, " "); // 去掉多余的空格和回車
    css = css.replace(/("[^"]*")|('[^']*')/g, x => x.charAt(0) + encodeURIComponent(x.slice(1, -1)) + x.charAt(0)); // 去字符串干擾 
    css = css.replace(/[^{}]+{/g, x => updateSelector(x, scope));
    css = css.replace(/("[^"]*")|('[^']*')/g, x => x.charAt(0) + decodeURIComponent(x.slice(1, -1)) + x.charAt(0)); // 還原字符串 
    return css;
}

用正則處理還是會有一些問題的端盆,不過勝在代碼簡潔怀骤,實際使用中只要注意避開就好了,比如下面這段css就會有問題

@keyframes show {
    100% {
        opacity: 1;
    }
}

4. 監(jiān)聽dom變化焕妙,處理動態(tài)創(chuàng)建的<style scoped>

// 當觀察到變動時執(zhí)行的回調(diào)函數(shù)
const callback = function (mutationsList) {
    if (mutationsList.length == 0) {
        return;
    }
    for (const mutation of mutationsList) {
        for (const node of mutation.addedNodes) {
            if (node.nodeName === "STYLE") {
                updateStyle(node);
            }
        }
    }
};
// 創(chuàng)建一個觀察器實例并傳入回調(diào)函數(shù)
const observer = new MutationObserver(callback);
const config = { childList: true, subtree: true };
observer.observe(document, config);
// MutationObserver 是異步的蒋伦,回調(diào)時間不可控,所以需要設(shè)置一個輪詢
setInterval(() => callback(observer.takeRecords()), 100);

5. 完整代碼


// style scoped
(() => {
    // 檢測原生 style是否支持 scoped 
    if (document.createElement("STYLE").scoped !== undefined) {
        return;
    }
    document.createElement("STYLE").__proto__.scoped = false;

    /**
     * 為選擇器添加范圍前綴scope
     * @param {String} selector css選擇器
     * @param {String} scope css范圍前綴
     * @returns 返回修改后的css選擇器
     */
    function updateSelector(selector, scope) {
        return selector.split(",").map(x => x.replace(/((?<=^\s*)\s|^)([\S])/, "$1" + scope + " $2")).join(",");
    }

    /**
     * 將css代碼中所有選擇器都加上范圍前綴scope
     * @param {String} css css代碼
     * @param {String} scope css范圍前綴
     * @returns 返回修改后的css代碼
     */
    function updateCss(css, scope) {
        // css = css.replace(/[\n\r\s]+/g, " "); // 去掉多余的空格和回車
        css = css.replace(/("[^"]*")|('[^']*')/g, x => x.charAt(0) + encodeURIComponent(x.slice(1, -1)) + x.charAt(0)); // 去字符串干擾 
        css = css.replace(/[^{}]+{/g, x => updateSelector(x, scope));
        css = css.replace(/("[^"]*")|('[^']*')/g, x => x.charAt(0) + decodeURIComponent(x.slice(1, -1)) + x.charAt(0)); // 還原字符串 
        return css;
    }

    /**
     * 處理 <style> 標簽
     * @param style dom
     */
    function updateStyle(style) {
        if (!style
            || style.parentElement == null
            || style.parentElement == document.head
            || style.parentElement == document.body
            || !style.hasAttribute("scoped")
            || style.scoped === true) {
            return;
        }
        style.scoped = true; // 防止重復處理

        const scope = ("_" + (Math.random() * 100000000).toString("36")).replace('.', '');
        style.parentElement.setAttribute(scope, '');

        style.textContent = updateCss(style.textContent, "[" + scope + "]");
    }

    // 當觀察到變動時執(zhí)行的回調(diào)函數(shù)
    const callback = function (mutationsList) {
        if (mutationsList.length == 0) {
            return;
        }
        for (const mutation of mutationsList) {
            for (const node of mutation.addedNodes) {
                if (node.nodeName === "STYLE") {
                    updateStyle(node);
                }
            }
        }
    };

    // 創(chuàng)建一個觀察器實例并傳入回調(diào)函數(shù)
    const observer = new MutationObserver(callback);
    const config = { childList: true, subtree: true };
    observer.observe(document, config);

    // MutationObserver 是異步的焚鹊,回調(diào)時間不可控痕届,所以需要設(shè)置一個輪詢
    setInterval(() => callback(observer.takeRecords()), 100);

    [...document.getElementsByTagName('style')].forEach(x => updateStyle(x));
})();

6. Demo

https://jsrun.net/tgcKp

7. 項目中應(yīng)用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市末患,隨后出現(xiàn)的幾起案子研叫,更是在濱河造成了極大的恐慌,老刑警劉巖璧针,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嚷炉,死亡現(xiàn)場離奇詭異,居然都是意外死亡探橱,警方通過查閱死者的電腦和手機申屹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來隧膏,“玉大人哗讥,你說我怎么就攤上這事“恚” “怎么了忌栅?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長曲稼。 經(jīng)常有香客問我,道長湖员,這世上最難降的妖魔是什么贫悄? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮娘摔,結(jié)果婚禮上窄坦,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好鸭津,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布彤侍。 她就那樣靜靜地躺著,像睡著了一般逆趋。 火紅的嫁衣襯著肌膚如雪盏阶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天闻书,我揣著相機與錄音名斟,去河邊找鬼。 笑死魄眉,一個胖子當著我的面吹牛砰盐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播坑律,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼岩梳,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了晃择?” 一聲冷哼從身側(cè)響起冀值,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎藕各,沒想到半個月后池摧,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡激况,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年作彤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乌逐。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡竭讳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出浙踢,到底是詐尸還是另有隱情绢慢,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布洛波,位于F島的核電站胰舆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蹬挤。R本人自食惡果不足惜缚窿,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望焰扳。 院中可真熱鬧倦零,春花似錦误续、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至葫隙,卻和暖如春栽烂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背停蕉。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工愕鼓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人慧起。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓菇晃,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蚓挤。 傳聞我的和親對象是個殘疾皇子磺送,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353