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. 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')

::: tip
直接在.style 对象上设置样式属性将需要使用驼峰式命名作为属性键,而不是使用短横线命名
如果需要设置更多的内联样式属性,则可以通过设置 .style.cssText 属性,以更加高效的方式进行设置 (注:如果使用 style.cssText,会把原来的 class 类样式全部覆盖)
:::

2.1.1 使用 Object.assign()一次性设置

// ...
Object.assign(el.style, {
  backgroundColor: 'red',
  margin: '25px'
})

2.2 使用 style 标签动态引入

::: tip 小提示

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}')

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 句一起执行,是不起作用的,因为插入后又被删除了!

::: danger 请记住
有些浏览器可能会阻止咱们从不同的来源(域)访问外部 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
  }
}

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('')}}`
}

3.2.3 最后就是把 css 插入样式表中

function createClassName(style) {
  const className = createRandomName()
  let styleSheet
  styleSheet.insertRule(`.${className}${phraseStyle(style)}`)
  return className
}

3.2.3 基于上面的一步步的思路,我们在结合 JS 的原型和原型链,对全部方法进行一个封装

使用原型链的知识,把所有的方法整合到 CssInJS 中去。并且在页面执行的时候,直接返回 CssInJS 的示例

在页面上,直接使用

var className = cssInJS.addClass({ background: 'red' })
// 这里可能有多个div,我们就选中第一个div添加class
document.getElementsByTagName('div')[0].classList.add(className)

::: tip 一个小小的注意事项
因为这段 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)

最后

各种方法各有千秋,甚至各有兼容性问题,还是得看需求来选实现方式,并没有哪一种方式可以吃遍所有的需求

至于为什么要用 JS 这样变态的操作 css?
一是为了性能,毕竟 css 做动画真的比 JS 控制流畅太多了
二是 css 的强大,动画已经不在话下,各种的控制比写一堆 JS 逻辑要快得多,而且是快准狠!

新博客地址 ,欢迎参观~js 动态插入css

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值