背景
在扫码登录的需求中,就是在 商城和至造云的登录框上挂个 DOM 渲染用 Vue写的一些组件实现扫码,组件库选择了 ant design vue(以下简称antd),尴尬的是引入之后发现,商城和至造云自身是带一套全局样式的,而 antd又带了一套全局样式,导致 商城和至造云的部分样式被覆盖,如a 标签颜色被 antd 覆盖,布局出现问题。因为 Ant-Design 的理念是一套设计语言,所以 antd 会引入一套 fork 自 normalize.css 的浏览器默认样式重置库。直到今天官方没有进展。引入全局样式的这个文件是 style/core/base.less,就是这个 base.less 会对各种元素的默认样式一顿格式化。
如图:
解决思路
核心问题就是base.less
这个文件对全局样式的侵入。那这个文件可以不要吗?不行,antd 的组件样式都是建立在这个格式化后的样式上的,所以要在不影响全局样式的条件下。如果将 base.less 外面套一层 .ant-container
的方案,但一个显著的缺陷就是提高了 base.less
中样式的权重导致样式错位。但是限定 base.less
这个思路是没有错的,base.less 需要被套一层「作用域」,那再给所有已有的 antd
组件提高权重保证原有的选择器优先级不变就好了。幸运的是,antd
相关的组件都至少会有一个以 ant-
开头的 class,我们只要利用好这个特点及 CSS 属性选择器即可达到目的。
流程如下:
将 antd 中的
base.less
替换为魔改的base.less
,这个魔改的 base.less 外面会套一层*[class*='ant-']
限定其样式的「作用域」。这一步将全局样式限定在了所有有ant-
的 class 的元素里。*[class*='ant-'] {
@import '~antd/lib/style/core/base.less';
}
提高完了 base.less 的权重,再来提升组件的样式的权重,此举还能间接提升所有 antd 的样式的权重,避免外部的全局样式对 antd 造成侵入。
既然是改样式,那就用 CSS 界的 babel —— PostCSS,写个 PostCSS 插件,https://github.com/fi3ework/postcss-rename-selector ,将所有.ant
开头的类选择器都同样升高即可,利用的是 postcss-selector-parser 这个 PostCSS 官方提供的解析选择器的库,过滤出「第一个以 ant- 开头的类选择器」,在其前面添加一个属性选择器[class*='ant-']
,如果这个选择器排在当前 rule 的第一个或者前面是一个 combinator,则再加一个通配符*
,这个同上面给 base.less 添加的选择器,两者同时提高相同权重既维持原有优先级不变。
另外,如果某些元素虽然不在 antd 的组件里,但是也想走 antd 的全局样式,只需在这些元素的最外层套一个 classclassName="ant-whatever"
,只要是ant-
开头的就可以。import parser, { Node } from 'postcss-selector-parser'
import { SelectorReplace } from '../index'
export function antdScopeReplacerFn(node: Node) {
if (node.type !== 'selector') return
const firstAntClassNodeIndex = node.nodes.findIndex((n) => {
return n.type === 'class' && n.value.startsWith('ant-')
})
if (firstAntClassNodeIndex < 0) return
const firstAntClassNode = node.nodes[firstAntClassNodeIndex]
const prevNode = node.nodes[firstAntClassNodeIndex - 1]
// preserve line break
const spaces = {
before: firstAntClassNode.rawSpaceBefore,
after: firstAntClassNode.rawSpaceAfter,
}
firstAntClassNode.setPropertyWithoutEscape('rawSpaceBefore', '')
const toInsert = []
if (firstAntClassNodeIndex === 0 || prevNode.type === 'combinator') {
const universal = parser.universal({
value: '*',
})
toInsert.push(universal)
}
const attr = parser.attribute({
attribute: 'class',
operator: '*=',
value: `"ant-"`,
raws: {},
})
toInsert.push(attr)
toInsert[0].spaces = spaces
firstAntClassNode.parent!.nodes.splice(firstAntClassNodeIndex, 0, ...toInsert)
}
export const antdReplacer: SelectorReplace = {
type: 'each',
replacer: antdScopeReplacerFn,
}
解决步骤
1. 改写antd/lib/style/core/index.less
在 post-install 阶段将
antd/lib/style/core/index.less
引入的@import base;
这一行直接删掉,然后手动引入我们自己魔改的 base.less。步骤:写一个 post-install 脚本,直接改写antd/lib/style/core/index.less
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint --fix",
"postinstall": "node scripts/postinstall.js"
}
```javascript
!/usr/bin/env node
const replace = require(‘replace-in-file’)
const cwd = process.cwd()
const removeAntdGlobalStyles = () => {
const options = {
files: [${cwd}/node_modules/ant-design-vue/lib/style/core/index.less
, ${cwd}/node_modules/ant-design-vue/es/style/core/index.less
],
from: “@import ‘base’;”,
to: ‘’,
}
replace(options) .then(() => { console.log(‘[INFO] Successfully Removed Antd Global Styles:’) }) .catch((e) => { console.error(‘[ERR] Error removing Antd Global Styles:’, e) process.exit(1) }) }
removeAntdGlobalStyles()
<a name="TasCu"></a>
### 2. PostCSS 中添加 [postcss-rename-selector](https://github.com/fi3ework/postcss-rename-selector) 插件并配置:
![image.png](https://cdn.nlark.com/yuque/0/2020/png/294143/1608514029930-58703dbb-c063-4b7e-a28a-f18930335388.png#align=left&display=inline&height=728&margin=%5Bobject%20Object%5D&name=image.png&originHeight=728&originWidth=874&size=125638&status=done&style=none&width=874)
```javascript
const { replacer, presets } = require('postcss-rename-selector')
plugins: [
replacer(presets.antdReplacer)
]
3. 引入全量样式 import 'antd/dist/antd.less'
或者按需引入配置 babel-plugin-import
[
'import',
{
libraryName: 'antd',
style: true,
},
],
4. 额外引入一个 base.less,限定在一个「作用域」下
@import '~antd/lib/style/mixins/index.less';
*[class*='ant-'] {
@import '~antd/lib/style/core/base.less';
}
5.完善问题
问题:html
标签css 不生效
/* stylelint-disable at-rule-no-unknown */
// Reboot
//
// Normalization of HTML elements, manually forked from Normalize.css to remove
// styles targeting irrelevant browsers while applying new styles.
//
// Normalize is licensed MIT. https://github.com/necolas/normalize.css
// HTML & Body reset
@{html-selector},
body {
.square(100%);
}
// remove the clear button of a text input control in IE10+
input::-ms-clear,
input::-ms-reveal {
display: none;
}
// Document
//
// 1. Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`.
// 2. Change the default font family in all browsers.
// 3. Correct the line height in all browsers.
// 4. Prevent adjustments of font size after orientation changes in IE on Windows Phone and in iOS.
// 5. Setting @viewport causes scrollbars to overlap content in IE11 and Edge, so
// we force a non-overlapping, non-auto-hiding scrollbar to counteract.
// 6. Change the default tap highlight to be completely transparent in iOS.
*,
*::before,
*::after {
box-sizing: border-box; // 1
}
@{html-selector} {
font-family: sans-serif; // 2
line-height: 1.15; // 3
-webkit-text-size-adjust: 100%; // 4
-ms-text-size-adjust: 100%; // 4
-ms-overflow-style: scrollbar; // 5
-webkit-tap-highlight-color: fade(@black, 0%); // 6
}
解决:修改html-selector
变量
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { replacer, presets } = require('postcss-rename-selector')
module.exports = {
css: {
// 是否使用css分离插件 ExtractTextPlugin
extract: false, // 注释css热更新生效
// 开启 CSS source maps?
sourceMap: false,
loaderOptions: {
postcss: {
plugins: [
replacer(presets.antdReplacer),
],
},
less: {
lessOptions: {
modifyVars: {
'primary-color': '#2c4298',
'link-color': '#2c4298',
'html-selector': '.my-html-selector',
},
javascriptEnabled: true,
},
},
},
},
}
问题:弹窗类组件默认挂在body
下,导致不在ant-
类下
解决:修改弹窗类组件默认挂载点
<template>
<!-- 如果某些元素虽然不在 antd 的组件里,但是也想走 antd 的全局样式,只需在这些元素的最外层套一个 class className="ant-whatever",只要是 ant- 开头的就可以 -->
<a-config-provider :locale="locale" :getPopupContainer="getPopupContainer">
<div class="ant-root">
<div class="my-html-selector">
<my-root></my-root>
<!-- 弹窗类组件默认挂载点-->
<div id="ant-popupContainer"></div>
</div>
</div>
</a-config-provider>
</template>
<script lang="ts">
import '@/assets/styles/base.less'
import {
defineComponent, readonly, ref, reactive,
} from 'vue'
import { ConfigProvider } from 'ant-design-vue'
import zhCN from 'ant-design-vue/es/locale/zh_CN'
// import Root from '@/views/MissionSquare.vue'
// import Root from '@/views/Return.vue'
// import Root from '@/views/Refund.vue'
import Root from '@/views/ScanLogin.vue'
// import Root from '@/views/SmartBuy.vue'
function getPopupContainer(triggerNode: HTMLElement) {
const targetNode = document.getElementById('ant-popupContainer')
if (targetNode) {
return targetNode
}
return document.body
}
export default defineComponent({
components: {
[ConfigProvider.name]: ConfigProvider,
'my-root': Root,
},
setup() {
return {
getPopupContainer,
locale: zhCN,
}
},
})
</script>
效果
a
标签颜色没有被覆盖
修改后引入的css(部分)
[class*=ant-] .my-html-selector, [class*=ant-] body {
width: 100%;
height: 100%
}
[class*=ant-] input::-ms-clear, [class*=ant-] input::-ms-reveal {
display: none
}
[class*=ant-] *, [class*=ant-] :after, [class*=ant-] :before {
box-sizing: border-box
}
[class*=ant-] .my-html-selector {
font-family: sans-serif;
line-height: 1.15;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
-ms-overflow-style: scrollbar;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0)
}
@-ms-viewport {
width: device-width
}
[class*=ant-] article, [class*=ant-] aside, [class*=ant-] dialog, [class*=ant-] figcaption, [class*=ant-] figure, [class*=ant-] footer, [class*=ant-] header, [class*=ant-] hgroup, [class*=ant-] main, [class*=ant-] nav, [class*=ant-] section {
display: block
}