Author: areknawo

Translator: Front-end wisdom

Source: css-tricks.com

The more you know, the more you don’t know

Like it and see. Make it a habit


GitHub: github.com/qq449245884… Has included more categories of previous articles, as well as a lot of my documentation and tutorial material. Welcome Star and Perfect, you can refer to the examination points for review in the interview, I hope we can have something together.

JavaScript can be said to be the king of interaction, as a scripting language with many Web apis to further expand its feature set, richer interface interaction operability. Examples of such apis include the WebGL API, Canvas API, DOM API, and a lesser-known set of CSS apis.

The idea of interacting with the DOM through JS apis has really caught on thanks to JSX and countless JS frameworks, but there doesn’t seem to be much use of similar techniques in CSS. Of course, there are solutions like CSS-in-JS, but the most popular solution is based on transpilation, which generates CSS without extra running. This is certainly good for performance, as the use of the CSS API can result in additional redraw, just like the use of the DOM API. But that’s not what we want. What if a company asks us to manipulate the styles of DOM elements and CSS classes, as well as create a complete style sheet using JS just like HTML?

Inline style

Before we dive into the complexities, let’s get back to the basics. For example, we can modify the inline style of a given HTMLElement by modifying its.style property.

Const el = document.createElement('div') el.style.backgroundColor = 'red' // or el.style.cssText = 'background-color: // el.setattribute ('style', 'background-color: red')Copy the code

Setting style properties directly on a.style object will require camelback naming as the property key, rather than dash naming. If we need to set more inline style properties, we can do so in a more efficient way by setting the.style.csstext property.

Keep in mind that the original CSS styles were removed after the cssText Settings were set. Therefore, we are required to do a bunch of styles at a time.

If this is too cumbersome to set inline styles, we can also consider using.style with object.assign () to set multiple style attributes at once.

// ...
Object.assign(el.style, {
    backgroundColor: "red",
    margin: "25px"
})
Copy the code

These “basics” are much more than we imagine. Style objects implement the CSSStyleDeclaration interface. This shows that it also has some interesting properties and methods, including the.csstext we just used, but also.length (which sets the number of properties), and methods like.item(),.getPropertyValue(), and.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()
    }
}
Copy the code

Here’s a trick – the item() method has an alternate syntax for accessing by index during traversal.

// ...
el.style.item(0) === el.style[0]; // true
Copy the code

The CSS class

Next, take a look at the more advanced structure, the CSS class, which has the string form.className when retrieved and set.

// ...
el.className = "class-one class-two";
el.setAttribute("class", "class-one class-two");
Copy the code

Another way to set a class string is to set the class attribute (same as retrieval). However, just as with the.style.cssText attribute, setting.className will require us to include all the classes for the given element in the string, both changed and unchanged.

Of course, you can do this with some simple string manipulation, as well as the newer.classlist property, which is not supported by IE9 and only partially supported by IE10 and IE11.

The classList property implements DOMTokenList, which has a bunch of useful methods. Examples such as.add(),.remove(),.toggle() and.replace() allow us to change the current CSS class set, while others such as.item(),.entries() or.foreach() simplify traversal of the index set.

// ...
const classNames = ["class-one", "class-two", "class-three"];
classNames.forEach(className => {
    if(!el.classList.contains(className)) {
        el.classList.add(className);
    }
});
Copy the code

Stylesheets

Traditionally, the Web Api has also had a StyleSheetList interface implemented by the Document. styleSheets property. The document. StyleSheets read-only property returns a StyleSheetList of StyleSheet objects, each of which is a linked or embedded StyleSheet in a document.

for(styleSheet of document.styleSheets){
	console.log(styleSheet);
}
Copy the code

As we can see from the print results, each loop prints a CSSStyleSheet object, and each CSSStyleSheet object consists of the following properties:

attribute describe
media Gets the media on which the current style is applied.
disabled Open or disable a style sheet.
href Returns the stylesheet address of the CSSStyleSheet object connection.
title Returns the title value of the CSSStyleSheet object.
type Returns the type value of the CSSStyleSheet object, usually text/ CSS.
parentStyleSheet Returns the stylesheet that contains the current stylesheet.
ownerNode Returns the DOM node where the CSSStyleSheet object is located, usually<link>or<style>.
cssRules Returns all the rules in the stylesheet.
ownerRule If imported via @import, the property is a pointer to the rule representing the import, otherwise null. IE does not support this property.

CSSStyleSheet object method:

methods describe
insertRule() Inserts CSS rules into the cssRules object of the current stylesheet.
deleteRule() Delete the CSS rules of the cssRules object in the current stylesheet.

So with all the content of StyleSheetList, let’s go to CSSStyleSheet itself. It’s kind of interesting here. CSSStyleSheet extends the StyleSheet interface and only has read-only attributes like.ownerNode,.href,.title, or.type, which are mostly retrieved directly from where a given StyleSheet is declared. Recall the standard HTML code for loading external CSS files to see what this means:

<head>
<link rel="stylesheet" type="text/css" href="style.css" title="Styles">
</head>
Copy the code

