背景
在扫码登录的需求中,就是在 商城和至造云的登录框上挂个 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') returnconst firstAntClassNodeIndex = node.nodes.findIndex((n) => {return n.type === 'class' && n.value.startsWith('ant-')})if (firstAntClassNodeIndex < 0) returnconst firstAntClassNode = node.nodes[firstAntClassNodeIndex]const prevNode = node.nodes[firstAntClassNodeIndex - 1]// preserve line breakconst 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 = spacesfirstAntClassNode.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) 插件并配置:```javascriptconst { 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; // 2line-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-requiresconst { replacer, presets } = require('postcss-rename-selector')module.exports = {css: {// 是否使用css分离插件 ExtractTextPluginextract: 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}
