我们都知道,在HTML中引入CSS的方式有<link>标签和<style>标签两种,下面结合webpack来实现以下功能:

  1. CSS通过link标签引入;
  2. CSS放在style标签里;
  3. 动态加载和卸载CSS
  4. 页面加载CSS前的transform。

安转需要用到的loader:

yarn add css-loader style-loader file-loader -D
1

通过link标签引用css文件,这需要借助file-loader来将css处理为文件。具体配置如下:

const path = require('path');

module.exports = {
    mode: 'none',
    entry:  './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'), // 打包文件的输出目录
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    {
                        loader: 'style-loader/url'
                    },
                    {
                        loader: 'file-loader'
                    }
                ]
            }
        ]
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

入口文件index.js

const isClick = false;
// 点击页面上的按钮后,页面会引入相应的样式
document.querySelector('#btn').addEventListener('click', () => {
    if (!isClick) {
        import('./style/index.css');
    }
});
1
2
3
4
5
6
7

点击加载前:

点击加载后:

从上图可以看出,对应的css被插入了相应的link标签中。

CSS放在<style>标签里

通常来说,css放在style标签里可以减少网络请求次数,缩短响应时间。需要注意的是,在老式IE浏览器中,对style标签的数量是有要求的。具体配置如下:

const path = require('path');

module.exports = {
    mode: 'none',
    entry:  './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'), // 打包文件的输出目录
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    {
                        loader: 'style-loader',
                        options: {
                            singleton: true // 处理为单个style标签
                        }
                    },
                    {
                        loader: 'css-loader'
                    }
                ]
            }
        ]
    }
};
1
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

点击加载前:

点击加载后:

动态卸载和加载CSS

style-loadercss对象提供了use()和unuse()两个方法,借助这两个方法,可以方便快捷地加载和卸载css样式。具体配置如下:

const path = require('path');

module.exports = {
    mode: 'none',
    entry:  './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'), // 打包文件的输出目录
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    {
                        loader: 'style-loader/useable',
                    },
                    {
                        loader: 'css-loader'
                    }
                ]
            }
        ]
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

入口文件:

import style from './style/index.css';
let flag = false;
setInterval(() => {
    // use和unuse是style上的方法
    if (flag) {
        style.unuse();
    }
    else {
        style.use();
    }
    flag = !flag;
}, 1000);
1
2
3
4
5
6
7
8
9
10
11
12

样式不会添加在import/require()上,而是在调用use/ref时添加,在调用unuse/unref时删除。

页面加载css前的transform

对于csstransform,简单来说:在加载css样式前,可以更改css。这样,方便开发者根据业务需要,对css进行相关处理。

需要对style-loader增加options.transform属性,值为指定的js文件,具体的webpack.config.js配置如下:

const path = require('path');

module.exports = {
    mode: 'none',
    entry:  './src/index.js',
    output: {
        publicPath: __dirname + '/dist/', // js引用路径或者CDN地址
        path: path.resolve(__dirname, 'dist'), // 打包文件的输出目录
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    {
                        loader: 'style-loader',
                        options: {
                            transform: './src/transform.js'
                        }
                    },
                    {
                        loader: 'css-loader'
                    }
                ]
            }
        ]
    }
};
1
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

transform.js:

module.exports = function (css) { // 传入的参数是css字符串本身
    console.log(css);
    const transformed = css.replace('yellow', 'green')
    // 如果屏幕宽度小于800,则替换背景颜色
    return window.innerWidth < 800 ? transformed : css;
}
1
2
3
4
5
6

在index.js中引入css文件即可:

import style from './style/index.css';
1

打开控制台,如下图所示,当屏幕宽度小于800时候,css中的yellow已经被替换为了green

需要注意的是:transform是在css引入前根据需要修改,所以之后是不会改变的。所以上方代码不是响应式,当把屏幕宽度拉长到大于800时候,依旧是绿色。重新刷新页面,才会是黄色。

css打包处理

