背景
看到項目中團隊成員寫CSS樣式風格迥異,CSS樣式的書寫順序沒有鮮明的規范。想到以前看過CSS樣式書寫順序的文章,決定找出來,給團隊成員科普一下。查閱了好幾篇文章,覺得這篇文章給出的理由最硬核,css樣式的書寫順序及原理——很重要! 然而擔心萬一文中的觀點不對,分享出去被打臉,於是決定驗證一下文中的說法。發現文中的核心觀點,下面這一段:
沒經受起實踐的檢驗。瀏覽器在渲染頁面的過程中,並不是實時逐條讀取樣式生成CSS Rule Tree,並進行繪制,而是會將同一個文件中的同類樣式先合並,再去構建樣式規則樹並繪制。請看下面的實驗,假如瀏覽器是實時逐條讀取樣式規則並進行繪制,那么box在渲染繪制的過程中,應該在某一瞬間出現綠色的背景,實際上發現並未出現,未繪制之前,后面設置的樣式就把前面設置的背景色重置了。
<style> .box { width:100vw; height: 100vh; background-color: green; } </style> <script> setTimeout(() => { document.querySelector('.item').style.backgroundColor='blue'; }, 2000); </script> <style> .box { background-color: red; } </style> <div class="box"></div>
實踐證明,樣式的書寫順序對頁面繪制沒有影響,雖然這篇文章的作者提出的觀點值得商榷,但是卻為我打開了學習stylelint的大門。我通過以文查文,找到了stylelint 。stylelint的價值在於能發現樣式書寫中的問題,並能給出一套合理的書寫規范。而這,正是我想找的。
1. stylelint帶來的好處
- 如下圖真實項目所示,可以發現樣式書寫的問題,以及對樣式書寫方法進行優化。
- 此外,能使所有人寫的樣式風格都一致,看別人寫的代碼,就像看自己寫的代碼一樣,立刻秒懂,易於代碼維護。
- 從心中沒有明確規則的書寫樣式,變成按照業內知名公司 (GitHub、Google、Airbnb)的樣式規范要求寫樣式。畢竟Google就是瀏覽器業內的知名公司,按照Google的樣式書寫規則去寫,不會被坑。
2. stylelint保存時自動格式化的配置方法
2.1 在VSCode應用市場,下載stylelint擴展,注意stylelint插件的版本要與 "stylelint-config-standard": "^22.0.0"版本匹配, 不然VSCode會彈出遷移提示的彈窗,另外stylelint 1.x版本,會把rgba格式化成十六進制顏色值,而移動端的瀏覽器不一定都支持這樣的屬性設置 參見 stylelint 接入實戰踩坑總結 最末章節,不要隨便升級 VSCode的stylelint插件
2.2 在項目下的.vscode/setting.json中添加開啟保存自動格式化的配置
{ "editor.codeActionsOnSave": { "source.fixAll.stylelint": true }, // 關閉vscode自帶的css,less,scss報錯提示 "css.validate": false, "less.validate": false, "scss.validate": false, "stylelint.validate": ["css", "less"] }
3. 如何批量修復項目樣式文件?
3.1 安裝stylelint相關的npm依賴包
yarn add -D stylelint stylelint-config-standard stylelint-order stylelint-config-recess-order
stylelint-config-standard
作用:配置 Stylelint 規則。
官方的代碼風格 :stylelint-config-standard。該風格是 Stylelint 的維護者汲取了 GitHub、Google、Airbnb 多家之長生成的。
stylelint-order
該插件的作用是強制你按照某個順序編寫 css。例如先寫定位,再寫盒模型,再寫內容區樣式,最后寫 CSS3 相關屬性。這樣可以極大的保證我們代碼的可讀性。
stylelint-config-recess-order
stylelint-order 插件的第三方配置
3.2 根目錄添加.stylelintrc.js 文件
module.exports = { extends: ['stylelint-config-standard', 'stylelint-config-recess-order'], plugins: ['stylelint-order'], rules: { indentation: 2, 'at-rule-no-unknown': [true, { ignoreAtRules: ['mixin', 'extend', 'content', 'include'] }], 'no-empty-source': null, // null是關閉規則的意思--less文件內容可以為空 'no-descending-specificity': null, //禁止特異性較低的選擇器在特異性較高的選擇器之后重寫 'font-family-no-missing-generic-family-keyword': null, // 關閉必須設置通用字體的規則 'function-calc-no-invalid': null, // 關閉對calc寫法的校驗 'property-no-unknown': [ true, { ignoreProperties: ['box-flex'], // 忽略某些未知屬性的檢測 }, ], 'selector-pseudo-element-no-unknown': [ true, { ignorePseudoElements: ['ng-deep'], // 忽略ng-deep這種合法的偽元素選擇器報警 }, ], 'declaration-colon-newline-after': null, //一個屬性過長的話可以寫成多行 'media-feature-name-no-unknown': null, // 關閉禁止未知的媒體功能名 // 下面的排序規則是stylelint-config-recess-order的css排序規則, // 要對某個屬性排序進行調整,這個屬性之前的樣式排序都要配置在自定義屬性排序中 'order/properties-order': [ { // Must be first. properties: ['all'], }, { // Position. properties: ['position', 'top', 'right', 'bottom', 'left', 'z-index'], }, { // Display mode. properties: ['box-sizing', 'display'], }, { // Flexible boxes. properties: ['flex', 'flex-basis', 'flex-direction', 'flex-flow', 'flex-grow', 'flex-shrink', 'flex-wrap'], }, { // Grid layout. properties: [ 'grid', 'grid-area', 'grid-template', 'grid-template-areas', 'grid-template-rows', 'grid-template-columns', 'grid-row', 'grid-row-start', 'grid-row-end', 'grid-column', 'grid-column-start', 'grid-column-end', 'grid-auto-rows', 'grid-auto-columns', 'grid-auto-flow', 'grid-gap', 'grid-row-gap', 'grid-column-gap', ], }, { // Align. properties: ['align-content', 'align-items', 'align-self'], }, { // Justify. properties: ['justify-content', 'justify-items', 'justify-self'], }, { // Order. properties: ['order'], }, { // Box model. properties: [ 'float', 'width', 'min-width', 'max-width', 'height', 'line-height', 'min-height', 'max-height', 'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left', 'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left', 'overflow', 'overflow-x', 'overflow-y', '-webkit-overflow-scrolling', '-ms-overflow-x', '-ms-overflow-y', '-ms-overflow-style', 'clip', 'clear', ], }, { // Typography. properties: [ 'font', 'font-family', 'font-size', 'font-style', 'font-weight', 'font-variant', 'font-size-adjust', 'font-stretch', 'font-effect', 'font-emphasize', 'font-emphasize-position', 'font-emphasize-style', '-webkit-font-smoothing', '-moz-osx-font-smoothing', 'font-smooth', 'hyphens', 'color', 'text-align', 'text-align-last', 'text-emphasis', 'text-emphasis-color', 'text-emphasis-style', 'text-emphasis-position', 'text-decoration', 'text-indent', 'text-justify', 'text-outline', '-ms-text-overflow', 'text-overflow', 'text-overflow-ellipsis', 'text-overflow-mode', 'text-shadow', 'text-transform', 'text-wrap', '-webkit-text-size-adjust', '-ms-text-size-adjust', 'letter-spacing', 'word-break', 'word-spacing', 'word-wrap', // Legacy name for `overflow-wrap` 'overflow-wrap', 'tab-size', 'white-space', 'vertical-align', 'list-style', 'list-style-position', 'list-style-type', 'list-style-image', ], }, { // Accessibility & Interactions. properties: [ 'pointer-events', '-ms-touch-action', 'touch-action', 'cursor', 'visibility', 'zoom', 'table-layout', 'empty-cells', 'caption-side', 'border-spacing', 'border-collapse', 'content', 'quotes', 'counter-reset', 'counter-increment', 'resize', 'user-select', 'nav-index', 'nav-up', 'nav-right', 'nav-down', 'nav-left', ], }, { // Background & Borders. properties: [ 'background', 'background-color', 'background-image', "-ms-filter:\\'progid:DXImageTransform.Microsoft.gradient", 'filter:progid:DXImageTransform.Microsoft.gradient', 'filter:progid:DXImageTransform.Microsoft.AlphaImageLoader', 'filter', 'background-repeat', 'background-attachment', 'background-position', 'background-position-x', 'background-position-y', 'background-clip', 'background-origin', 'background-size', 'background-blend-mode', 'isolation', 'border', 'border-color', 'border-style', 'border-width', 'border-top', 'border-top-color', 'border-top-style', 'border-top-width', 'border-right', 'border-right-color', 'border-right-style', 'border-right-width', 'border-bottom', 'border-bottom-color', 'border-bottom-style', 'border-bottom-width', 'border-left', 'border-left-color', 'border-left-style', 'border-left-width', 'border-radius', 'border-top-left-radius', 'border-top-right-radius', 'border-bottom-right-radius', 'border-bottom-left-radius', 'border-image', 'border-image-source', 'border-image-slice', 'border-image-width', 'border-image-outset', 'border-image-repeat', 'outline', 'outline-width', 'outline-style', 'outline-color', 'outline-offset', 'box-shadow', 'mix-blend-mode', 'filter:progid:DXImageTransform.Microsoft.Alpha(Opacity', "-ms-filter:\\'progid:DXImageTransform.Microsoft.Alpha", 'opacity', '-ms-interpolation-mode', ], }, { // SVG Presentation Attributes. properties: [ 'alignment-baseline', 'baseline-shift', 'dominant-baseline', 'text-anchor', 'word-spacing', 'writing-mode', 'fill', 'fill-opacity', 'fill-rule', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'flood-color', 'flood-opacity', 'image-rendering', 'lighting-color', 'marker-start', 'marker-mid', 'marker-end', 'mask', 'shape-rendering', 'stop-color', 'stop-opacity', ], }, { // Transitions & Animation. properties: [ 'transition', 'transition-delay', 'transition-timing-function', 'transition-duration', 'transition-property', 'transform', 'transform-origin', 'animation', 'animation-name', 'animation-duration', 'animation-play-state', 'animation-timing-function', 'animation-delay', 'animation-iteration-count', 'animation-direction', ], }, ], }, };
3.3 在package.json中添加stylelint樣式修復命令
{ "scripts": { "lintless": "stylelint src/**/*.less --fix", // ... }, }
3.4 在終端下執行yarn lintless命令, 就能對不符合配置樣式規范的代碼進行修復,部分問題還需要進一步手動修復。
yarn lintless
具體規則查詢參考:
[1] stylelint規則中文翻譯
[2] stylelint構造及規則了解