CSS样式冲突

87 阅读14分钟

概述

由于CSS 没有作用域的概念,不同组件中的 CSS 样式会全局生效,因此开发过程中有极大可能会遇到样式冲突的问题。尤其是多人协作的项目,如果没有统一的 CSS 样式命名规范,很容易出现同一类名导致的样式覆盖情况。因此开发过程中需要对不同组件的样式进行隔离,以避免协作开发时产生样式冲突。下面我们将循序渐进了解CSS样式和冲突

设置CSS样式的方式

设置CSS样式表有三种方式

  • 内联样式
  • 内部样式表
  • 外部样式表

内联样式:直接在HTML元素的style属性中设置样式。

<div style="color: red; background-color: yellow;">This is a div with inline styles.</div>

这种方式用的比较少,一般只用微调

内部样式:在HTML文档的部分使用标签定义样式。

<head>
  <style>
    .example {
      color: blue;
      background-color: white;
    }
  </style>
</head>
<body>
  <div class="example">This text uses embedded styles.</div>
</body>

外部样式:将样式定义在独立的CSS文件中,并在HTML文档中通过标签引入。styles.css:

.example {
  color: green;
  background-color: white;
}
<head>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <div class="example">This text uses external styles.</div>
</body>

实际开发中,一般采用这种方式,因为使用外部样式可以使 HTML 文件保持简洁,方便对样式进行复用和维护

这三种方式存在样式冲突的问题吗? 内联样式(Inline Styles)具有最高的优先级,因此一般不会出现样式冲突的问题,因为它们会覆盖其他样式来源(如内部样式表或外部样式表)。

内部样式表和外部样式表可能引发样式冲突,接下来我们看看这三种样式存在的样式冲突问题

内部样式冲突

1. 多个内部样式块

如果一个HTML页面包含多个标签,并且这些标签中定义了相同的选择器和属性,后定义的样式会覆盖先定义的样式。

<head>
  <style>
    .example {
      color: blue;
    }
  </style>
  <style>
    .example {
      color: red;
    }
  </style>
</head>
<body>
  <div class="example">This text will be red.</div>
</body>

在上述例子中,.example类的颜色最终会是红色,因为后面的标签覆盖了前面的定义。

2. 与外部样式表冲突

如果一个页面同时包含内部样式和外部样式表,且两者定义了相同的选择器和属性,后加载的样式会覆盖前加载的样式。

<head>
  <link rel="stylesheet" href="styles.css">
  <style>
    .example {
      color: green;
    }
  </style>
</head>
<body>
  <div class="example">This text will be green.</div>
</body>

在上述例子中,如果styles.css中也定义了.example类的颜色属性,内部样式中的定义会覆盖外部样式表中的定义,因为内部样式在外部样式表之后被加载。

3. 与内联样式冲突

内联样式的优先级高于内部样式和外部样式表,因此内联样式会覆盖内部样式和外部样式表中的定义。

<head>
  <style>
    .example {
      color: yellow;
    }
  </style>
</head>
<body>
  <div class="example" style="color: purple;">This text will be purple.</div>
</body>

在上述例子中,内联样式将覆盖内部样式中的定义,最终颜色会是紫色。

外部样式冲突

1. 多个外部样式表之间的冲突

当一个HTML页面引入多个外部样式表,如果这些样式表定义了相同的选择器和属性,后加载的样式表会覆盖先加载的样式表。

<head>
  <link rel="stylesheet" href="styles1.css">
  <link rel="stylesheet" href="styles2.css">
</head>
<body>
  <div class="example">This text will be styled by styles2.css.</div>
</body>

如果styles1.css和styles2.css中都有对.example类的定义,则styles2.css中的定义会覆盖styles1.css中的定义。

2. 与内部样式和内联样式的冲突

内联样式和内部样式的优先级高于外部样式表,因此这些样式会覆盖外部样式表中的定义。

<head>
  <link rel="stylesheet" href="styles.css">
  <style>
    .example {
      color: green;
    }
  </style>
</head>
<body>
  <div class="example" style="color: red;">This text will be red.</div>
</body>

在上述例子中,内联样式中的color: red;会覆盖内部样式和外部样式表中的定义。

3. 与第三方库的冲突

引入第三方库时,这些库可能定义了一些通用的选择器class名,与项目自身的样式发生冲突。

<head>
  <link rel="stylesheet" href="bootstrap.css">
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <div class="btn">This button might be styled unexpectedly.</div>
</body>

在上述例子中,bootstrap.css中的.btn类可能会与styles.css中的.btn类发生冲突。

看了这么多样式冲突的情况,那我们怎么解决样式冲突,这里分为传统前端开发和现代前端开发,传统前端就是不用vue和react等前端框架

解决样式冲突的方法

