JavaScript can be said that the interaction of the king, as a scripting language with many Web Api further expanded its feature set, richer interactive interface operability. Examples of such API include WebGL API, Canvas API, DOM API, there is a group of lesser known CSS API.
Because JSX
and countless JS框架
have enabled by JS API DOM interaction with the idea really caught on, but the use of similar techniques in CSS does not seem a lot. Of course, there are like CSS-in-JS type of solution, but the most popular solution is based on translation (transpilation), without additional operation to generate CSS. This is certainly good for performance, since the use of CSS API may result in additional redrawn, which is DOM API
the same use. But this is not we want. If the company requires our day, it is necessary to manipulate DOM elements and CSS style class, but also the same as using HTML to use JS to create a complete style sheet, how to do?
Inline style before our in-depth knowledge of the complex, to come back to care about some of the basics. For example, you can modify it .style
to edit a given attribute HTMLElement
of the inline style.
const el = document.createElement('div')
el.style.backgroundColor = 'red'
// 或者
el.style.cssText = ' padding: 0px; max-width: 1000%; box-sizing: border-box !important; word-wrap: break-word !important; text-align: left; font-size: 14px; white-space: pre; display: flex; position: relative; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;">// 或者
el.setAttribute('style', ' padding: 0px; max-width: 100%; box-sizing: border-box; font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif; font-size: 17px; letter-spacing: 0.544px; color: rgb(33, 37, 41); line-height: 1.75em; word-wrap: break-word !important;">直接在
.style
对象上设置样式属性将需要使用驼峰式命名作为属性键,而不是使用短横线命名
。 如果咱们需要设置更多的内联样式属性,则可以通过设置.style.cssText
属性,以更加高效的方式进行设置 。请记住,给cssText设置后原先的css样式被清掉了,因此,要求咱们一次死一堆样式 。
如果这种设置内联样式过于繁琐,咱们还可以考虑将
.style
与Object.assign()
一起使用,以一次设置多个样式属性。
// ...
Object.assign(el.style, {
backgroundColor: "red",
margin: "25px"
})
这些“基本知识”比咱们想象的要多得多。.style
对象实现CSSStyleDeclaration
接口。这说明它带还有一些有趣的属性和方法,这包括刚刚使用的.cssText
,还包括.length
(设置属性的数量),以及.item()
、.getPropertyValue()
和.setPropertyValue()
之类的方法:
// ...
const propertiesCount = el.style.length
for(let i = 0; i < propertiesCount; i++) {
const name = el.style.item(i) // 'background-color'
const value = el.style.getPropertyValue(name) // 're'
const priority = el.style.getPropertyPriority(name) // 'important'
if(priority === 'important') {
el.style.removeProperty()
}
}
这里有个小窍门-在遍历过程中.item()
方法具有按索引访问形式的备用语法。
// ...
el.style.item(0) === el.style[0]; // true
CSS 类
接着,来看看更高级的结构——CSS类,它在检索和设置时具有字符串形式是.classname
。
// ...
el.className = "class-one class-two";
el.setAttribute("class", "class-one class-two");
设置类字符串的另一种方法是设置
class
属性(与检索相同)。 但是,就像使用.style.cssText
属性一样,设置.className
将要求咱们在字符串中包括给定元素的所有类,包括已更改和未更改的类。当然,可以使用一些简单的字符串操作来完成这项工作,还有一种就是使用较新的
.classList
属性,这个属性,IE9 不支持它,而 IE10 和 IE11 仅部分支持它。
classlist
属性实现了DOMTokenList
,有一大堆有用的方法。例如.add()
、.remove()
、.toggle()和.replace()
允许咱们更改当前的 CSS 类集合,而其他的,例如.item()
、.entries()
或.foreach()
则可以简化这个索引集合的遍历过程。
// ...
const classNames = ["class-one", "class-two", "class-three"];
classNames.forEach(className => {
if(!el.classList.contains(className)) {
el.classList.add(className);
}
});
Stylesheets
一直以来,Web Api 还有一个StyleSheetList
接口,该接口由document.styleSheets
属性实现。 document.styleSheets
只读属性,返回一个由 StyleSheet
对象组成的 StyleSheetList
,每个 StyleSheet
对象都是一个文档中链接或嵌入的样式表。
for(styleSheet of document.styleSheets){
console.log(styleSheet);
}
通过打印结果咱们可以知道,每次循环打印的是 CSSStyleSheet 对象,每个 CSSStyleSheet 对象由下列属性组成:
Attributes | description |
---|---|
media | Get the current role of the media style. |
disabled | Open or disable a style sheet. |
href | Return style table address CSSStyleSheet connected objects. |
title | Return value CSSStyleSheet title object. |
type | Return CSSStyleSheet object type value, usually text / css. |
parentStyleSheet | Returns the current style sheet that contains the piece of style sheets. |
ownerNode | Back CSSStyleSheet DOM node object is located, typically <link> or <style>. |
cssRules | Return all the rules of style sheets. |
ownerRule | If it is introduced through @import, Pointer to the attribute rule is introduced, null otherwise . IE does not support this property. |
CSS Style Sheet对象方法:
method | description |
---|---|
insertRule() | In the current stylesheet cssRules object into a CSS rule. |
deleteRule() | In the current style sheet object to delete cssRules CSS rules. |
有了
StyleSheetList
的全部内容,咱们来CSS StyleSheet
本身。 在这里就有点意思了,CSS StyleSheet
扩展了StyleSheet
接口,并且只有这种只读属性,如.ownerNode
,.href
,.title
或.type
,它们大多直接从声明给定样式表的地方获取。回想一下加载外部CSS文件的标准HTML代码,咱们就会明白这句话是啥意思:
<head>
<link rel="stylesheet" type="text/css" href="style.css" title="Styles">
</head>
现在,咱们知道HTML文档可以包含多个样式表,所有这些样式表都可以包含不同的规则,甚至可以包含更多的样式表(当使用@import
时)。CSSStyleSheet
有两个方法:、.insertrule()
和.deleterule()
来增加和删除 Css 规则。
// ...
const ruleIndex = styleSheet.insertRule("div {
styleSheet.deleteRule(ruleIndex);
.insertRule(rule,index)
:此函数可以在cssRules
规则集合中插入一个指定的规则,参数rule
是标示规则的字符串,参数index
是值规则字符串插入的位置。
deleteRule(index)
:此函数可以删除指定索引的规规则,参数index
规定规则的索引。
CSSStyleSheet
也有自己的两个属性:.ownerRule
和.cssRule
。虽然.ownerRule
与@import
相关,但比较有趣的是.cssRules
。简单地说,它是CSSRuleList
的CSSRule
,可以使用前面提到的.insertrule()
和.deleterule()
方法修改它。请记住,有些浏览器可能会阻止咱们从不同的来源(域)访问外部CSSSty
leSheet的.cssRules
属性。那么什么是
CSSRuleList
?
CSSRuleList
是一个数组对象包含着一个有序的CSSRule
对象的集合。每一个CSSRule
可以通过rules.item(index)
的形式访问, 或者rules[index]
。 这里的rules
是一个实现了CSSRuleList
接口的对象,index
是一个基于0
开始的,顺序与CSS
样式表中的顺序是一致的。样式对象的个数是通过rules.length
表达。对于CSSStyleRule对象:
每一个样式表
CSSStyleSheet
对象可以包含若干CSSStyleRule
对象,也就是css样式规则,如下:
<style type="text/css">
h1{color:red}
div{color:green}
</style>
在上面的代码中
style
标签是一个CSSStyleSheet
样式表对象,这个样式表对象包含两个CSSStyleRule
对象,也就是两个css样式规则。
CSSStyleRule
对象具有下列属性:1.
type
:返回0-6
的数字,表示规则的类型,类型列表如下:0:CSSRule.UNKNOWN_RULE。
1:CSSRule.STYLE_RULE (定义一个CSSStyleRule对象)。
2:CSSRule.CHARSET_RULE (定义一个CSSCharsetRule对象,用于设定当前样式表的字符集,默认与当前网页相同)。
3:CSSRule.IMPORT_RULE (定义一个CSSImportRule对象,就是用@import引入其他的样式表)
4:CSSRule.MEDIA_RULE (定义一个CSSMediaRule对象,用于设定此样式是用于显示器,打印机还是投影机等等)。
5:CSSRule.FONT_FACE_RULE (定义一个CSSFontFaceRule对象,CSS3的@font-face)。
6:CSSRule.PAGE_RULE (定义一个CSSPageRule对象)。
2.
cssText
:返回一个字符串,表示的是当前规则的内容,例如:
div{color:green}
3.
parentStyleSheet
:返回所在的CSSStyleRule
对象。4.
parentRule
:如果规则位于另一规则中,该属性引用另一个CSSRule对象。5.
selectorText
:返回此规则的选择器,如上面的div就是选择器。6.
style
:返回一个CSSStyleDeclaration
对象。
// ...
const ruleIndex = styleSheet.insertRule("div {
const rule = styleSheet.cssRules.item(ruleIndex);
rule.selectorText; // "div"
rule.style.backgroundColor; // "red"
实现
现在,咱们对 CSS 相关的 JS Api有了足够的了解,可以创建咱们自己的、小型的、基于运行时的
CSS-in-JS
实现。咱们的想法是创建一个函数,它传递一个简单的样式配置对象,生成一个新创建的CSS类的哈希名称供以后使用。实现流程很简单,咱们需要一个能够访问某种样式表的函数,并且只需使用
.insertrule()
方法和样式配置就可以运行了。先从样式表部分开始:
function createClassName(style) {
// ...
let styleSheet;
for (let i = 0; i < document.styleSheets.length; i++) {
if (document.styleSheets[i].CSSInJS) {
styleSheet = document.styleSheets[i];
break;
}
}
if (!styleSheet) {
const style = document.createElement("style");
document.head.appendChild(style);
styleSheet = style.sheet;
styleSheet.CSSInJS = true;
}
// ...
}
如果你使用的是ESM或任何其他类型的JS模块系统,则可以在函数外部安全地创建样式表实例,而不必担心其他人对其进行访问。 但是,为了演示例,咱们将
stylesheet
上的.CSSInJS
属性设置为标志的形式,通过标志来判断是否要使用它。现在,如果如果还需要创建一个新的样式表怎么办? 最好的选择是创建一个新的
<style/>
标记,并将其附加到HTML文档的<head/>
上。 这会自动将新样式表添加到document.styleSheets
列表,并允许咱们通过<style/>
标记的.sheet
属性对其进行访问,是不是很机智?
function createRandomName() {
const code = Math.random().toString(36).substring(7);
return `css-${code}`;
}
function phraseStyle(style) {
const keys = Object.keys(style);
const keyValue = keys.map(key => {
const kebabCaseKey =
key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
const value =
`${style[key]}${typeof style[key] === "number" ? "px" : ""}`;
return `${kebabCaseKey}:${value};`;
});
return `{${keyValue.join("")}}`;
}
除了上面的小窍门之外。 自然,咱们首先需要一种为CSS类生成新的随机名称的方法。
然后,将样式对象正确地表达为可行的CSS字符串的形式。 这包括驼峰命名和短横线全名之间的转换,以及可选的像素单位(px)转换的处理。
function createClassName(style) {
const className = createRandomName();
let styleSheet;
// ...
styleSheet.insertRule(`.${className}${phraseStyle(style)}`);
return className;
}
完整代码如下:
HTML
<div id="el"></div>
JS
function createRandomName() {
const code = Math.random().toString(36).substring(7);
return `css-${code}`;
}
function phraseStyle(style) {
const keys = Object.keys(style);
const keyValue = keys.map(key => {
const kebabCaseKey = key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
const value = `${style[key]}${typeof style[key] === "number" ? "px" : ""}`;
return `${kebabCaseKey}:${value};`;
});
return `{${keyValue.join("")}}`;
}
function createClassName(style) {
const className = createRandomName();
let styleSheet;
for (let i = 0; i < document.styleSheets.length; i++) {
if (document.styleSheets[i].CSSInJS) {
styleSheet = document.styleSheets[i];
break;
}
}
if (!styleSheet) {
const style = document.createElement("style");
document.head.appendChild(style);
styleSheet = style.sheet;
styleSheet.CSSInJS = true;
}
styleSheet.insertRule(`.${className}${phraseStyle(style)}`);
return className;
}
const el = document.getElementById("el");
const redRect = createClassName({
width: 100,
height: 100,
backgroundColor: "red"
});
el.classList.add(redRect);
运行效果:
总结
正如本文咱们所看到的,使用 JS 操作CSS 是一件非常有趣的事,咱们可以挖掘很多好用的 API,上面的例子只是冰山一角,在CSS API(或者更确切地说是API)中还有更多方法,它们正等着被揭开神秘面纱。