js 动态插入css
# 1. 外部 css 文件已经存在
# 1.1. 动态引入 css 文件
在 head 标签内在创建一个 link 标签来引入 css。这个前提是外部的 css 已经存在
function loadStyle(url) {
var link = document.createElement('link')
link.type = 'text/css'
link.rel = 'stylesheet'
link.href = url
var head = document.getElementsByTagName('head')[0]
head.appendChild(link)
}
// 使用方法引入css
loadStyle('test.css')
2
3
4
5
6
7
8
9
10
11
# 2. css 文件 不存在,通过 js 创建后引入
# 2.1 最基础的内联方法
const el = document.createElement('div')
el.style.backgroundColor = 'red'
// 或者
el.style.cssText = 'background-color: red'
// 或者
el.setAttribute('style', 'background-color: red')
2
3
4
5
6
7
直接在.style 对象上设置样式属性将需要使用驼峰式命名
作为属性键,而不是使用短横线命名
如果需要设置更多的内联样式属性,则可以通过设置 .style.cssText
属性,以更加高效的方式进行设置 (注:如果使用 style.cssText,会把原来的 class 类样式全部覆盖)
2.1.1 使用 Object.assign()一次性设置
// ...
Object.assign(el.style, {
backgroundColor: 'red',
margin: '25px'
})
2
3
4
5
# 2.2 使用 style 标签动态引入
小提示
IE 中<link>
标签被视为一个特殊标签,不能访问其子元素,所以要使用 stylesheet.cssText
,使用 try catch 语句捕获 IE 抛出的错误
function loadCssCode(code) {
var style = document.createElement('style')
style.type = 'text/css'
style.rel = 'stylesheet'
try {
//for Chrome Firefox Opera Safari
style.appendChild(document.createTextNode(code))
} catch (ex) {
//for IE
style.styleSheet.cssText = code
}
var head = document.getElementsByTagName('head')[0]
head.appendChild(style)
}
loadCssCode('body{background-color:#f00}')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 3. 使用 Document.styleSheets
隆重介绍 StyleSheetList
接口,该接口由 Document.styleSheets
属性实现
先看下 document.styleSheets
能给我们带来什么 (PS:截图下面有详细介绍)
属性 | 描述 |
---|---|
media | 获取当前样式作用的媒体。 |
disabled | 打开或禁用一张样式表。 |
href | 返回 CSSStyleSheet 对象连接的样式表地址。 |
title | 返回 CSSStyleSheet 对象的 title 值。 |
type | 返回 CSSStyleSheet 对象的 type 值,通常是 text/css。 |
parentStyleSheet | 返回包含了当前样式表的那张样式表。 |
ownerNode | 返回 CSSStyleSheet 对象所在的 DOM 节点,通常是<link> 或<style> 。 |
cssRules | 返回样式表中所有的规则。 |
ownerRule | 如果是通过@import 导入的,属性就是指向表示导入的规则的指针,否则值为 null。IE 不支持这个属性。 |
# 3.1 document.styleSheets 如何动态插入/修改样式?
CSSStyleSheet 对象方法:
方法 | 描述 |
---|---|
insertRule() | 在当前样式表的 cssRules 对象插入 CSS 规则。 |
deleteRule() | 在当前样式表删除 cssRules 对象的 CSS 规则。 |
不信?展开刚才我们打印的 document.styleSheets
随便一项的 CSSStyleSheet
# 3.2 使用 insertRule 插入一段样式
// 插入一段样式
const ruleIndex = document.styleSheets[0].insertRule('div {background-color: red}')
// 删除这段样式的规则
styleSheet.deleteRule(ruleIndex)
2
3
4
5
如果上面 2 句一起执行,是不起作用的,因为插入后又被删除了!
请记住
有些浏览器可能会阻止咱们从不同的来源(域)访问外部 CSSStyleSheet 的.cssRules 属性。
# 3.2 基于有趣的 api,实现一个自己的 css-in-js
创建一个函数,它传递一个简单的样式配置对象,生成一个新创建的 CSS 类的哈希名称供以后使用
# 3.2.1 先来添加一个样式表 <style>
为了避免重复创建 <style>
,我们可以用一个自己的标识,CSSInJS
来标记这是我们自己创建的节点
function addClass(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 styleTag = document.createElement('style')
document.head.appendChild(styleTag)
// style.sheet 和 document.styleSheets[0] 其实是一致的
// 所以第4行代码我们就可以拿到 CSSInJS 这个变量
styleSheet = styleTag.sheet
styleSheet.CSSInJS = true
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 3.2.2 接下来就是要生成随机 class 名,还有对于 css 的转换
- 毕竟是动态插入的 css,命名最好不要和之前的代码有冲突,最有效的方式就是生成一个随机的 css 名称
- 我们传入的 css 配置中,少不了
backgroung-image
等中间有-
的 css 属性,可是 js 的属性对-
不太友好,所以采用 JS 里面的 css 都使用驼峰的方式,转换部分我们单独写一个方法
// 生成随机名称
function createRandomName() {
const code = Math.random()
.toString(36)
.substring(7)
return `css-${code}`
}
// 转换css属性
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()
// 为数字单位添加'px'后缀
const value = `${style[key]}${typeof style[key] === 'number' ? 'px' : ''}`
return `${kebabCaseKey}:${value};`
})
return `{${keyValue.join('')}}`
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 3.2.3 最后就是把 css 插入样式表中
function createClassName(style) {
const className = createRandomName()
let styleSheet
styleSheet.insertRule(`.${className}${phraseStyle(style)}`)
return className
}
2
3
4
5
6
# 3.2.3 基于上面的一步步的思路,我们在结合 JS 的原型和原型链,对全部方法进行一个封装
使用原型链的知识,把所有的方法整合到 CssInJS
中去。并且在页面执行的时候,直接返回 CssInJS
的示例
在页面上,直接使用
var className = cssInJS.addClass({ background: 'red' })
// 这里可能有多个div,我们就选中第一个div添加class
document.getElementsByTagName('div')[0].classList.add(className)
2
3
一个小小的注意事项
因为这段 demo 毕竟是一段简易的 js,很多功能可能并不完善~
比如涉及到 css 权重的问题,如果某个 div 是根据多个 class 类名选中,然后设定的某个属性
而我们动态添加的 css 只有一个 class 名,可能添加了也不会生效,当然也有好的解决办法就是 addClass 的时候,传入指定类名等,具体的就看自己发挥了~
css 权重计算不清楚的可以看这里 👉 CSS 选择器权重计算
完整代码展示:
;(function() {
const CssInJS = function() {}
CssInJS.prototype.createRandomName = function() {
const code = Math.random()
.toString(36)
.substring(7)
return `css-${code}`
}
CssInJS.prototype.addClass = function(style) {
// 这里有点小不同,我们开始进行方法的互相调用了
const className = this.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 styleTag = document.createElement('style')
document.head.appendChild(styleTag)
styleSheet = styleTag.sheet
styleSheet.CSSInJS = true
}
styleSheet.insertRule(`.${className}${this.phraseStyle(style)}`)
return className
}
CssInJS.prototype.phraseStyle = function(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('')}}`
}
window.cssInJS = new CssInJS()
})(window)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 最后
各种方法各有千秋,甚至各有兼容性问题,还是得看需求来选实现方式,并没有哪一种方式可以吃遍所有的需求
至于为什么要用 JS 这样
变态
的操作 css?
一是为了性能,毕竟 css 做动画真的比 JS 控制流畅太多了
二是 css 的强大,动画已经不在话下,各种的控制比写一堆 JS 逻辑要快得多,而且是快准狠!