1. 使用命名空间或前缀

为项目中的类名添加命名空间或前缀,避免与第三方库的类名冲突。

/* styles.css */
.myproject-btn {
  background-color: blue;
  color: white;
}

2. 提升选择器的优先级

通过使用更具体的选择器或增加选择器的层级,提升优先级。

/* styles.css */
body .example {
  color: blue;
}

BEM样式命名规范

BEM 是 Yandex 公司提出的一套CSS 样式命名规范,用于编写可重用且易于理解的CSS代码。BEM的命名规范有助于提高代码的可读性和维护性。下面是BEM的基本概念和一些示例。

BEM 分别指代 Block(块)、Element(元素)、Modifier(修饰符),BEM 规定 CSS 样式名称由这三部分内容组成。

  • Block,有意义的独立模块。
  • Element,Element 是 Block 的子元素,语义上与 Block 相关联,但本身没有独立意义,这里无独立意义指的是 Element 不会脱离 Block 单独使用,即非通用样式。
  • Modifier,修饰器,通常用于表示 Block 或 Element 的外观和行为变化。

BEM 三部分内容有以下几种组合方式:


.Block__Element {}
.Block--Modifier {}
.Block__Element--Modifier {}

Block 和 Element 之间用双下划线__相连接,Block 或 Element 与 Modifier 之间用双中划线--连接。

表单示例

假设我们有一个表单组件,包含输入字段和提交按钮。

<form class="form">
  <div class="form__group">
    <label class="form__label" for="email">Email</label>
    <input class="form__input" type="email" id="email">
  </div>
  <button class="form__button  form__button--submit">Submit</button>
</form>

CSS代码:

/* Block */
.form {
  max-width: 400px;
  margin: 0 auto;
}

/* Element */
.form__group {
  margin-bottom: 15px;
}

.form__label {
  display: block;
  margin-bottom: 5px;
}

.form__input {
  width: 100%;
  padding: 8px;
  box-sizing: border-box;
}

.form__button {
  padding: 10px 20px;
  border: none;
  cursor: pointer;
}

/* Modifier */
.form__button--submit {
    background-color: green;
    color: white;
}

.form__button--cancel {
    background-color: gray;
    color: white;
}

BEM是一种有效的CSS命名规范,通过将CSS类名分解为块、元素和修饰符,使得代码更加结构化和易于维护。在实际开发中,BEM的使用可以显著提高代码的可读性和复用性,从而提高开发效率。但是 BEM 类名写起来繁杂冗长,不够优雅。

vue和react的样式冲突

Vue和React应用会将代码加载到一个HTML文件中,通常是index.html。所以这两的CSS文件都是全局的,只要CSS文件中存在同名的选择器就会产生样式冲突。

vue应用样式冲突

vue 框架本身内置 scoped 属性实现组件间样式隔离,当组件 style 标签加上 scoped 属性后,表示样式仅在当前组件生效。vue是怎么做到加上scope属性就能使样式只在此组件生效的,我们看看我们用cli创建的hello-world项目打包前后的css文件

未打包前:

<style scoped>
h3 {
  margin: 40px 0 0;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

打包后

h3[data-v-b9167eee] {
  margin:40px 0 0
}
ul[data-v-b9167eee] {
  list-style-type:none;
  padding:0
}
li[data-v-b9167eee] {
  display:inline-block;
  margin:0 10px
}
a[data-v-b9167eee] {
  color:#42b983
}
#app {
  font-family:Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  text-align:center;
  color:#2c3e50;
  margin-top:60px
}

可以看出加上scope属性后正对每个css选择器都加上了唯一标识,使选择器全局唯一了

我们再用一个简单的例子来说明vue的scope的作用,建一个简单的vue应用,就两个界面:

主页面:MainPage

<template>
  <div className="main_container">
    <div className="title" @click="clickA">奇舞团</div>
    <div className="sub_title">360最大的前端开发组织</div>
  </div>
</template>

<script>

  export default {
    name: 'MainPage',
    methods: {
      clickA() {
        window.open(`#/about`)
      },
    },
  }
</script>

<style scoped>
  .main_container {
    width: 100vw;
    height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    background-color: blue;
  }

  .title {
    font-size: 30px;
    font-weight: 500;
    color: white;
    cursor: pointer;
  }

  .sub_title {
    font-size: 10px;
    font-weight: 300;
    color: white;
  }
</style>

关于页:AboutPage

<template>
  <div className="main_container">
    <div className="title">欢迎来到奇舞团</div>
    <div className="sub_title">奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队
      Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。</div>
  </div>
</template>

<script>

  export default {
    name: 'AboutPage',
  }
</script>

