帮助中心

这里有最新的使用文档和教程

< 返回

JS让浏览器支持<style scoped>

2023-04-20 19:11 作者:31IDC 阅读量:679 所属分类:Linux系统

【JS 】让浏览器支持<style scoped>

1. HTML style scoped 属性

今天无意中发现居然还有这么一个神仙属性 HTML style scoped 属性
他的作用主要是将 style的作用域控制在他自身的父元素之内。
例如:

<div>
  <style type="text/css" scoped>
    h1 {color:red;}
    p {color:blue;}
  </style>
  <h1>这个标题是红色的</h1>
  <p>这个段落是蓝色的。</p>
</div>

<h1>这个标题是黑色的</h1>
<p>这个段落是黑色的。</p>

以上代码中style中的样式只能作用于第一个div内的元素。
这样一个功能应用在目前公司老项目中非常nice(asp.net+mvc,导入部分页就不会影响当前页其他元素了)
但是可惜,除了火狐,其他浏览器都不支持!!!

 
 

所以我决定自己实现一个

 

2. 思路

  1. 判断浏览器是否支持scoped
  2. 找到页面中所有带有scoped属性的style元素,给其父元素添加一个属性
  3. 修改style中的所有css选择器,加上属性前缀
  4. 监听dom变化,处理动态创建的<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. 监听dom变化,处理动态创建的<style scoped>

// 当观察到变动时执行的回调函数
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);
            }
        }
    }
};
// 创建一个观察器实例并传入回调函数
const observer = new MutationObserver(callback);
const config = { childList: true, subtree: true };
observer.observe(document, config);
// MutationObserver 是异步的,回调时间不可控,所以需要设置一个轮询
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(&        

31IDC - 12 年深耕海外 IDC 高端资源