yarn add style-loader css-loader -D
1
// index.js
import avatar from './avatar.jpg';
import './index.css';

const img = new Image();
img.src = avatar;
img.classList.add('avatar');

document.body.appendChild(img);
1
2
3
4
5
6
7
8
9
// index.css
@import './avatar.css';
1
2
// avatar.css
.avatar {
    width: 100px;
    height: 100px;
    border-radius: 5px;
}
1
2
3
4
5
6

在上述代码中,在index.js中引入了index.css,在index.css中通过@import语法引入了avatar.css。页面展示效果如下:

从上面例子中,可以知道:

  • css-loader:用于加载.css文件,并且转换成commonjs对象。会分析多个css文件之间的关系(@import语法等),把多个css文件合并成一段css代码。
  • style-loader:将合并后的css通过style标签的形式插入到html文件head标签中。

less打包处理

yarn add less less-loader style-loader css-loader -D
# 样式添加前缀
yarn add postcss-loader autoprefixer -D
1
2
3
// index.js
import avatar from './avatar.jpg';
import './index.less';

const img = new Image();
img.src = avatar;
img.classList.add('avatar');

document.body.appendChild(img);
1
2
3
4
5
6
7
8
9
// index.less
.avatar {
    width: 100px;
    height: 100px;
    border-radius: 5px;
    transform: translate(100px, 200px);
}
1
2
3
4
5
6
7
// postcss.config.js
module.exports = {
    plugins: [
        require('autoprefixer')
    ]
};
1
2
3
4
5
6
{
    test: /\.less$/,
    // less-loader会将less语法转化为css语法
    // css-loader会分析多个css文件之间的关系(@import语法等),把多个css文件合并成一段css代码
    // style-loader将合并后的css插入到html文件的style标签中
    use: [
        'style-loader',
        'css-loader',
        'less-loader',
        'postcss-loader'
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12

需要注意:autoprefixer是css的后置处理器,是在css文件打包完成后再对css文件代码添加浏览器前缀。

autoprefixer对@import方式引用css文件无效的解决方案

具体解决方案是配置importLoaders,具体参考

// index.less
@import './avatar.less';
1
2
// avatar.less
.avatar {
    width: 100px;
    height: 100px;
    border-radius: 5px;
    transform: translate(100px, 200px);
}
1
2
3
4
5
6
7

我们在index.js中引入了index.less,又在index.less中通过@import语法引入了avatar.less。

需要注意:在index.js中引入了index.less,index.less文件的loader处理顺序为:less-loader->postcss-loader->css-loader->style-loader。

而对于avatar.less,其loader处理顺序为:css-loader->style-loader。默认并不会被less-loader和postcss-loader处理。这是因为importLoaders默认值为0。我们在index.js中引入了index.less,又在index.less中引入了avatar.less(@import './avatar.less';)。如果不配置importLoaders: 2,那么avatar.less将不会被postcss-loader和less-loader处理。如果css-loader后面只有postcss-loader,则设置importLoaders: 1

{
    test: /\.less$/,
    // less-loader会将less语法转化为css语法
    // css-loader会分析多个css文件之间的关系(@import语法等),把多个css文件合并成一段css代码
    // style-loader将合并后的css插入到html文件的style标签中
    use: [
    'style-loader',
    {
        loader: 'css-loader',
        options: {
            importLoaders: 2
        }
    },
    'postcss-loader',
    'less-loader'
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

此外还需要注意:postcss-loader必须在css-loader之后,less-loader之前,否则前缀也添加不成功。

CSS Module(CSS模块化)

CSS模块化主要用来解决不同文件样式的冲突问题,避免样式之间的相互影响。

// createAvatar.js
import avatar from './avatar.jpg';

function createAvatar() {
    const img = new Image();
    img.src = avatar;
    // 给image标签添加一个avatar的类
    img.classList.add('avatar');
    document.body.appendChild(img);
}

export default createAvatar;
1
2
3
4
5
6
7
8
9
10
11
12
// index.js
import avatar from './avatar.jpg';
import './index.less';
import createAvatar from './createAvatar';

createAvatar(); // 每运行一次,就会在页面中添加一张图片
const img = new Image();
img.src = avatar;
img.classList.add('avatar');

document.body.appendChild(img);
1
2
3
4
5
6
7
8
9
10
11

例如上述代码:我们在index.js中创建了一个Image对象并给其添加了一个avatar的类。与此同时,在createAvatar.js中也创建了一个Image对象并给其添加了一个avatar的类。在index.less中给avatar的类添加了一些样式,这个样式是全局的,会同时影响到index.js和createAvatar.js中两个图片的样式。结果如下:

但是上图中的结果有时候并不是我们所期望的,如何才能让avatar的类的样式称为一个局部样式呢?具体配置如下:

{
    loader: 'css-loader',
    options: {
        importLoaders: 2,
        modules: true // 开启css模块化打包,使得引入的样式文件仅仅作用于当前的js文件,不会对其他文件中同名类造成影响
    }
}
1
2
3
4
5
6
7
import avatar from './avatar.jpg';
import style from './index.less'; // index.less引入方式改变
import createAvatar from './createAvatar';

createAvatar(); // 每运行一次,就会在页面中添加一张图片
const img = new Image();
img.src = avatar;
img.classList.add(style.avatar); // 添加类的方式发生改变

document.body.appendChild(img);
1
2
3
4
5
6
7
8
9
10

重新打包,结果如下:

从上图结果可以看出:createAvatar.js中的图片样式并没有受到影响,符合预期。如果想让其也拥有对应的样式,也需要在createAvatar.js进行如下配置:

import avatar from './avatar.jpg';
import style from './index.less'; // 引入index.less

function createAvatar() {
    const img = new Image();
    img.src = avatar;
    // 给image标签添加一个avatar的类
    img.classList.add(style.avatar); // 基于style添加avatar类
    document.body.appendChild(img);
}

export default createAvatar;
1
2
3
4
5
6
7
8
9
10
11
12

sass打包处理

配置类似于less,具体参考

样式公共变量的提取

yarn add style-resources-loader -D
1

mini-css-extract-plugin提取样式文件

style-loaderMiniCssExtractPlugin.loader两者作用是冲突的,style-loader用于将样式代码插入到<head>标签中,MiniCssExtractPlugin.loader用于将样式文件提取到单独的css文件中。

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

{
    test: /\.less$/,
    use: [
        // 'style-loader',
        MiniCssExtractPlugin.loader,
        'css-loader',
        'less-loader'
    ]
}
plugins: [
    new MiniCssExtractPlugin({
        filename: '[name]_[contenthash:8].css' // 设置css文件指纹
    })
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

移动端css适配 px自动转换成rem

说到响应式布局,我们一般想到的可能是CSS媒体查询,虽然媒体查询可以实现响应式布局,但是该方案的缺陷是:需要针对不同的屏幕写多套适配样式代码。

yarn add px2rem-loader -D
// lib-flexible作用是根据页面的分辨率动态计算根元素的font-size
yarn add lib-flexible
1
2
3

W3C对rem的定义是:font-size of the root element。 rem是相对单位,px是绝对单位。 原理:页面渲染时计算根元素的font-size值,可以使用手淘的lib-flexible库。

{
    test: /\.less$/,
    use: [
        // 'style-loader',
        MiniCssExtractPlugin.loader,
        'css-loader',
        'less-loader',
        'postcss-loader',
        {
            loader: 'px2rem-loader',
            options: {
                // 适用于750的设计稿
                remUnit: 75, // 1rem等于75px
                remPrecision: 8 // px转为rem时小数点的位数
            }
        }
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

引入lib-flexible

我们可以在<head>标签中直接将lib-flexible的代码通过<script>标签的形式引入。引入之后,页面根元素的font-size值会根据当前页面的分辨率进行改变。