<style scoped>
  .main_container {
    width: 100vw;
    height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    background-color: red;
  }


  .title {
    font-size: 30px;
    font-weight: 500;
    color: black;
    cursor: pointer;
  }

  .sub_title {
    text-align: center;
    width: 60%;
    font-size: 10px;
    line-height: 14px;
    font-weight: 300;
    color: yellow;
  }
</style>

下面是vue应用打包后的css文件:

#app {
  font-family:Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  text-align:center;
  color:#2c3e50
}
.main_container[data-v-3b8f6db2] {
  width:100vw;
  height:100vh;
  display:flex;
  flex-direction:column;
  justify-content:center;
  align-items:center;
  background-color:blue
}
.title[data-v-3b8f6db2] {
  font-size:30px;
  font-weight:500;
  color:#fff;
  cursor:pointer
}
.sub_title[data-v-3b8f6db2] {
  font-size:10px;
  font-weight:300;
  color:#fff
}
.main_container[data-v-08ca7bc2] {
  width:100vw;
  height:100vh;
  display:flex;
  flex-direction:column;
  justify-content:center;
  align-items:center;
  background-color:red
}
.title[data-v-08ca7bc2] {
  font-size:30px;
  font-weight:500;
  color:#000;
  cursor:pointer
}
.sub_title[data-v-08ca7bc2] {
  text-align:center;
  width:60%;
  font-size:10px;
  line-height:14px;
  font-weight:300;
  color:#ff0
}

可以看出上面的css文件是符合预期的,加上scope属性后对每个css选择器都加上了唯一标识,使选择器全局唯一了。

下面是去掉组件style标签的scope属性后的css文件

#app {
  font-family:Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  text-align:center;
  color:#2c3e50
}
.main_container {
  background-color:blue
}
.sub_title, .title {
  color:#fff
}
.main_container {
  width:100vw;
  height:100vh;
  display:flex;
  flex-direction:column;
  justify-content:center;
  align-items:center;
  background-color:red
}
.title {
  font-size:30px;
  font-weight:500;
  color:#000;
  cursor:pointer
}
.sub_title {
  text-align:center;
  width:60%;
  font-size:10px;
  line-height:14px;
  font-weight:300;
  color:#ff0
}

去掉scope后,选择器就不全局唯一了,会产生样式冲突,后添加的会覆盖前面添加的。导致主页的样式实际应用的是About页的,因为主页先加载,about页后加载,后加载的css会覆盖前面加载的。可以看出vue 框架本身内置 的scoped 属性可以实现组件中的样式的私有化,不对全局造成样式污染,表示当前style属性只属于当前模块。

另外从上面代码可以看出,scoped的作用就是会在css样式选择器上加上唯一性的标记【data-v-something】属性,即CSS带属性选择器,以此完成类似作用域的选择方式,从而达到样式私有化,不污染全局的作用。

react应用样式冲突

在React应用中,默认情况下,CSS是全局的。这意味着在一个CSS文件中定义的样式会应用到整个应用中的所有组件,可能导致样式冲突和意外的样式覆盖。

以下是一个react项目实例,当两组件存在同名class标签属性,后加载的会覆盖前面加载的

App.js

import { BrowserRouter, Routes, Route } from "react-router-dom";

import './App.css';

import  MainPage  from './main_page/main_page'
import  About  from './about/about'

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<MainPage/>} />
        <Route path="/about" element={<About/>} />
      </Routes>
    </BrowserRouter>
  );
}


export default App;

MainPage.jsx和css

import './main_page.css'; 

function MainPage() {

  function  clickA() {
    window.open(`/about`)
  }

  return <>
    <div className="main_container">
      <div className="title" onClick={clickA}>奇舞团</div>
      <div className="sub_title">360最大的前端开发组织</div>
    </div>
  </>
}

export default MainPage


--------------------


  .main_container {
  width: 100vw;
  height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  background-color: blue;
}

.title {
  font-size: 30px;
  font-weight: 500;
  color: white;
  cursor: pointer;
}

.sub_title {
  font-size: 10px;
  font-weight: 300;
  color: white;
}

About.jsx和css

import './about.css'; 

function About() {

  return <>
    <div className="main_container">
      <div className="title">欢迎来到奇舞团</div>
      <div className="sub_title">奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。</div>
    </div>
  </>
}

export default About

--------------


  .main_container {
  width: 100vw;
  height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  background-color: red;
}


.title {
  font-size: 30px;
  font-weight: 500;
  color: black;
  cursor: pointer;
}

.sub_title {
  text-align: center;
  width: 60%;
  font-size: 10px;
  line-height: 14px;
  font-weight: 300;
  color: yellow;
}

main页和about页的class选择器是一样的,但属性值不一样。但运行后的结果我们发现main页的样式被about页的样式覆盖了。

