使用Javascript将CSS样式表作为字符串注入

68

我正在开发一个Chrome扩展,希望用户能够添加自己的CSS样式来改变扩展页面的外观(不是网页)。我查看了使用document.stylesheets的方法,但它似乎希望将规则拆分,并且不允许您注入完整的样式表。是否有解决方案可以让我使用字符串在页面上创建新样式表?

目前我没有使用jQuery或类似的东西,因此纯JavaScript解决方案更可取。


你有没有研究过 document.body.appendchild? - gaynorvader
https://dev59.com/BnRB5IYBdhLWcg3wJUeu - Christophe Roussy
最后一个答案是最有意义的。 - MartianMartian
6个回答

136

有几种方法可以实现这个,但是最简单的方法是创建一个<style>元素,设置它的 textContent 属性,并将其附加到页面的<head>中。

/**
 * Utility function to add CSS in multiple passes.
 * @param {string} styleString
 */
function addStyle(styleString) {
  const style = document.createElement('style');
  style.textContent = styleString;
  document.head.append(style);
}

addStyle(`
  body {
    color: red;
  }
`);

addStyle(`
  body {
    background: silver;
  }
`);
如果你愿意的话,可以稍微改动一下,这样当调用addStyle()时CSS就会被替换而不是追加。
/**
 * Utility function to add replaceable CSS.
 * @param {string} styleString
 */
const addStyle = (() => {
  const style = document.createElement('style');
  document.head.append(style);
  return (styleString) => style.textContent = styleString;
})();

addStyle(`
  body {
    color: red;
  }
`);

addStyle(`
  body {
    background: silver;
  }
`);

IE编辑:请注意,IE9及以下版本仅允许使用最多32个样式表,因此在使用第一个代码片段时要小心。该数字在IE10中增加到4095。

2020编辑:这个问题非常老了,但我仍然偶尔会收到有关它的通知,因此我已经更新了代码,使其略微现代化,并用.textContent替换了.innerHTML。虽然这个特定的实例是安全的,但在可能的情况下尽量避免使用innerHTML,因为它可能是一种XSS攻击向量。


我最终也会想到使用innerHTML,但你提供的第二个片段真的很酷! - Dan Hlavenka
这两个片段都给了我一个错误:TypeError: document.body是null。 document.body.appendChild(node); - angry kiwi
你不能通过将字符串追加到节点的innerHTML属性上来实现连续添加吗?例如像 node.innerHTML = node.innerHTML + " " + str; 这样的操作? - brandito

21

多亏了这个人,我找到了正确的答案。下面是具体方法:

function addCss(rule) {
  let css = document.createElement('style');
  css.type = 'text/css';
  if (css.styleSheet) css.styleSheet.cssText = rule; // Support for IE
  else css.appendChild(document.createTextNode(rule)); // Support for the rest
  document.getElementsByTagName("head")[0].appendChild(css);
}

// CSS rules
let rule  = '.red {background-color: red}';
    rule += '.blue {background-color: blue}';

// Load the rules and execute after the DOM loads
window.onload = function() {addCss(rule)};

fiddle


8

你听说过Promise吗?它们可以在所有现代浏览器中使用,而且相对简单易用。看看这个简单的方法如何注入css到html头部:

function loadStyle(src) {
    return new Promise(function (resolve, reject) {
        let link = document.createElement('link');
        link.href = src;
        link.rel = 'stylesheet';

        link.onload = () => resolve(link);
        link.onerror = () => reject(new Error(`Style load error for ${src}`));

        document.head.append(link);
    });
}

你可以按照以下方式实现:
window.onload = function () {
    loadStyle("https://fonts.googleapis.com/css2?family=Raleway&display=swap")
        .then(() => loadStyle("css/style.css"))
        .then(() => loadStyle("css/icomoon.css"))
        .then(() => {
            alert('All styles are loaded!');
        }).catch(err => alert(err));
}

这真的很酷,对吧?这是使用 Promises 来确定样式优先级的一种方式。
或者,如果您想同时导入所有样式,可以这样做:
function loadStyles(srcs) {
    let promises = [];
    srcs.forEach(src => promises.push(loadStyle(src)));
    return Promise.all(promises);
}

使用方法如下:

loadStyles([    'css/style.css',    'css/icomoon.css']);

你可以实现自己的方法,例如按优先级导入脚本,同时导入脚本或同时导入样式和脚本。如果我得到更多票数,我会发布我的实现。
如果你想了解更多关于 Promises 的内容,请阅读 这里

确实很有趣。 在这里,您等待CSS的结尾以加载下一个。 一次性全部加载不是更有效吗? - Pascal Ganaye
一次性加载所有内容肯定更高效。但在某些情况下,可能需要分配特定的优先级。我已经发布了一个同时样式加载的实现。 - Iglesias Leonardo
我猜这是更好的解决方案,因为 Promises 将异步运行,并且不会阻止渲染,与已接受的解决方案相反。 - user1020069

7

我认为注入任何HTML字符串的最简单方法是通过:insertAdjacentHTML

// append style block in <head>

const someStyle = `
<style>
    #someElement { color: green; }
</style>
`;

document.head.insertAdjacentHTML('beforeend', someStyle);

所有其他答案都给简单的事情增加了复杂性。这个答案之所以优雅,是因为它很简单。 - Francisco Luz
在2023年,这是正确的答案。 - DaveBurns
这种方法还允许IDE突出显示CSS语法。 - undefined

2
我最近也有同样的需求,编写了一个功能来完成与Liam相同的操作,但还允许多行CSS。
injectCSS(function(){/*
    .ui-button {
        border: 3px solid #0f0;
        font-weight: bold;
        color: #f00;
    }
    .ui-panel {
        border: 1px solid #0f0;
        background-color: #eee;
        margin: 1em;
    }
*/});

// or the following for one line

injectCSS('.case2 { border: 3px solid #00f; } ');

这段文本的翻译如下:

这个函数的来源。您可以从Github存储库下载。或者在这里查看更多使用示例

我更喜欢在RequireJS中使用它,但在没有AMD加载器的情况下,它也可以作为全局函数运行。


8
你使用多行注释的方法很有意思。我不确定这是否是一个“好”主意,但肯定是有趣的。 - Dan Hlavenka
1
我认为这是一种危险的误用。CSS 也允许多行注释,因此在从现有样式表中复制样式时要小心。 - djjeck

0
创建一个 style 标签,并将 CSS 样式作为字符串添加到 textContent 属性中。将其附加到文档头部,完成!
var styles = document.createElement("style");
styles.setAttribute("type", "text/css");
styles.textContent = `#app{background-color:lightblue;}`;
document.head.appendChild(styles);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接