Now, we know that AN HTML document can contain multiple stylesheets, all of which can contain different rules, and even more stylesheets (when using @import). CSSStyleSheet has two methods to add and delete Css rules:.insertrule() and.deleterule().

// ...
const ruleIndex = styleSheet.insertRule("div {background-color: red}");
styleSheet.deleteRule(ruleIndex);
Copy the code

.insertrule (rule,index): This function inserts a specified rule into the cssRules rule collection. The rule argument is the string that identifies the rule and the index argument is the position where the value rule string is inserted.

DeleteRule (index): This function can delete rules for a specified index. The index parameter specifies the index of the rule.

CSSStyleSheet also has two attributes of its own:.ownerRule and.cssRule. While.ownerRule is related to @import, the interesting thing is.cssRules. Simply put, it is a CSSRule of CSSRuleList, and you can modify it using the.insertrule() and.deleterule() methods mentioned earlier. Keep in mind that some browsers may prevent us from accessing the.cssRules property of the external CSSStyleSheet from different sources (domains).

So what is a CSSRuleList?

CSSRuleList is an array object containing an ordered set of CSSRuleList objects. Each CSSRule can be accessed as rules.item(index), or rules[index]. Rules is an object that implements the CSSRuleList interface, and index is an object that starts with 0, in the same order as in the CSS stylesheet. The number of style objects is expressed through rules.length.

For CSSStyleRule objects:

Each STYLESheet CSS Stylesheet object can contain a number of CSS Stylerule objects, or CSS style rules, as follows:

<style type="text/css">
  h1{color:red}
  div{color:green}
</style>
Copy the code

In the code above, the style tag is a CSS Stylesheet object, which contains two CSS Stylerule objects, namely, two CSS style rules.

The CSSStyleRule object has the following properties:

1.typeReturns the0 to 6Represents the type of the rule. The type list is as follows:

Zero: CSSRule UNKNOWN_RULE.

1: cssrule-style_rule (define a CSSStyleRule object)

2: cssrule-charset_rule (defines a CSSCharsetRule object that sets the character set for the current stylesheet, which is the same as the current web page by default).

3: cssrule-import_rule (Define a CSSImportRule object that uses @import to import other stylesheets)

4: cssrule-media_rule (defines a CSSMediaRule object that sets whether this style is used for displays, printers, projectors, etc.).

FONT_FACE_RULE (define a CSSFontFaceRule object, @font-face for CSS3).

6: cssrule.page_rule (define a CSSPageRule object).

2.cssText: returns a string representing the contents of the current rule, for example:

div{color:green}
Copy the code

3.parentStyleSheet: Returns whereCSSStyleRuleObject.

4.parentRule: This property references another CSSRule object if the rule is in another rule.

5.selectorText: returns a selector for this rule, such as the div above.

6.style: Returns aCSSStyleDeclarationObject.

// ...
const ruleIndex = styleSheet.insertRule("div {background-color: red}");
const rule = styleSheet.cssRules.item(ruleIndex);

rule.selectorText; // "div"
rule.style.backgroundColor; // "red"
Copy the code

implementation

Now that we know enough about csS-related JS apis, we can create our own small, runtime-based IMPLEMENTATION of CSS-in-JS. The idea is to create a function that passes a simple style configuration object and generates the hash name of a newly created CSS class for later use.

The implementation process is simple, we need a function that can access some style sheet and run it using the.insertrule() method and style configuration. Start with the stylesheet section:

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; } / /... }Copy the code

If you use ESM or any other type of JS module system, you can safely create stylesheet instances outside of functions without worrying about others accessing them. But, for example, let’s look at the The CSSInJS property is set to the form of a flag that determines whether to use it or not.

Now, what if if you also need to create a new style sheet? The best option is to create a new

tag and attach it to the of the HTML document. This will automatically add the new stylesheet to the document.styleSheets list and allow us to access it through the.sheet property of the

tag. Isn’t that clever?

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("")}}`; }Copy the code

Except for the tips above. Naturally, we first need a way to generate new random names for CSS classes. The style object is then correctly represented as a viable CSS string. This includes the conversion between the hump name and the dash full name, as well as the handling of the optional pixel unit (PX) conversion.

function createClassName(style) {
  const className = createRandomName();
  let styleSheet;
  // ...
  styleSheet.insertRule(`.${className}${phraseStyle(style)}`);
  return className;
}
Copy the code

The complete code is as follows:

HTML

<div id="el"></div>
Copy the code

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);Copy the code

Operation effect:

conclusion

As we’ve seen in this article, working with CSS with JS is a lot of fun, and there are many useful apis to explore. The examples above are just the tip of the iceberg. There are many more methods in CSS apis (or rather apis) that are just waiting to be unveiled.

Original text: css-tricks.com/an-introduc…

The possible bugs in editing can not be known in real time. In order to solve these bugs after the event, I spent a lot of time on log debugging. By the way, I recommend a good bug monitoring tool for youFundebug.


communication

This article is updated every week, you can search wechat “big move the world” for the first time to read and urge more (one or two earlier than the blog hey), this article GitHub github.com/qq449245884… It has been included and sorted out a lot of my documents. Welcome Star and perfect. You can refer to the examination points for review in the interview.