为什么会覆盖呢,我们知道,react的项目构建后,css一般会合成一个文件,我们看看这个文件里面是什么:

body {
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  font-family:-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
  margin:0
}
code {
  font-family:source-code-pro, Menlo, Monaco, Consolas, Courier New, monospace
}
.App {
  text-align:center
}
.App-logo {
  height:40vmin;
  pointer-events:none
}
@media (prefers-reduced-motion:no-preference) {
  .App-logo {
    animation:App-logo-spin 20s linear infinite
  }
}
.App-header {
  align-items:center;
  background-color:#282c34;
  color:#fff;
  display:flex;
  flex-direction:column;
  font-size:calc(10px + 2vmin);
  justify-content:center;
  min-height:100vh
}
.App-link {
  color:#61dafb
}
@keyframes App-logo-spin {
  0% {
    transform:rotate(0deg)
  }
  to {
    transform:rotate(1turn)
  }
}
.main_container {
  background-color:blue
}
.sub_title, .title {
  color:#fff
}
.main_container {
  align-items:center;
  background-color:red;
  display:flex;
  flex-direction:column;
  height:100vh;
  justify-content:center;
  width:100vw
}
.title {
  color:#000;
  cursor:pointer;
  font-size:30px;
  font-weight:500
}
.sub_title {
  color:#ff0;
  font-size:10px;
  font-weight:300
}

从上面文件可以看出,css文件中的选择器产生了冲突,后添加的覆盖了前面的,导致了样式冲突

为什么会出现样式覆盖呢,我们明明导入的是不同的样式文件,虽然样式文件中的class名一样。这是因为react打包时,默认将所有这些 CSS 文件会合并成一个CSS文件,在同一个CSS文件中,如果出现同名class,后添加的会覆盖前面的。

使用 CSS Modules

为了解决react开发中的样式冲突,我们可以使用CSS Modules,使用它可以让 CSS 作用于特定组件而不会污染全局样式。

CSS Module 将 CSS 作为独立文件导入,在组件内部使用时将 CSS 文件类名当做导出对象的属性进行引用,通过构建工具动态生成唯一的类名替换源文件引用的类名,相当于实现了 CSS 局部作用域。

使用CSS Modules的方式很简单,将css文件的后缀名改成xxx.module.css即可,其他事情就由webpack打包工具去做了,但jsx文件中class的设置需要改一下,比如MainPage.jsx

import styles from './main_page.module.css'; 

function MainPage() {

  function  clickA() {
    window.open(`/about`)
  }

  return <>
    <div className={styles.main_container}>
      <div className={styles.title} onClick={clickA}>奇舞团</div>
      <div className={styles.sub_title}>360最大的前端开发组织</div>
    </div>
  </>
}

export default MainPage

css文件

body {
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  font-family:-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
  margin:0
}
code {
  font-family:source-code-pro, Menlo, Monaco, Consolas, Courier New, monospace
}
.App {
  text-align:center
}
.App-logo {
  height:40vmin;
  pointer-events:none
}
@media (prefers-reduced-motion:no-preference) {
  .App-logo {
    animation:App-logo-spin 20s linear infinite
  }
}
.App-header {
  align-items:center;
  background-color:#282c34;
  color:#fff;
  display:flex;
  flex-direction:column;
  font-size:calc(10px + 2vmin);
  justify-content:center;
  min-height:100vh
}
.App-link {
  color:#61dafb
}
@keyframes App-logo-spin {
  0% {
    transform:rotate(0deg)
  }
  to {
    transform:rotate(1turn)
  }
}
.main_page_main_container__Mzly+ {
  align-items:center;
  background-color:blue;
  display:flex;
  flex-direction:column;
  height:100vh;
  justify-content:center;
  width:100vw
}
.main_page_title__zK8uK {
  color:#fff;
  cursor:pointer;
  font-size:30px;
  font-weight:500
}
.main_page_sub_title__xy5tw {
  color:#fff;
  font-size:10px;
  font-weight:300
}
.main_container {
  align-items:center;
  background-color:red;
  display:flex;
  flex-direction:column;
  height:100vh;
  justify-content:center;
  width:100vw
}
.title {
  color:#000;
  cursor:pointer;
  font-size:30px;
  font-weight:500
}
.sub_title {
  color:#ff0;
  font-size:10px;
  font-weight:300;
  line-height:14px;
  text-align:center;
  width:60%
}

从上面的css文件中可以看出,将css文件module化后,class属性名后面加了哈希值,这样每个class名就在就全局唯一了,去除了样式的全局性。

总结

当我们进行前端开发时,不管是以古老的前端三剑客模式开发,还是使用现代的前端框架Vue或者React,都有可能遇到样式冲突的问题,解决方案都是通过让 CSS 选择器名称的唯一性进而实现样式隔离。