[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"donations-sidebar":3,"$f617FT-cWX-rdByOFdos63gGc6oEpBrM6FOsjTGLs2p0":4,"sidebar-data":11,"posts-{\"page\":1,\"pageSize\":10,\"categoryId\":\"11d4d397-685c-4180-a7b3-9b0e3a1e411e\"}":353},[],{"id":5,"name":6,"slug":7,"description":6,"sortOrder":8,"createdAt":9,"updatedAt":10},"11d4d397-685c-4180-a7b3-9b0e3a1e411e","Css","css",2,"2022-05-23T07:19:37.000Z","2023-02-08T02:49:14.000Z",{"categories":12,"tags":95,"postCount":279,"tagCount":280,"hotPosts":281},[13,21,27,29,35,42,49,53,60,64,68,74,80,85,89],{"id":14,"name":15,"slug":16,"description":17,"sortOrder":18,"createdAt":19,"updatedAt":19,"postCount":20},"e8d0bd45-d10c-46d3-8afb-0c072df7f8a7","技术","tech","技术文章",0,"2026-06-27T04:18:37.371Z",1,{"id":22,"name":23,"slug":24,"description":23,"sortOrder":20,"createdAt":25,"updatedAt":10,"postCount":26},"15ac46ad-edf7-4435-9a64-ff78117d58c5","Vue3 生态","vue3-生态","2022-05-21T08:05:39.000Z",6,{"id":5,"name":6,"slug":7,"description":6,"sortOrder":8,"createdAt":9,"updatedAt":10,"postCount":28},9,{"id":30,"name":31,"slug":31,"description":31,"sortOrder":32,"createdAt":33,"updatedAt":10,"postCount":34},"d10456a5-e649-4741-a38f-f07f266ce5f2","开发环境",3,"2022-05-24T01:52:41.000Z",13,{"id":36,"name":37,"slug":38,"description":37,"sortOrder":39,"createdAt":40,"updatedAt":10,"postCount":41},"5ed5cc62-43ea-49a2-b0b2-38bc7aae52a0","Vue3","vue3",4,"2022-05-24T01:55:05.000Z",8,{"id":43,"name":44,"slug":45,"description":44,"sortOrder":46,"createdAt":47,"updatedAt":10,"postCount":48},"da130ba9-d4f4-49f3-aa0f-149078097ef0","JavaScript","javascript",5,"2022-05-24T02:22:57.000Z",18,{"id":50,"name":51,"slug":51,"description":51,"sortOrder":26,"createdAt":52,"updatedAt":10,"postCount":20},"d8cbe380-54b3-4a61-a12d-5438c2918574","限时优惠","2022-05-25T07:18:03.000Z",{"id":54,"name":55,"slug":56,"description":55,"sortOrder":57,"createdAt":58,"updatedAt":10,"postCount":59},"e0f3b8d8-cfe7-41fb-802b-a79699d95968","JavaScript插件","javascript插件",7,"2022-06-01T14:08:31.000Z",16,{"id":61,"name":62,"slug":62,"description":62,"sortOrder":41,"createdAt":63,"updatedAt":10,"postCount":26},"4ea3d8af-9cc3-49bb-a9cd-34dbcdc3bd85","构建工具","2022-06-02T07:28:13.000Z",{"id":65,"name":66,"slug":66,"description":66,"sortOrder":28,"createdAt":67,"updatedAt":10,"postCount":41},"9ed9827c-9cbb-42da-80e4-d04c7fdba886","开发工具","2022-06-21T03:35:05.000Z",{"id":69,"name":70,"slug":71,"description":70,"sortOrder":72,"createdAt":73,"updatedAt":10,"postCount":46},"6b9179c3-17b2-43ff-a431-a03d6eb32d89","Vue2 生态","vue2-生态",10,"2022-07-16T13:14:29.000Z",{"id":75,"name":76,"slug":77,"description":76,"sortOrder":78,"createdAt":79,"updatedAt":10,"postCount":46},"73a5f62c-3c47-45b9-9ae2-f29953ae8dc0","Node","node",11,"2022-07-16T13:15:39.000Z",{"id":81,"name":82,"slug":82,"description":82,"sortOrder":83,"createdAt":84,"updatedAt":10,"postCount":8},"2b696c16-48ef-403b-a88b-6e57cfc79596","开发问题",12,"2022-07-16T14:06:54.000Z",{"id":86,"name":87,"slug":87,"description":87,"sortOrder":34,"createdAt":88,"updatedAt":10,"postCount":20},"c0f0561e-a47a-4ecd-8caa-cc1df2315d57","算法","2022-07-16T14:22:34.000Z",{"id":90,"name":91,"slug":92,"description":91,"sortOrder":93,"createdAt":94,"updatedAt":10,"postCount":32},"a629c1f7-29f1-439e-be3c-29670b17ba20","Vue2","vue2",15,"2022-07-16T14:41:51.000Z",[96,102,107,110,115,120,124,128,133,138,143,148,151,156,160,164,169,174,179,184,188,192,196,198,202,207,212,215,218,221,224,227,231,234,236,238,241,245,248,252,256,259,262,264,267,269,273,276],{"id":97,"name":98,"slug":99,"createdAt":100,"updatedAt":101},"076bd8b9-293e-45cb-9dc3-e162007ca474","Axios","axios","2022-06-05T07:41:56.000Z","2025-12-30T07:26:21.000Z",{"id":103,"name":104,"slug":105,"createdAt":106,"updatedAt":10},"2aa7f6d0-1fac-4ed1-b9bb-f3afc813f42c","Axure","axure","2022-06-21T03:35:15.000Z",{"id":108,"name":6,"slug":7,"createdAt":109,"updatedAt":10},"b084ddd8-09be-4e57-98f0-cf4e376aecd7","2022-05-21T09:59:55.000Z",{"id":111,"name":112,"slug":113,"createdAt":114,"updatedAt":10},"78a62bff-ff77-4878-8c25-3e6aae18c668","Docker","docker","2022-07-16T14:34:37.000Z",{"id":116,"name":117,"slug":118,"createdAt":119,"updatedAt":10},"2de16806-ef3f-4e54-a259-d1e1e182468c","Git","git","2022-07-16T14:25:15.000Z",{"id":121,"name":122,"slug":123,"createdAt":109,"updatedAt":10},"994cc226-578b-4a72-a57e-a47a63d2793e","JavaScript生态","javascript生态",{"id":125,"name":126,"slug":127,"createdAt":109,"updatedAt":10},"5086e93c-23b9-43d3-9643-cc87f0e9ee94","JenKins","jenkins",{"id":129,"name":130,"slug":131,"createdAt":132,"updatedAt":10},"b73007a8-bb5c-42a8-9fd9-163033a5b45d","Linux","linux","2022-07-16T14:40:17.000Z",{"id":134,"name":135,"slug":136,"createdAt":137,"updatedAt":10},"0b658b92-dd6b-4db3-a398-9f6d69950a02","Markdown","markdown","2022-07-16T14:39:25.000Z",{"id":139,"name":140,"slug":141,"createdAt":142,"updatedAt":10},"ab034d3a-6e5b-4db5-a2dc-faf4ccbb63f5","Nest","nest","2022-07-16T13:15:49.000Z",{"id":144,"name":145,"slug":146,"createdAt":147,"updatedAt":10},"52c41978-da06-4962-9636-45bbaeedda80","Nginx","nginx","2022-05-21T09:59:56.000Z",{"id":149,"name":150,"slug":150,"createdAt":147,"updatedAt":10},"0f1cc678-40e4-44b1-b2cf-a6fd8a1c867a","npm",{"id":152,"name":153,"slug":154,"createdAt":155,"updatedAt":10},"a4370d78-70e1-4073-a8f6-3dc5d81fd8fd","Nuxt","nuxt","2022-06-01T13:07:07.000Z",{"id":157,"name":158,"slug":159,"createdAt":109,"updatedAt":10},"d232e01f-048e-4151-8a0a-fff9561f946f","Pinia","pinia",{"id":161,"name":162,"slug":163,"createdAt":147,"updatedAt":10},"14e9ab02-b0bb-4c85-8604-fe6f1f0f33cd","Pnpm","pnpm",{"id":165,"name":166,"slug":167,"createdAt":168,"updatedAt":168},"399d1d38-cc0d-43ce-8baf-c769447a2ebd","React生态","react生态","2023-02-21T02:03:09.000Z",{"id":170,"name":171,"slug":172,"createdAt":173,"updatedAt":10},"c95bbe84-bdd0-410a-86a9-e87958c55f4f","Redis","redis","2022-10-05T05:14:14.000Z",{"id":175,"name":176,"slug":177,"createdAt":178,"updatedAt":10},"6d05f9df-e116-450f-af57-85ed710c4870","Swiper","swiper","2022-06-01T14:08:46.000Z",{"id":180,"name":181,"slug":182,"createdAt":183,"updatedAt":10},"66f3aeb0-84ef-45f6-a43a-944eefc9895a","Vite","vite","2022-06-02T07:28:24.000Z",{"id":185,"name":186,"slug":187,"createdAt":109,"updatedAt":10},"bf5b94d3-090b-4098-a03c-4bc69781fb2d","Vue","vue",{"id":189,"name":190,"slug":191,"createdAt":147,"updatedAt":10},"2f7fb1be-b9c5-4606-b54f-e9f66f2653b2","Vue-Router","vue-router",{"id":193,"name":194,"slug":195,"createdAt":109,"updatedAt":10},"2fef3b91-1c1c-4ae8-b2c1-0e04b4f9b3a2","Vue2生态","vue2生态",{"id":197,"name":37,"slug":38,"createdAt":109,"updatedAt":10},"62b94c93-724f-488d-8fc5-0449971d9204",{"id":199,"name":200,"slug":201,"createdAt":109,"updatedAt":10},"20bff9cd-7848-4c16-8775-42cf12b44b30","Vue3生态","vue3生态",{"id":203,"name":204,"slug":205,"createdAt":206,"updatedAt":10},"c807b2c6-cb12-4409-a1f1-6bea9f330a6b","Vuex","vuex","2022-07-16T13:14:59.000Z",{"id":208,"name":209,"slug":210,"createdAt":211,"updatedAt":10},"5782dff5-2ea2-4427-9696-d4363a7fd5bc","Webpack","webpack","2022-07-16T14:33:41.000Z",{"id":213,"name":214,"slug":214,"createdAt":109,"updatedAt":10},"d0aa41f4-68f8-48d4-a4ed-3a503ea90451","下载",{"id":216,"name":217,"slug":217,"createdAt":109,"updatedAt":10},"a046060c-39ef-474a-8c85-2546aca0e2e5","代码片段",{"id":219,"name":220,"slug":220,"createdAt":109,"updatedAt":10},"fee73435-b2be-4b55-85b1-d133ea96aea4","伪元素",{"id":222,"name":223,"slug":223,"createdAt":109,"updatedAt":10},"436bd369-8c57-4869-8827-e88e50e5e0ab","伪类",{"id":225,"name":226,"slug":226,"createdAt":109,"updatedAt":10},"4c6be544-8a00-4445-92a3-e3dcbaf6142e","动画",{"id":228,"name":229,"slug":229,"createdAt":230,"updatedAt":10},"9321a12e-ea72-49a9-a32d-5566149f812f","图片压缩","2022-08-02T00:37:47.000Z",{"id":232,"name":233,"slug":233,"createdAt":147,"updatedAt":10},"512b16fb-576a-4397-a7c5-dd20e6a8f9ca","布局",{"id":235,"name":66,"slug":66,"createdAt":109,"updatedAt":10},"f32faa96-f2ec-45c6-9a17-2c76062edcb0",{"id":237,"name":31,"slug":31,"createdAt":109,"updatedAt":10},"3c46ed3f-6d6b-4f91-bcb3-af5112860bf5",{"id":239,"name":240,"slug":240,"createdAt":109,"updatedAt":10},"dbfc086a-73a6-4560-814d-593acb61cf98","性能优化",{"id":242,"name":243,"slug":243,"createdAt":244,"updatedAt":10},"1831cd06-0d6b-48f7-94fa-324782fe23cb","拖拽","2022-07-28T12:39:13.000Z",{"id":246,"name":247,"slug":247,"createdAt":147,"updatedAt":10},"9a74300d-06f7-46d0-80d9-8fe67ec0539b","数组",{"id":249,"name":250,"slug":250,"createdAt":251,"updatedAt":10},"19ac8998-7e0a-459b-9702-bb1adca70e8c","文本复制","2022-07-17T01:54:45.000Z",{"id":253,"name":254,"slug":254,"createdAt":255,"updatedAt":10},"5ff33473-71a4-4e02-8c82-f9ea369a768f","时间","2022-07-17T01:51:12.000Z",{"id":257,"name":258,"slug":258,"createdAt":109,"updatedAt":10},"aa47ca4d-d3f6-4cac-b495-2c67c9592c36","最新优惠",{"id":260,"name":261,"slug":261,"createdAt":147,"updatedAt":10},"f6766d54-54fc-405e-932d-b7d550559125","服务器",{"id":263,"name":62,"slug":62,"createdAt":109,"updatedAt":10},"d856559a-03ff-40b4-980d-3f272b998c3c",{"id":265,"name":266,"slug":266,"createdAt":109,"updatedAt":10},"692d5d68-b188-4e5c-aca8-65d0229399a1","渐变",{"id":268,"name":87,"slug":87,"createdAt":109,"updatedAt":10},"38e1fd6b-d7c6-4d62-bf70-7bacc175bea9",{"id":270,"name":271,"slug":271,"createdAt":272,"updatedAt":10},"be7b10bc-49eb-4a03-bea7-ceb915d500fe","规范","2022-07-16T14:41:06.000Z",{"id":274,"name":275,"slug":275,"createdAt":147,"updatedAt":10},"b42e2916-ad62-4b8a-a863-cd8c19a829de","面试",{"id":277,"name":278,"slug":278,"createdAt":147,"updatedAt":10},"7069add9-b636-44f1-9cd4-ea3a6d2b85d3","面试题",104,48,[282,299,311,324,337],{"id":283,"title":284,"slug":284,"content":285,"excerpt":284,"coverImage":286,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":290,"publishedAt":291,"createdAt":291,"updatedAt":292,"category":293,"author":294,"tags":297},"a1bd1f49-6f6d-4fea-9789-b5636e19a6b3","uni-app瀑布流","## 实现思路\n> 获取父组件的列表数组，watch监听数组长度变化，截取后面新的数据，创建两个左右数组，比较左右dom的长度，哪个短，就push一条数据进去，源数组删除一条数据。利用img的load（加载成功）和error方法（加载失败），触发数组的push，实现瀑布流\n\n## 代码实现\n\n```language\n\u003Ctemplate>\n    \u003Cview class=\\\"waterfall\\\">\n        \u003Cview class=\\\"waterfall_left\\\">\n            \u003Cview class=\\\"waterfall_list\\\" v-for=\\\"(item, index) in leftList\\\" :key=\\\"index\\\">\n                \u003CSearch\n                    :name=\\\"item.name\\\"\n                    :image=\\\"item.src\\\"\n                    :label=\\\"item.label\\\"\n                    :item=\\\"item\\\"\n                    @considerPush=\\\"considerPush\\\"\n                >\n                \u003C\u002FSearch>\n            \u003C\u002Fview>\n        \u003C\u002Fview>\n        \u003Cview class=\\\"waterfall_right\\\">\n            \u003Cview class=\\\"waterfall_list\\\" v-for=\\\"(item, index) in rightList\\\" :key=\\\"index\\\">\n                \u003CSearch\n                    :name=\\\"item.name\\\"\n                    :image=\\\"item.src\\\"\n                    :label=\\\"item.label\\\"\n                    :item=\\\"item\\\"\n                    @considerPush=\\\"considerPush\\\"\n                >\n                \u003C\u002FSearch>\n            \u003C\u002Fview>\n        \u003C\u002Fview>\n    \u003C\u002Fview>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nimport Search from '@\u002Fcomponents\u002Fsearch\u002Fsearch.vue';\nexport default {\n    components: { Search },\n    props: {\n        list: {\n            type: Array,\n            default: () => []\n        }\n    },\n    data() {\n        return {\n            \u002F\u002F 左侧列表\n            leftList: [],\n            \u002F\u002F 右侧列表\n            rightList: [],\n            \u002F\u002F 组件数据备份\n            newList: [],\n            \u002F\u002F默认请求数,主要为了正常排序\n            interceptNumber: 10\n        };\n    },\n    created() {\n        this.touchOff(); \u002F\u002F 触发排列\n    },\n    mounted() {},\n    watch: {\n        list(newValue, oldValue) {\n            this.interceptNumber = newValue.length - oldValue.length;\n            this.touchOff();\n        }\n    },\n    computed: {},\n    methods: {\n        \u002F\u002F 触发重新排列\n        touchOff() {\n            this.newList = [...this.list.slice(-this.interceptNumber)];\n            if (this.newList.length !== 0) {\n                this.leftList.push(this.newList.shift()); \u002F\u002F触发排列\n            }\n        },\n        \u002F\u002F 计算排列\n        considerPush() {\n            this.$nextTick(() => {\n                if (this.newList.length == 0) return; \u002F\u002F没有数据了\n                let leftH = 0;\n                let rightH = 0; \u002F\u002F左右高度\n                let query = uni.createSelectorQuery().in(this);\n                query.selectAll('.waterfall_left').boundingClientRect();\n                query.selectAll('.waterfall_right').boundingClientRect();\n                query.exec(res => {\n                    leftH = res[0].length != 0 ? res[0][0].height : 0; \u002F\u002F防止查询不到做个处理\n                    rightH = res[1].length != 0 ? res[1][0].height : 0;\n                    if (leftH == rightH || leftH \u003C rightH) {\n                        \u002F\u002F 相等 || 左边小\n                        this.leftList.push(this.newList.shift());\n                    } else {\n                        \u002F\u002F 右边小\n                        this.rightList.push(this.newList.shift());\n                    }\n\n                    \u002F\u002F console.log('左右高度：', leftH, rightH, leftH == rightH || leftH \u003C rightH);\n                });\n            });\n        }\n    }\n};\n\u003C\u002Fscript>\n\n\u003Cstyle scoped lang=\\\"scss\\\">\n.waterfall {\n    display: flex;\n    align-items: flex-start;\n    justify-content: flex-start;\n    .waterfall_left {\n        flex: 1;\n    }\n\n    .waterfall_right {\n        flex: 1;\n    }\n}\n\u003C\u002Fstyle>\n\n```\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F7d4fa8d1d2775177b882a4656e3a5ed5.png","PUBLISHED",false,null,1817,"2022-11-22T07:54:41.000Z","2026-06-26T10:25:05.932Z",{"id":43,"name":44,"slug":45},{"id":295,"name":296,"avatar":289},"f9d0f2de-c700-4f90-b535-afd3dbe78128","Admin",[298],{"id":121,"name":122,"slug":123},{"id":300,"title":301,"slug":301,"content":302,"excerpt":301,"coverImage":303,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":304,"publishedAt":305,"createdAt":305,"updatedAt":306,"category":307,"author":308,"tags":309},"b2c46bf6-d971-4cce-b21b-052dbea8e8a2","v-html使用img点击实现放大效果","## 代码实现\n```js\n\u002F**\n * JS获取html代码中所有的图片地址\n * @param htmlstr\n * @returns arr 数组\n *\u002F\n\nexport function getimgsrc(htmlstr) {\n    let reg = \u002F\u003Cimg.+?src=('|\\\")?([^'\\\"]+)('|\\\")?(?:\\s+|>)\u002Fg;\n    let arr = [];\n    let tem = 0;\n    \u002F\u002Feslint-disable-next-line\n    while ((tem = reg.exec(htmlstr))) {\n        arr.push(tem[2]); \u002F\u002F eslint-disable-line\n    }\n\n    return arr;\n}\n\n```\n\n\n\n```vue\n\u003Ctemplate>\n    \u003Cdiv class=\\\"image-expansion\\\" :class=\\\"classArr\\\">\n        \u003Cdiv @click.stop=\\\"hanldeImage($event)\\\" v-html=\\\"formatHtmlData\\\">\u003C\u002Fdiv>\n\n        \u003Cel-image-viewer\n            v-if=\\\"imgPreviewUrl\\\"\n            :initial-index=\\\"subscript\\\"\n            :src=\\\"imgPreviewUrl\\\"\n            :on-close=\\\"closeViewer\\\"\n            :url-list=\\\"imgList\\\"\n        >\u003C\u002Fel-image-viewer>\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nimport { getimgsrc } from '..\u002F..\u002Futils\u002Fgetimgsrc';\nimport ElImageViewer from 'element-ui\u002Fpackages\u002Fimage\u002Fsrc\u002Fimage-viewer';\nexport default {\n    components: {\n        ElImageViewer\n    },\n    props: {\n        htmlData: {\n            type: String,\n            default: () => {\n                return '';\n            }\n        },\n        classArr: {\n            type: Array,\n            default: () => {\n                return ['min'];\n            }\n        },\n\n        isArticle: {\n            type: Boolean,\n            default: () => {\n                return false;\n            }\n        }\n    },\n    data() {\n        return {\n            imgList: [],\n            formatHtmlData: '',\n            imgPreviewUrl: '',\n            subscript: 0\n        };\n    },\n    computed: {},\n\n    watch: {\n        \u002F\u002F监听数据，防止数据不更新\n        htmlData: {\n            handler(newName, oldName) {\n                \u002F\u002F判断是否为文章\n                if (this.isArticle) {\n                    newName ? (this.formatHtmlData = newName.replace(\u002F(\u003C([^>]+)>)\u002Fgi, '').replace(\u002F[\\\r\\\n]\u002Fg, '')) : '';\n                } else {\n                    \u002F\u002F剔除strong和p标签\n                    newName ? (this.formatHtmlData = newName.replace(\u002F(\u003C\\\u002F?strong.*?>)|(\u003C\\\u002F?p.*?>)\u002Fg, '')) : '';\n\n                    \u002F\u002F获取html全部图片，push成图片数组\n                    this.imgList = Object.values(getimgsrc(this.formatHtmlData));\n                    \u002F\u002F获取图片下标\n                    let subscript = this.imgList.indexOf(this.imgPreviewUrl);\n                    this.subscript = subscript > -1 ? subscript : 0;\n                }\n            },\n\n            immediate: true\n        }\n    },\n\n    mounted() {},\n\n    methods: {\n        \u002F\u002F监听点击事件\n        hanldeImage(event) {\n            if (event.target.nodeName === 'IMG' || event.target.nodeName === 'img') {\n                \u002F\u002F获取点击的图片url,decodeURIComponent转码一下，防禁url转码\n                this.imgPreviewUrl = decodeURIComponent(event.target.currentSrc);\n\n                \u002F\u002F获取图片下标\n                let subscript = this.imgList.indexOf(this.imgPreviewUrl);\n                this.subscript = subscript > -1 ? subscript : 0;\n\n                \u002F\u002F禁止遮罩层后面的内容滚动\n                document.documentElement.style.overflowY = 'hidden';\n            } else {\n                this.$emit('goDetail');\n            }\n        },\n\n        \u002F\u002F关闭弹框\n        closeViewer() {\n            this.imgPreviewUrl = '';\n            \u002F\u002F恢复遮罩层后面的内容滚动\n            document.documentElement.style.overflowY = 'auto';\n        }\n    }\n};\n\u003C\u002Fscript>\n\n\u003Cstyle lang=\\\"scss\\\" scoped>\n.image-expansion {\n}\n\n.min {\n    \u002Fdeep\u002F img {\n        cursor: pointer;\n        height: 28px;\n        padding: 0 10px 3px;\n    }\n}\n\n.max {\n    \u002Fdeep\u002F img {\n        cursor: pointer;\n    }\n}\n\n.class1 {\n    \u002Fdeep\u002F div {\n        font-size: 15px;\n        font-family: Microsoft YaHei;\n        font-weight: 400;\n        color: #888888;\n        line-height: 30px;\n    }\n}\n\u003C\u002Fstyle>\n\n\n```\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002Fc77c3fb113d1ab2f67e7afba1ca33b95.png",1571,"2023-01-10T07:22:29.000Z","2026-06-26T10:25:07.650Z",{"id":69,"name":70,"slug":71},{"id":295,"name":296,"avatar":289},[310],{"id":228,"name":229,"slug":229},{"id":312,"title":313,"slug":314,"content":315,"excerpt":313,"coverImage":316,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":317,"publishedAt":318,"createdAt":318,"updatedAt":319,"category":320,"author":321,"tags":322},"9f33b49b-0785-4404-9a4e-d85937e7fc92","在 vue3 中优雅的使用 jsx\u002Ftsx","在-vue3-中优雅的使用-jsx-tsx","## 安装插件（@vitejs\u002Fplugin-vue-jsx）\n```shell\npnpm add @vitejs\u002Fplugin-vue-jsx -D\n```\n\n## 配置vite.config.ts\n```ts\nimport vueJsx from \\\"@vitejs\u002Fplugin-vue-jsx\\\";\n\nexport default defineConfig({\n  plugins: [\n    vueJsx(),\n  ]\n})\n```\n\n## 插值\n```language\n\u002F\u002F vue3模板语法\n\u003Cspan>{{ a + b }}\u003C\u002Fspan>\n\n\u002F\u002F jsx\u002Ftsx\n\u003Cspan>{ a + b }\u003C\u002Fspan>\n```\n\n\n## class与style 绑定\n\n```ts\n\u002F\u002F 模板字符串\n\u003Cdiv className={`header ${ isBg ? 'headerBg' : '' }`}>header\u003C\u002Fdiv>\n\u002F\u002F数组\n\u003Cdiv class={ [ 'header', isBg && 'headerBg' ] } >header\u003C\u002Fdiv>\n```\n\n```ts\nconst color = 'red'\nconst element = \u003Csapn style={{ color, fontSize: '16px' }}>style\u003C\u002Fsapn>\n```\n\n## 条件渲染\n\n```ts\n   setup() {\n       const isShow = false\n       const element = () => {\n           if (isShow) {\n               return \u003Cspan>我是if\u003C\u002Fspan>\n           } else {\n               return \u003Cspan>我是else\u003C\u002Fspan>\n           }\n       }\n       return () => (\n           \u003Cdiv>\n               \u003Cspan v-show={isShow}>我是v-show\u003C\u002Fspan>\n               {\n                   element()\n               }\n               {\n                   isShow ? \u003Cp>我是三目1\u003C\u002Fp> : \u003Cp>我是三目2\u003C\u002Fp>\n               }\n           \u003Cdiv>\n       )\n   }\n```\n\n## 列表渲染\n```language\nsetup() {\n   const listData = [\n       {name: 'Tom', age: 18},\n       {name: 'Jim', age: 20},\n       {name: 'Lucy', age: 16}\n   ]\n   return () => (\n       \u003Cdiv>\n           \u003Cdiv class={'box'}>\n               \u003Cspan>姓名\u003C\u002Fspan>\n               \u003Cspan>年龄\u003C\u002Fspan>\n           \u003C\u002Fdiv>\n           {\n               prop.listData.map(item => {\n                   return \u003Cdiv class={'box'}>\n                       \u003Cspan>{item.name}\u003C\u002Fspan>\n                       \u003Cspan>{item.age}\u003C\u002Fspan>\n                   \u003C\u002Fdiv>\n               })\n           }\n       \u003C\u002Fdiv>\n   )\n}\n\n```\n\n## 事件处理\n\n```language\nsetup() {\n    const clickBox = val => {\n        console.log(val)\n    }\n    return () => (\n        \u003Cdiv class={'box1'} onClick={() => clickBox('box1')}>\n            \u003Cspan>我是box1\u003C\u002Fspan>\n            \u003Cdiv class={'box2'} onClick={() => clickBox('box2')}>\n                \u003Cspan>我是box2\u003C\u002Fspan>\n                \u003Cdiv class={'box3'} onClick={withModifiers(() => clickBox('box3'), ['stop'])}>我是box3\u003C\u002Fdiv>\n            \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n    )\n}\n```\n\n## v-model\n\n```ts\n\u002F\u002F 正常写法\n\u003Cinput v-model=\\\"value\\\" \u002F> \u002F\u002F vue\n\u003Cinput v-model={value} \u002F> \u002F\u002F jsx\n\n\u002F\u002F 指定绑定值写法\n\u003Cinput v-model:modelValue=\\\"value\\\" \u002F> \u002F\u002F vue\n\u003Cinput v-model={[value,'modelValue']} \u002F> \u002F\u002F jsx\n\n\u002F\u002F 修饰符写法\n\u003Cinput v-model:modelValue.trim=\\\"value\\\" \u002F> \u002F\u002F vue\n\u003Cinput v-model={[value,'modelValue',['trim']]} \u002F> \u002F\u002F jsx\n```\n\n## slot插槽\n\n### 定义插槽\n```ts\nimport { renderSlot } from \\\"vue\\\"\nexport default defineComponent({\n    \u002F\u002F 从ctx中解构出来 slots\n    setup(props, { slots }) {\n        return () => (\n            \u003Cdiv>\n                { renderSlot(slots, 'default') }\n                { slots.title?.() }\n            \u003C\u002Fdiv>\n        )\n    }\n})\n\n```\n\n### 使用插槽\n\n```ts\nimport Vslot from '.\u002FslotTem'\nexport default defineComponent({\n    setup() {\n        return () => (\n            \u003Cdiv class={'box'}>\n                \u003CVslot v-slots={{\n                    title: () => {\n                        return \u003Cp>我是title插槽\u003C\u002Fp>\n                    },\n                    default: () => {\n                        return \u003Cp>我是default插槽\u003C\u002Fp>\n                    }\n                }} \u002F>\n            \u003C\u002Fdiv>\n        )\n    }\n})\n\n```\n\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F3e4cb0d3df611ee3f57b8ed503e1015e.png",1391,"2023-04-19T09:23:27.000Z","2026-06-26T09:54:47.369Z",{"id":36,"name":37,"slug":38},{"id":295,"name":296,"avatar":289},[323],{"id":197,"name":37,"slug":38},{"id":325,"title":326,"slug":327,"content":328,"excerpt":326,"coverImage":329,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":330,"publishedAt":331,"createdAt":331,"updatedAt":332,"category":333,"author":334,"tags":335},"1dcdd755-0e46-45a3-8998-9a213fd3fcd5","Vue3 导入导出Excel","vue3-导入导出excel","## 安装\n```shell\npnpm add -S XLSX\n```\n\n\n## 方法封装\n```js\nimport * as XLSX from 'xlsx';\n\n\u002F\u002F参数说明\n\u002F\u002Fconfiguration: {\n\u002F\u002F  data: [], \u002F\u002F 导出的数据\n\u002F\u002F  head: {}, \u002F\u002F 导出的数据对应的表头\n\u002F\u002F  name: '', \u002F\u002F 导出的文件名\n\u002F\u002F  label: '', \u002F\u002F 导出的表单名\n\u002F\u002F  widthArr: [], \u002F\u002F 导出的表单列宽\n\u002F\u002F}\n\n\u002F\u002F 导出excel\nexport const ExportXlsx = (configuration) => {\n  const { data, head, name, label, widthArr } = configuration;\n\n  const list = data.map((item) => {\n    const obj = {};\n    for (const k in item) {\n      if (head[k]) {\n        obj[head[k]] = item[k];\n      }\n    }\n    return obj;\n  });\n\n  \u002F\u002F 创建工作表\n  const xLSXData = XLSX.utils.json_to_sheet(list);\n  \u002F\u002F 创建工作簿\n  const wb = XLSX.utils.book_new();\n  \u002F\u002F 将工作表放入工作簿中\n  XLSX.utils.book_append_sheet(wb, xLSXData, label);\n  xLSXData['!cols'] = [];\n  \u002F\u002F 设置列宽\n  widthArr.forEach((item) => {\n    xLSXData['!cols'].push({ wpx: item });\n  });\n\n  \u002F\u002F 生成文件并下载\n  XLSX.writeFile(wb, `${name}.xlsx`);\n};\n\n\u002F\u002F 导入excel\nexport const ImportXlsx = (e) => {\n  const file = e.target.files[0];\n  const reader = new FileReader();\n  reader.readAsArrayBuffer(file);\n  reader.onload = (e) => {\n    const data = e.target.result;\n    const workbook = XLSX.read(data, { type: 'binary', cellDates: true });\n    const wsname = workbook.SheetNames[0];\n    const outdata = XLSX.utils.sheet_to_json(workbook.Sheets[wsname]);\n    console.log(outdata);\n  };\n};\n\n```\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F80231a326d3846b895a7d46c99e738ce.png",1334,"2023-03-14T06:18:59.000Z","2026-06-26T09:54:47.798Z",{"id":43,"name":44,"slug":45},{"id":295,"name":296,"avatar":289},[336],{"id":121,"name":122,"slug":123},{"id":338,"title":339,"slug":340,"content":341,"excerpt":339,"coverImage":342,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":343,"publishedAt":344,"createdAt":344,"updatedAt":345,"category":346,"author":350,"tags":351},"80920598-a452-4357-bf53-842b200560e8","React18入门到精通教程","react18入门到精通教程","\n## JSX实现列表渲染\n> 使用`map()`方法遍历数组，必须添加`key`属性提高性能\n```jsx\nconst songs = [\n  { id: 1, name: \\\"helo1\\\" },\n  { id: 2, name: \\\"helo2\\\" },\n  { id: 3, name: \\\"helo3\\\" },\n];\n\nfunction App() {\n  return (\n    \u003Cdiv>\n      \u003Cul>\n        {songs.map((song) => (\n          \u002F\u002F 关键：添加唯一key标识符（避免使用索引）\n          \u003Cli key={song.id}>  \n            {song.id}-{song.name}\n          \u003C\u002Fli>\n        ))}\n      \u003C\u002Ful>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n**最佳实践：**\n1. 使用`\u003Cul>`包裹列表项\n2. `key`应使用稳定唯一标识（如ID），避免数组索引\n3. 空列表处理：`{songs.length > 0 && ...}` 或 `{songs.map(...) || \u003CEmptyView\u002F>}`\n\n## JSX实现条件渲染\n### 简单逻辑\n```jsx\n\u002F\u002F 三元表达式\n{isLoggedIn ? \u003CDashboard \u002F> : \u003CLoginForm \u002F>}\n\n\u002F\u002F 逻辑短路\n{hasNotification && \u003CNotificationBadge count={5} \u002F>}\n\n\u002F\u002F 空值处理\n{userProfile?.avatar || \u003CDefaultAvatar \u002F>}\n```\n\n### 复杂逻辑\n```jsx\nconst renderContent = (type) => {\n  switch(type) {\n    case 'success': \n      return \u003CSuccessAlert \u002F>;\n    case 'error':\n      return \u003CErrorAlert \u002F>;\n    default:\n      return \u003CInfoAlert \u002F>;\n  }\n}\n\nfunction App() {\n  return (\n    \u003Cdiv className=\\\"container\\\">\n      {renderContent(status)}\n      \n      {\u002F* 另一种模式：立即执行函数 *\u002F}\n      {(() => {\n        if (isLoading) return \u003CSpinner \u002F>;\n        if (isEmpty) return \u003CEmptyState \u002F>;\n        return \u003CDataTable \u002F>;\n      })()}\n    \u003C\u002Fdiv>\n  )\n}\n```\n\n## JSX样式处理\n### 行内样式\n```jsx\n\u002F\u002F 直接对象\n\u003Cdiv style={{ \n  color: 'white', \n  backgroundColor: 'teal',\n  padding: '1rem'\n}}>\n\n\u002F\u002F 样式对象复用\nconst alertStyle = {\n  padding: '15px',\n  borderRadius: '4px',\n  margin: '10px 0'\n};\n\nfunction Alert({ type }) {\n  return (\n    \u003Cdiv style={{\n      ...alertStyle,  \u002F\u002F 扩展运算符合并样式\n      background: type === 'error' ? '#f8d7da' : '#d4edda'\n    }}>\n      {message}\n    \u003C\u002Fdiv>\n  )\n}\n```\n\n### 类名控制（推荐）\n```css\n\u002F* styles.module.css *\u002F\n.card {\n  border: 1px solid #ddd;\n  border-radius: 8px;\n  padding: 20px;\n  box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n}\n\n.highlight {\n  background-color: #ffffe0;\n}\n```\n\n```jsx\nimport styles from '.\u002Fstyles.module.css';\n\nfunction ProductCard({ featured }) {\n  return (\n    \u003Cdiv className={`${styles.card} ${featured ? styles.highlight : ''}`}>\n      {\u002F* 内容 *\u002F}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n## React 18新特性\n### 并发模式（Concurrent Mode）\n```jsx\nimport { startTransition } from 'react';\n\n\u002F\u002F 非紧急状态更新\nfunction handleSearch(query) {\n  startTransition(() => {\n    setSearchQuery(query); \u002F\u002F 可中断的渲染\n  });\n}\n```\n\n### 自动批处理（Automatic Batching）\n```jsx\n\u002F\u002F React 17及之前：两次渲染\n\u002F\u002F React 18：自动批处理，一次渲染\nfunction handleClick() {\n  setCount(c => c + 1);\n  setFlag(f => !f);\n}\n```\n\n## Redux状态管理（现代写法）\n### 安装依赖\n```bash\nnpm install @reduxjs\u002Ftoolkit react-redux\n```\n\n### 创建Store\n```js\n\u002F\u002F store.js\nimport { configureStore, createSlice } from '@reduxjs\u002Ftoolkit';\n\nconst counterSlice = createSlice({\n  name: 'counter',\n  initialState: { value: 0 },\n  reducers: {\n    increment: state => { state.value += 1 },\n    decrement: state => { state.value -= 1 },\n    incrementByAmount: (state, action) => {\n      state.value += action.payload\n    }\n  }\n});\n\n\u002F\u002F 异步操作示例\nexport const fetchUserData = () => async (dispatch) => {\n  const response = await fetch('\u002Fapi\u002Fuser');\n  dispatch(setUser(await response.json()));\n};\n\nexport const store = configureStore({\n  reducer: {\n    counter: counterSlice.reducer,\n    \u002F\u002F 其他reducer...\n  }\n});\n\nexport const { increment, decrement } = counterSlice.actions;\n```\n\n### 组件集成\n```jsx\n\u002F\u002F index.js\nimport { Provider } from 'react-redux';\nimport { store } from '.\u002Fstore';\n\nroot.render(\n  \u003CProvider store={store}>\n    \u003CApp \u002F>\n  \u003C\u002FProvider>\n);\n\n\u002F\u002F Counter.js\nimport { useSelector, useDispatch } from 'react-redux';\nimport { increment, decrement } from '.\u002Fstore';\n\nfunction Counter() {\n  const count = useSelector(state => state.counter.value);\n  const dispatch = useDispatch();\n\n  return (\n    \u003Cdiv>\n      \u003Cbutton onClick={() => dispatch(decrement())}>-\u003C\u002Fbutton>\n      \u003Cspan>{count}\u003C\u002Fspan>\n      \u003Cbutton onClick={() => dispatch(increment())}>+\u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n## 最佳实践总结\n1. **组件设计**：遵循单一职责原则，拆分智能组件（容器组件）和展示组件\n2. **状态管理**：\n   - 局部状态用`useState`\u002F`useReducer`\n   - 全局共享状态用Redux\n   - 避免过度使用状态提升\n3. **性能优化**：\n   - 使用`React.memo`记忆组件\n   - 使用`useCallback`\u002F`useMemo`避免不必要的重渲染\n   - 虚拟化长列表（react-window）\n4. **Hooks规范**：\n   - 避免在循环\u002F条件中使用Hook\n   - 自定义Hook以`use`前缀命名\n5. **TypeScript集成**：\n   ```tsx\n   interface UserCardProps {\n     name: string;\n     age: number;\n     onSelect: (id: string) => void;\n   }\n   \n   const UserCard: React.FC\u003CUserCardProps> = ({ name, age }) => (\n     \u003Cdiv>{name} ({age})\u003C\u002Fdiv>\n   )\n   ```\n\n下面为您完善教程，增加React路由和生命周期相关内容：\n\n## React路由管理（React Router v6）\n\n### 安装与基础配置\n```bash\nnpm install react-router-dom@6\n```\n\n### 路由基础结构\n```jsx\n\u002F\u002F index.js\nimport { BrowserRouter } from 'react-router-dom';\n\nconst root = ReactDOM.createRoot(document.getElementById('root'));\nroot.render(\n  \u003CBrowserRouter>\n    \u003CApp \u002F>\n  \u003C\u002FBrowserRouter>\n);\n\n\u002F\u002F App.js\nimport { Routes, Route, Link } from 'react-router-dom';\n\nfunction App() {\n  return (\n    \u003Cdiv>\n      \u003Cnav>\n        \u003CLink to=\\\"\u002F\\\">首页\u003C\u002FLink>\n        \u003CLink to=\\\"\u002Fabout\\\">关于\u003C\u002FLink>\n        \u003CLink to=\\\"\u002Fusers\\\">用户列表\u003C\u002FLink>\n      \u003C\u002Fnav>\n      \n      \u003CRoutes>\n        \u003CRoute path=\\\"\u002F\\\" element={\u003CHome \u002F>} \u002F>\n        \u003CRoute path=\\\"\u002Fabout\\\" element={\u003CAbout \u002F>} \u002F>\n        \u003CRoute path=\\\"\u002Fusers\\\" element={\u003CUserList \u002F>} \u002F>\n        \u003CRoute path=\\\"\u002Fusers\u002F:id\\\" element={\u003CUserDetail \u002F>} \u002F>\n        \u003CRoute path=\\\"*\\\" element={\u003CNotFound \u002F>} \u002F>\n      \u003C\u002FRoutes>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### 嵌套路由\n```jsx\n\u002F\u002F Dashboard.js\nimport { Outlet } from 'react-router-dom';\n\nfunction Dashboard() {\n  return (\n    \u003Cdiv>\n      \u003Ch2>仪表盘\u003C\u002Fh2>\n      \u003COutlet \u002F> {\u002F* 子路由渲染位置 *\u002F}\n    \u003C\u002Fdiv>\n  );\n}\n\n\u002F\u002F 路由配置\n\u003CRoutes>\n  \u003CRoute path=\\\"\u002Fdashboard\\\" element={\u003CDashboard \u002F>}>\n    \u003CRoute index element={\u003CDashboardHome \u002F>} \u002F>\n    \u003CRoute path=\\\"settings\\\" element={\u003CDashboardSettings \u002F>} \u002F>\n    \u003CRoute path=\\\"analytics\\\" element={\u003CDashboardAnalytics \u002F>} \u002F>\n  \u003C\u002FRoute>\n\u003C\u002FRoutes>\n```\n\n### 编程式导航\n```jsx\nimport { useNavigate, useParams, useLocation } from 'react-router-dom';\n\nfunction UserCard({ user }) {\n  const navigate = useNavigate();\n  \n  return (\n    \u003Cdiv onClick={() => navigate(`\u002Fusers\u002F${user.id}`)}>\n      {user.name}\n    \u003C\u002Fdiv>\n  );\n}\n\nfunction UserDetail() {\n  const { id } = useParams(); \u002F\u002F 获取URL参数\n  const location = useLocation(); \u002F\u002F 获取位置对象\n  \n  return (\n    \u003Cdiv>\n      \u003Ch2>用户ID: {id}\u003C\u002Fh2>\n      \u003Cp>当前路径: {location.pathname}\u003C\u002Fp>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### 路由守卫（认证保护）\n```jsx\nimport { Navigate } from 'react-router-dom';\n\nfunction ProtectedRoute({ children }) {\n  const { isAuthenticated } = useAuth();\n  \n  if (!isAuthenticated) {\n    return \u003CNavigate to=\\\"\u002Flogin\\\" replace \u002F>;\n  }\n  \n  return children;\n}\n\n\u002F\u002F 使用\n\u003CRoute \n  path=\\\"\u002Fdashboard\\\" \n  element={\n    \u003CProtectedRoute>\n      \u003CDashboard \u002F>\n    \u003C\u002FProtectedRoute>\n  } \n\u002F>\n```\n\n## React生命周期\n\n### 类组件生命周期方法\n\n```jsx\nclass LifecycleDemo extends React.Component {\n  \u002F\u002F 1. 初始化阶段\n  constructor(props) {\n    super(props);\n    this.state = { count: 0 };\n    console.log('Constructor');\n  }\n\n  \u002F\u002F 2. 挂载阶段\n  componentDidMount() {\n    console.log('Component did mount');\n    \u002F\u002F 适合进行API调用、事件订阅\n    this.timer = setInterval(() => {\n      this.setState(prev => ({ count: prev.count + 1 }));\n    }, 1000);\n  }\n\n  \u002F\u002F 3. 更新阶段\n  shouldComponentUpdate(nextProps, nextState) {\n    console.log('Should component update?');\n    return nextState.count !== this.state.count;\n  }\n\n  componentDidUpdate(prevProps, prevState) {\n    console.log('Component did update');\n  }\n\n  \u002F\u002F 4. 卸载阶段\n  componentWillUnmount() {\n    console.log('Component will unmount');\n    \u002F\u002F 清理操作\n    clearInterval(this.timer);\n  }\n\n  render() {\n    console.log('Render');\n    return \u003Cdiv>Count: {this.state.count}\u003C\u002Fdiv>;\n  }\n}\n```\n\n### 函数组件生命周期（Hooks实现）\n\n```jsx\nimport { useState, useEffect } from 'react';\n\nfunction FunctionLifecycle() {\n  const [count, setCount] = useState(0);\n  const [data, setData] = useState(null);\n\n  \u002F\u002F 相当于componentDidMount + componentDidUpdate\n  useEffect(() => {\n    console.log('每次渲染后执行');\n  });\n\n  \u002F\u002F 相当于componentDidMount\n  useEffect(() => {\n    console.log('组件挂载后执行');\n    \n    \u002F\u002F 数据获取\n    fetch('\u002Fapi\u002Fdata')\n      .then(res => res.json())\n      .then(setData);\n    \n    \u002F\u002F 相当于componentWillUnmount\n    return () => {\n      console.log('组件卸载前执行');\n    };\n  }, []); \u002F\u002F 空依赖数组\n\n  \u002F\u002F 依赖变化时执行\n  useEffect(() => {\n    console.log('count变化时执行:', count);\n    \n    document.title = `Count: ${count}`;\n    \n    return () => {\n      console.log('清理count效果');\n    };\n  }, [count]); \u002F\u002F count依赖\n\n  return (\n    \u003Cdiv>\n      \u003Cp>Count: {count}\u003C\u002Fp>\n      \u003Cbutton onClick={() => setCount(c => c + 1)}>增加\u003C\u002Fbutton>\n      {data && \u003Cpre>{JSON.stringify(data, null, 2)}\u003C\u002Fpre>}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### 生命周期阶段对比\n\n| 阶段 | 类组件方法 | 函数组件Hook |\n|------|------------|--------------|\n| **挂载** | constructor | useState初始化 |\n|       | render      | 函数体执行    |\n|       | componentDidMount | useEffect(() => {}, []) |\n| **更新** | shouldComponentUpdate | React.memo, useMemo |\n|       | render      | 函数体执行    |\n|       | componentDidUpdate | useEffect(() => {}) |\n| **卸载** | componentWillUnmount | useEffect返回函数 |\n| **错误处理** | componentDidCatch | 暂无直接等效，需错误边界组件 |\n\n### 现代React开发建议\n\n1. **优先使用函数组件+Hooks**：\n   - 90%的场景可替代类组件\n   - 更简洁的代码结构\n   - 更好的逻辑复用\n\n2. **关键生命周期替代**：\n   - `componentDidMount` → `useEffect(() => {}, [])`\n   - `componentDidUpdate` → `useEffect(() => {})` 或带依赖的 `useEffect`\n   - `componentWillUnmount` → `useEffect(() => { return cleanup }, [])`\n   - `shouldComponentUpdate` → `React.memo` 或 `useMemo`\n\n3. **数据获取最佳实践**：\n```jsx\nuseEffect(() => {\n  let isMounted = true;\n  \n  const fetchData = async () => {\n    try {\n      const result = await api.getData();\n      if (isMounted) setData(result);\n    } catch (error) {\n      if (isMounted) setError(error);\n    }\n  };\n  \n  fetchData();\n  \n  return () => {\n    isMounted = false; \u002F\u002F 避免组件卸载后设置状态\n  };\n}, []);\n```\n\n## 路由与生命周期整合示例\n\n```jsx\nfunction UserProfile() {\n  const { id } = useParams();\n  const [user, setUser] = useState(null);\n  const [loading, setLoading] = useState(true);\n\n  useEffect(() => {\n    let isActive = true;\n    \n    const fetchUser = async () => {\n      try {\n        setLoading(true);\n        const data = await fetchUserById(id);\n        if (isActive) {\n          setUser(data);\n          setLoading(false);\n        }\n      } catch (error) {\n        if (isActive) {\n          setError(error.message);\n          setLoading(false);\n        }\n      }\n    };\n    \n    fetchUser();\n    \n    return () => {\n      isActive = false; \u002F\u002F 清理效果\n    };\n  }, [id]); \u002F\u002F id变化时重新获取\n\n  if (loading) return \u003CSpinner \u002F>;\n  \n  return (\n    \u003Cdiv>\n      \u003Ch2>{user.name}\u003C\u002Fh2>\n      \u003Cp>Email: {user.email}\u003C\u002Fp>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n## 完整项目结构建议\n\n```\nsrc\u002F\n├── components\u002F      # 通用UI组件\n├── pages\u002F           # 页面组件\n├── layouts\u002F         # 布局组件\n├── hooks\u002F           # 自定义Hooks\n├── store\u002F           # Redux状态\n│   ├── slices\u002F\n│   └── store.js\n├── services\u002F        # API服务\n├── routers\u002F         # 路由配置\n├── utils\u002F           # 工具函数\n├── assets\u002F          # 静态资源\n└── App.js           # 主应用组件\n```\n\n这些新增内容涵盖了React路由的现代用法（v6版本）以及React生命周期的详细解释，包括类组件和函数组件的实现方式对比。同时还提供了路由与生命周期整合的实际示例，帮助开发者理解如何在真实项目中应用这些概念。","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F46e205aa1bd33d1bb7201019fc2fdf43.png",1147,"2023-02-21T02:01:52.000Z","2026-06-26T10:25:15.418Z",{"id":347,"name":348,"slug":349},"f1701085-b8c1-413a-8750-58e7a0a33832","React","react",{"id":295,"name":296,"avatar":289},[352],{"id":165,"name":166,"slug":167},{"list":354,"total":28,"page":20,"pageSize":72},[355,368,381,394,407,420,433,446,460],{"id":356,"title":357,"slug":357,"content":358,"excerpt":359,"coverImage":360,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":361,"publishedAt":362,"createdAt":362,"updatedAt":363,"category":364,"author":365,"tags":366},"5ae0e368-2586-4030-847d-8fa2c78e839a","前端蒙层中间挖空效果","# 前端蒙层中间挖空效果实现\n\n我将创建一个带有中间挖空效果的蒙层，这种效果常用于引导用户注意力或突出显示特定区域。\n\n## 设计思路\n- 创建一个覆盖全屏的半透明蒙层\n- 使用CSS的`clip-path`或`mask`属性在中间创建透明区域\n- 添加交互功能，如点击蒙层关闭\n\n## 实现代码\n\n```html\n\u003C!DOCTYPE html>\n\u003Chtml lang=\\\"zh-CN\\\">\n\u003Chead>\n    \u003Cmeta charset=\\\"UTF-8\\\">\n    \u003Cmeta name=\\\"viewport\\\" content=\\\"width=device-width, initial-scale=1.0\\\">\n    \u003Ctitle>蒙层中间挖空效果\u003C\u002Ftitle>\n    \u003Cstyle>\n        * {\n            margin: 0;\n            padding: 0;\n            box-sizing: border-box;\n        }\n        \n        body {\n            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n            background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);\n            color: #333;\n            line-height: 1.6;\n            padding: 20px;\n            min-height: 100vh;\n        }\n        \n        .container {\n            max-width: 1200px;\n            margin: 0 auto;\n        }\n        \n        header {\n            text-align: center;\n            margin-bottom: 40px;\n            color: white;\n        }\n        \n        h1 {\n            font-size: 2.5rem;\n            margin-bottom: 10px;\n            text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);\n        }\n        \n        .subtitle {\n            font-size: 1.2rem;\n            opacity: 0.9;\n        }\n        \n        .content {\n            display: grid;\n            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));\n            gap: 20px;\n            margin-bottom: 40px;\n        }\n        \n        .card {\n            background: white;\n            border-radius: 10px;\n            padding: 20px;\n            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);\n            transition: transform 0.3s ease;\n        }\n        \n        .card:hover {\n            transform: translateY(-5px);\n        }\n        \n        .card h3 {\n            margin-bottom: 15px;\n            color: #2575fc;\n        }\n        \n        .demo-area {\n            display: flex;\n            flex-wrap: wrap;\n            gap: 15px;\n            justify-content: center;\n            margin-top: 30px;\n        }\n        \n        .btn {\n            background: #2575fc;\n            color: white;\n            border: none;\n            padding: 12px 25px;\n            border-radius: 5px;\n            cursor: pointer;\n            font-size: 1rem;\n            transition: background 0.3s ease;\n            box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);\n        }\n        \n        .btn:hover {\n            background: #1a68e8;\n        }\n        \n        .btn-secondary {\n            background: #6a11cb;\n        }\n        \n        .btn-secondary:hover {\n            background: #5a0db3;\n        }\n        \n        \u002F* 蒙层样式 *\u002F\n        .overlay {\n            position: fixed;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            background: rgba(0, 0, 0, 0.7);\n            display: none;\n            z-index: 1000;\n        }\n        \n        \u002F* 方法1: 使用clip-path *\u002F\n        .overlay-clip {\n            clip-path: polygon(\n                0% 0%, \n                0% 100%, \n                25% 100%, \n                25% 25%, \n                75% 25%, \n                75% 75%, \n                25% 75%, \n                25% 100%, \n                100% 100%, \n                100% 0%\n            );\n        }\n        \n        \u002F* 方法2: 使用mask *\u002F\n        .overlay-mask {\n            -webkit-mask: radial-gradient(circle at center, transparent 100px, black 100px);\n            mask: radial-gradient(circle at center, transparent 100px, black 100px);\n        }\n        \n        \u002F* 方法3: 使用多个div组合 *\u002F\n        .overlay-complex {\n            background: none;\n        }\n        \n        .overlay-complex::before,\n        .overlay-complex::after {\n            content: '';\n            position: absolute;\n            background: rgba(0, 0, 0, 0.7);\n        }\n        \n        .overlay-complex::before {\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 40%;\n        }\n        \n        .overlay-complex::after {\n            bottom: 0;\n            left: 0;\n            width: 100%;\n            height: 40%;\n        }\n        \n        .overlay-left, .overlay-right {\n            position: absolute;\n            background: rgba(0, 0, 0, 0.7);\n            top: 40%;\n            height: 20%;\n        }\n        \n        .overlay-left {\n            left: 0;\n            width: 40%;\n        }\n        \n        .overlay-right {\n            right: 0;\n            width: 40%;\n        }\n        \n        .highlight-box {\n            position: absolute;\n            top: 40%;\n            left: 40%;\n            width: 20%;\n            height: 20%;\n            border: 2px dashed #ffcc00;\n            border-radius: 5px;\n            box-shadow: 0 0 0 9999px rgba(255, 204, 0, 0.2);\n        }\n        \n        .overlay-content {\n            position: absolute;\n            top: 50%;\n            left: 50%;\n            transform: translate(-50%, -50%);\n            background: white;\n            padding: 20px;\n            border-radius: 10px;\n            text-align: center;\n            max-width: 400px;\n            width: 90%;\n            z-index: 1001;\n        }\n        \n        .close-btn {\n            position: absolute;\n            top: 10px;\n            right: 15px;\n            background: none;\n            border: none;\n            font-size: 1.5rem;\n            cursor: pointer;\n            color: #666;\n        }\n        \n        footer {\n            text-align: center;\n            color: white;\n            margin-top: 40px;\n            padding-top: 20px;\n            border-top: 1px solid rgba(255, 255, 255, 0.2);\n        }\n    \u003C\u002Fstyle>\n\u003C\u002Fhead>\n\u003Cbody>\n    \u003Cdiv class=\\\"container\\\">\n        \u003Cheader>\n            \u003Ch1>前端蒙层中间挖空效果\u003C\u002Fh1>\n            \u003Cp class=\\\"subtitle\\\">多种方法实现蒙层中间挖空，突出显示特定内容区域\u003C\u002Fp>\n        \u003C\u002Fheader>\n        \n        \u003Cdiv class=\\\"content\\\">\n            \u003Cdiv class=\\\"card\\\">\n                \u003Ch3>方法一：使用 clip-path\u003C\u002Fh3>\n                \u003Cp>通过 CSS clip-path 属性裁剪出中间透明区域，实现挖空效果。\u003C\u002Fp>\n                \u003Cp>优点：代码简洁，性能较好。\u003C\u002Fp>\n                \u003Cp>缺点：形状固定，不够灵活。\u003C\u002Fp>\n                \u003Cdiv class=\\\"demo-area\\\">\n                    \u003Cbutton class=\\\"btn\\\" id=\\\"showClipOverlay\\\">演示效果\u003C\u002Fbutton>\n                \u003C\u002Fdiv>\n            \u003C\u002Fdiv>\n            \n            \u003Cdiv class=\\\"card\\\">\n                \u003Ch3>方法二：使用 mask\u003C\u002Fh3>\n                \u003Cp>使用 CSS mask 属性创建径向渐变，中间透明，四周不透明。\u003C\u002Fp>\n                \u003Cp>优点：可以实现圆形挖空，代码简洁。\u003C\u002Fp>\n                \u003Cp>缺点：兼容性稍差。\u003C\u002Fp>\n                \u003Cdiv class=\\\"demo-area\\\">\n                    \u003Cbutton class=\\\"btn\\\" id=\\\"showMaskOverlay\\\">演示效果\u003C\u002Fbutton>\n                \u003C\u002Fdiv>\n            \u003C\u002Fdiv>\n            \n            \u003Cdiv class=\\\"card\\\">\n                \u003Ch3>方法三：使用多个元素组合\u003C\u002Fh3>\n                \u003Cp>通过多个 div 元素组合，模拟中间挖空效果。\u003C\u002Fp>\n                \u003Cp>优点：兼容性最好，形状灵活。\u003C\u002Fp>\n                \u003Cp>缺点：代码较多，结构复杂。\u003C\u002Fp>\n                \u003Cdiv class=\\\"demo-area\\\">\n                    \u003Cbutton class=\\\"btn btn-secondary\\\" id=\\\"showComplexOverlay\\\">演示效果\u003C\u002Fbutton>\n                \u003C\u002Fdiv>\n            \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n        \n    \u003C\u002Fdiv>\n    \n    \u003C!-- 方法1: clip-path 蒙层 -->\n    \u003Cdiv class=\\\"overlay overlay-clip\\\" id=\\\"clipOverlay\\\">\n        \u003Cdiv class=\\\"overlay-content\\\">\n            \u003Cbutton class=\\\"close-btn\\\">&times;\u003C\u002Fbutton>\n            \u003Ch3>clip-path 挖空效果\u003C\u002Fh3>\n            \u003Cp>这种方法使用 CSS clip-path 属性裁剪出中间的矩形区域。\u003C\u002Fp>\n            \u003Cp>点击蒙层或关闭按钮可以关闭此提示。\u003C\u002Fp>\n        \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n    \n    \u003C!-- 方法2: mask 蒙层 -->\n    \u003Cdiv class=\\\"overlay overlay-mask\\\" id=\\\"maskOverlay\\\">\n        \u003Cdiv class=\\\"overlay-content\\\">\n            \u003Cbutton class=\\\"close-btn\\\">&times;\u003C\u002Fbutton>\n            \u003Ch3>mask 挖空效果\u003C\u002Fh3>\n            \u003Cp>这种方法使用 CSS mask 属性创建径向渐变，实现圆形挖空。\u003C\u002Fp>\n            \u003Cp>点击蒙层或关闭按钮可以关闭此提示。\u003C\u002Fp>\n        \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n    \n    \u003C!-- 方法3: 多个元素组合蒙层 -->\n    \u003Cdiv class=\\\"overlay overlay-complex\\\" id=\\\"complexOverlay\\\">\n        \u003Cdiv class=\\\"overlay-left\\\">\u003C\u002Fdiv>\n        \u003Cdiv class=\\\"overlay-right\\\">\u003C\u002Fdiv>\n        \u003Cdiv class=\\\"highlight-box\\\">\u003C\u002Fdiv>\n        \u003Cdiv class=\\\"overlay-content\\\">\n            \u003Cbutton class=\\\"close-btn\\\">&times;\u003C\u002Fbutton>\n            \u003Ch3>多个元素组合挖空效果\u003C\u002Fh3>\n            \u003Cp>这种方法使用多个 div 元素组合，模拟中间挖空效果。\u003C\u002Fp>\n            \u003Cp>点击蒙层或关闭按钮可以关闭此提示。\u003C\u002Fp>\n        \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n    \n    \u003Cscript>\n        \u002F\u002F 获取元素\n        const clipOverlay = document.getElementById('clipOverlay');\n        const maskOverlay = document.getElementById('maskOverlay');\n        const complexOverlay = document.getElementById('complexOverlay');\n        \n        const showClipBtn = document.getElementById('showClipOverlay');\n        const showMaskBtn = document.getElementById('showMaskOverlay');\n        const showComplexBtn = document.getElementById('showComplexOverlay');\n        \n        const closeButtons = document.querySelectorAll('.close-btn');\n        \n        \u002F\u002F 显示蒙层函数\n        function showOverlay(overlay) {\n            overlay.style.display = 'block';\n        }\n        \n        \u002F\u002F 隐藏蒙层函数\n        function hideOverlay(overlay) {\n            overlay.style.display = 'none';\n        }\n        \n        \u002F\u002F 绑定事件\n        showClipBtn.addEventListener('click', () => showOverlay(clipOverlay));\n        showMaskBtn.addEventListener('click', () => showOverlay(maskOverlay));\n        showComplexBtn.addEventListener('click', () => showOverlay(complexOverlay));\n        \n        \u002F\u002F 点击蒙层关闭\n        clipOverlay.addEventListener('click', (e) => {\n            if (e.target === clipOverlay) hideOverlay(clipOverlay);\n        });\n        \n        maskOverlay.addEventListener('click', (e) => {\n            if (e.target === maskOverlay) hideOverlay(maskOverlay);\n        });\n        \n        complexOverlay.addEventListener('click', (e) => {\n            if (e.target === complexOverlay) hideOverlay(complexOverlay);\n        });\n        \n        \u002F\u002F 点击关闭按钮关闭\n        closeButtons.forEach(btn => {\n            btn.addEventListener('click', () => {\n                hideOverlay(clipOverlay);\n                hideOverlay(maskOverlay);\n                hideOverlay(complexOverlay);\n            });\n        });\n    \u003C\u002Fscript>\n\u003C\u002Fbody>\n\u003C\u002Fhtml>\n```\n\n## 功能说明\n\n这个页面展示了三种实现蒙层中间挖空的方法：\n\n1. **使用 clip-path**：通过CSS的clip-path属性裁剪出中间透明区域\n2. **使用 mask**：通过CSS的mask属性创建径向渐变实现圆形挖空\n3. **使用多个元素组合**：通过多个div元素组合模拟中间挖空效果\n\n每种方法都有其优缺点：\n- clip-path：代码简洁但形状固定\n- mask：可以实现圆形挖空但兼容性稍差\n- 多个元素组合：兼容性最好但代码较多\n\n用户可以通过点击页面上的按钮来查看每种方法的效果，点击蒙层或关闭按钮可以关闭提示。\n\n这个设计采用了现代化UI，包含卡片布局、渐变背景和交互动画，提供了良好的用户体验。","多种方法实现蒙层中间挖空，突出显示特定内容区域","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F62a992ec61723351b751f2a2d844fb99_1762916206941.png",456,"2025-11-12T03:00:54.000Z","2026-06-26T08:40:27.091Z",{"id":5,"name":6,"slug":7},{"id":295,"name":296,"avatar":289},[367],{"id":108,"name":6,"slug":7},{"id":369,"title":370,"slug":371,"content":372,"excerpt":370,"coverImage":373,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":374,"publishedAt":375,"createdAt":375,"updatedAt":376,"category":377,"author":378,"tags":379},"c3d6d7d9-2e2e-46fa-a941-184c7bdee170","网页适配 iPhoneX，就是这么简单","网页适配-iphonex-就是这么简单","\n## iphonex 提供的 meta 头\n```htmL\n\u003Cmeta name=\\\"viewport\\\" content=\\\"viewport-fit=contain\\\">\n\u003Cmeta name=\\\"viewport\\\" content=\\\"viewport-fit=cover\\\">\n\u003Cmeta name=\\\"viewport\\\" content=\\\"viewport-fit=auto\\\">\n```\n\n### contain\n> 可视化窗口完全包含网页内容\n\n### cover\n网页内容完全覆盖\n\n### auto\n> 默认值和 contain 一样\n\n>注意：网页默认不添加扩展的表现是 viewport-fit=contain，需要适配 iPhoneX 必须设置\nviewport-fit=cover，这是适配的关键步骤。\nios11 增加新特性，webkit 的 css 函数\n为了应对刘海屏幕，苹果也给出了响应的策略\n\n### css 预定义变量\n>四个预定义变量为设定安全区域和边界的距离，如下:\n\n| 属性名 | 功能 |\n| - | - |\n| safe-area-inset-left | 安全区域距离左边边界距离 |\n| safe-area-inset-right | 安全区域距离右边边界距离 |\n| safe-area-inset-top | 安全区域距离顶部边界距离 |\n| safe-area-inset-bottom | 安全区域距离底部边界距离 |\n\n\n\nsafe-area-inset-left\n\n一般情况下是 0\n\nsafe-area-inset-right\n\n一般情况下是 0\n\nsafe-area-inset-top\n\n在刘海全屏的时候 top 为 44px\n\nsafe-area-inset-bottom\n\n刘海全屏的条件下是 34px\n\ncss 函数 env() 和 constant()\n这两个函数都是 webkit 中 css 函数，可以直接使用变量函数，只有在 webkit 内核下才支持\n\nenv 函数\n\n必须在 ios >= 11.2 才支持\n\nconstant 函数\n\n必须 ios \u003C 11.2 支持\n\nThe env() function shipped in iOS 11 with the name constant(). Beginning with Safari Technology\nPreview 41 and the iOS 11.2 beta, constant() has been removed and replaced with env(). You can use\nthe CSS fallback mechanism to support both versions, if necessary, but should prefer env() going\nforward.\n兼容前后版本代码\n\npadding-top: constant(safe-area-inset-top);\npadding-top: env(safe-area-inset-top);\n看下图:\n\n\n\n## 兼容代码\n> 下面写的是 scss\n```css\n.phonex {\n  padding-top: constant(safe-area-inset-top); \u002F\u002F为导航栏+状态栏的高度 88px\n  padding-top: env(safe-area-inset-top); \u002F\u002F为导航栏+状态栏的高度 88px\n  padding-left: constant(safe-area-inset-left); \u002F\u002F如果未竖屏时为0\n  padding-left: env(safe-area-inset-left); \u002F\u002F如果未竖屏时为0\n  padding-right: constant(safe-area-inset-right); \u002F\u002F如果未竖屏时为0\n  padding-right: env(safe-area-inset-right); \u002F\u002F如果未竖屏时为0\n  padding-bottom: constant(safe-area-inset-bottom); \u002F\u002F为底下圆弧的高度 34px\n  padding-bottom: env(safe-area-inset-bottom); \u002F\u002F为底下圆弧的高度 34px\n}\n```\n\n###  完整检测代码\n>@supports 隔离兼容模式\n```css\n@mixin iphonex-css {\n  padding-top: constant(safe-area-inset-top); \u002F\u002F为导航栏+状态栏的高度 88px\n  padding-top: env(safe-area-inset-top); \u002F\u002F为导航栏+状态栏的高度 88px\n  padding-left: constant(safe-area-inset-left); \u002F\u002F如果未竖屏时为0\n  padding-left: env(safe-area-inset-left); \u002F\u002F如果未竖屏时为0\n  padding-right: constant(safe-area-inset-right); \u002F\u002F如果未竖屏时为0\n  padding-right: env(safe-area-inset-right); \u002F\u002F如果未竖屏时为0\n  padding-bottom: constant(safe-area-inset-bottom); \u002F\u002F为底下圆弧的高度 34px\n  padding-bottom: env(safe-area-inset-bottom); \u002F\u002F为底下圆弧的高度 34px\n}\n@mixin iphonex-support {\n  @supports (bottom: constant(safe-area-inset-top)) or (bottom: env(safe-area-inset-top)) {\n    body.iphonex {\n      @include iphonex-css;\n    }\n  }\n}\n```\n\n使用@media 媒体查询\n```css\n@mixin iphonex-css {\n  padding-top: constant(safe-area-inset-top); \u002F\u002F为导航栏+状态栏的高度 88px\n  padding-top: env(safe-area-inset-top); \u002F\u002F为导航栏+状态栏的高度 88px\n  padding-left: constant(safe-area-inset-left); \u002F\u002F如果未竖屏时为0\n  padding-left: env(safe-area-inset-left); \u002F\u002F如果未竖屏时为0\n  padding-right: constant(safe-area-inset-right); \u002F\u002F如果未竖屏时为0\n  padding-right: env(safe-area-inset-right); \u002F\u002F如果未竖屏时为0\n  padding-bottom: constant(safe-area-inset-bottom); \u002F\u002F为底下圆弧的高度 34px\n  padding-bottom: env(safe-area-inset-bottom); \u002F\u002F为底下圆弧的高度 34px\n}\n```\n\n### iphonex 适配\n```css\n@mixin iphonex-media {\n  @media only screen and (device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) {\n    body.iphonex {\n      @include iphonex-css;\n    }\n  }\n}\n```\n\n\n> env 和 constant 只有在 viewport-fit=cover 时候才能生效, 上面使用的safari 的控制台可以检测模拟器中网页开启web inspector.\n\n\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F9eb815fc458b8f39c02b171e07f82b4e.png",826,"2022-11-03T06:30:51.000Z","2026-05-24T20:37:55.000Z",{"id":5,"name":6,"slug":7},{"id":295,"name":296,"avatar":289},[380],{"id":108,"name":6,"slug":7},{"id":382,"title":383,"slug":384,"content":385,"excerpt":383,"coverImage":386,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":387,"publishedAt":388,"createdAt":388,"updatedAt":389,"category":390,"author":391,"tags":392},"5e06c564-c8f8-47c3-97e6-2fa678d5ce7b","Scss入门到精通","scss入门到精通","\n\n## 什么是 CSS 预处理器？\n\n> CSS 预处理器用一种专门的编程语言，进行 Web 页面样式设计，然后再编译成正常的 CSS 文件，以供项目使用。CSS 预处理器为 CSS 增加一些编程的特性，无需考虑浏览器的兼容性问题”，例如你可以在 CSS 中使用变量、简单的逻辑程序、函数等等在编程语言中的一些基本特性，可以让你的 CSS 更加简洁、适应性更强**、**可读性更佳，更易于代码的维护等诸多好处。\n\n\n\n## [Sass]声明变量\n\n#### Sass 的变量包括三个部分：\n\n- 声明变量的符号“$”\n\n- 变量名称\n\n- 赋予变量的值\n\n来看一个简单的示例，假设你的按钮颜色可以给其声明几个变量：\n\n```scss\n$brand-primary : darken(#428bca, 6.5%) !default; \u002F\u002F #337ab7\n$btn-primary-color : #fff !default;\n$btn-primary-bg : $brand-primary !default;\n$btn-primary-border : darken($btn-primary-bg, 5%) !default;\n```\n\n>  如果值后面加上!default则表示默认值。\n\n\n\n## [Sass]普通变量与默认变量\n\n#### 普通变量\n\n>  定义之后可以在全局范围内使用。\n\n```scss\n$fontSize: 12px;\nbody{\n    font-size:$fontSize;\n}\n```\n\n#### 编译后的css代码：\n\n```scss\nbody{\n    font-size:12px;\n}\n```\n\n\n\n#### 默认变量\n\n> sass 的默认变量仅需要在值后面加上 !default 即可。\n\n```scss\n$baseLineHeight:1.5 !default;\nbody{\n    line-height: $baseLineHeight; \n}\n```\n\n#### 编译后的css代码：\n\n```scss\nbody{\n    line-height:1.5;\n}\n```\n\n>  sass 的默认变量一般是用来设置默认值，然后根据需求来覆盖的，覆盖的方式也很简单，只需要在默认变量之前重新声明下变量即可。\n\n```scss\n$baseLineHeight: 2;\n$baseLineHeight: 1.5 !default;\nbody{\n    line-height: $baseLineHeight; \n}\n```\n\n#### 编译后的css代码：\n\n```scss\nbody{\n    line-height:2;\n}\n```\n\n>  可以看出现在编译后的 line-height 为 2，而不是我们默认的 1.5。默认变量的价值在进行组件化开发的时候会非常有用。\n\n\n\n## [Sass]变量的调用\n\n> 比如在定义了变量\n\n```scss\n$brand-primary : darken(#428bca, 6.5%) !default; \u002F\u002F #337ab7\n$btn-primary-color: #fff !default;\n$btn-primary-bg : $brand-primary !default;\n$btn-primary-border : darken($btn-primary-bg, 5%) !default;\n```\n\n> 在按钮 button 中调用，可以按下面的方式调用\n\n```scss\n.btn-primary {\n   background-color: $btn-primary-bg;\n   color: $btn-primary-color;\n   border: 1px solid $btn-primary-border;\n}\n```\n\n> 编译出来的CSS:\n\n```scss\n.btn-primary {\n  background-color: #337ab7;\n  color: #fff;\n  border: 1px solid #2e6da4;\n}\n```\n\n [Sass]局部变量和全局变量\n\nSass 中变量的作用域在过去几年已经发生了一些改变。直到最近，规则集和其他范围内声明变量的作用域才默认为本地。如果已经存在同名的全局变量，从 3.4 版本开始，Sass 已经可以正确处理作用域的概念，并通过创建一个新的局部变量来代替。\n\n\n\n#### 全局变量与局部变量\n\n```scss\n$color: orange !default;\u002F\u002F定义全局变量(在选择器、函数、混合宏...的外面定义的变量为全局变量)\n.block {\n  color: $color;\u002F\u002F调用全局变量\n}\nem {\n  $color: red;\u002F\u002F定义局部变量\n  a {\n    color: $color;\u002F\u002F调用局部变量\n  }\n}\nspan {\n  color: $color;\u002F\u002F调用全局变量\n}\n```\n\n> css 的结果：\n\n```scss\n.block {\n  color: orange;\n}\nem a {\n  color: red;\n}\nspan {\n  color: orange;\n}\n```\n\n\n\n上面的示例演示可以得知，在元素内部定义的变量不会影响其他元素。如此可以简单的理解成，**全局变量**就是定义在元素外面的变量，如下代码：\n\n```\n$color:orange !default;\n```\n\n$color 就是一个全局变量，而定义在元素内部的变量，比如 $color:red; 是一个**局部变量**。\n\n除此之外，Sass 现在还提供一个 !global 参数。!global 和 !default 对于定义变量都是很有帮助的。我们之后将会详细介绍这两个参数的使用以及其功能。\n\n**全局变量的影子**\n\n当在局部范围（选择器内、函数内、混合宏内...）声明一个已经存在于全局范围内的变量时，局部变量就成为了**全局变量的影子**。基本上，**局部变量只会在局部范围内覆盖全局变量**。\n\n上面例子中的 em 选择器内的变量 $color 就是一个全局变量的影子。\n\n```scss\n\u002F\u002FSCSS\n$color: orange !default;\u002F\u002F定义全局变量\n.block {\n  color: $color;\u002F\u002F调用全局变量\n}\nem {\n  $color: red;\u002F\u002F定义局部变量（全局变量 $color 的影子）\n  a {\n    color: $color;\u002F\u002F调用局部变量\n  }\n}\n```\n\n**什么时候声明变量？**\n\n我的建议，创建变量只适用于感觉确有必要的情况下。不要为了某些骇客行为而声明新变量，这丝毫没有作用。只有满足所有下述标准时方可创建新变量：\n\n1. 该值至少重复出现了两次；\n2. 该值至少可能会被更新一次；\n3. 该值所有的表现都与变量有关（非巧合）。\n\n基本上，没有理由声明一个永远不需要更新或者只在单一地方使用变量。\n\n\n\n## [Sass]嵌套-选择器嵌套\n\nSass 中还提供了选择器嵌套功能，但这也并不意味着你在 Sass 中的嵌套是无节制的，因为你嵌套的层级越深，编译出来的 CSS 代码的选择器层级将越深，这往往是大家不愿意看到的一点。这个特性现在正被众多开发者滥用。\n\n选择器嵌套为样式表的作者提供了一个通过局部选择器相互嵌套实现全局选择的方法，Sass 的嵌套分为三种：\n\n- 选择器嵌套\n- 属性嵌套\n- 伪类嵌套\n\n1、选择器嵌套\n\n假设我们有一段这样的结构：\n\n```scss\n\u003Cheader>\n\u003Cnav>\n    \u003Ca href=“##”>Home\u003C\u002Fa>\n    \u003Ca href=“##”>About\u003C\u002Fa>\n    \u003Ca href=“##”>Blog\u003C\u002Fa>\n\u003C\u002Fnav>\n\u003Cheader>\n```\n\n想选中 header 中的 a 标签，在写 CSS 会这样写：\n\n```scss\nnav a {\n  color:red;\n}\n\nheader nav a {\n  color:green;\n}\n```\n\n那么在 Sass 中，就可以使用选择器的嵌套来实现：\n\n```scss\nnav {\n  a {\n    color: red;\n\n    header & {\n      color:green;\n    }\n  }  \n}\n```\n\n \n\n### 任务\n\n在编辑器第 2 行和第 5 行输入正确的选择器，使其编译出来的CSS如下：\n\n```scss\nnav a {\n   color: red;\n}\n\nheader nav a {\n   color: green;\n}\n```\n\n## [Sass]嵌套-属性嵌套\n\nSass 中还提供属性嵌套，CSS 有一些属性前缀相同，只是后缀不一样，比如：border-top\u002Fborder-right，与这个类似的还有 margin、padding、font 等属性。假设你的样式中用到了：\n\n```scss\n.box {\n    border-top: 1px solid red;\n    border-bottom: 1px solid green;\n}\n```\n\n在 Sass 中我们可以这样写：\n\n```scss\n.box {\n  border: {\n   top: 1px solid red;\n   bottom: 1px solid green;\n  }\n}\n```\n\n## [Sass]嵌套-伪类嵌套\n\n其实伪类嵌套和属性嵌套非常类似，只不过他需要借助`&`符号一起配合使用。我们就拿经典的“clearfix”为例吧：\n\n```scss\n.clearfix{\n&:before,\n&:after {\n    content:\\\"\\\";\n    display: table;\n  }\n&:after {\n    clear:both;\n    overflow: hidden;\n  }\n}\n```\n\n编译出来的 CSS：\n\n```\nclearfix:before, .clearfix:after {\n  content: \\\"\\\";\n  display: table;\n}\n.clearfix:after {\n  clear: both;\n  overflow: hidden;\n}\n```\n\n**避免选择器嵌套：**\n\n- 选择器嵌套最大的问题是将使最终的代码难以阅读。开发者需要花费巨大精力计算不同缩进级别下的选择器具体的表现效果。\n- 选择器越具体则声明语句越冗长，而且对最近选择器的引用(&)也越频繁。在某些时候，出现混淆选择器路径和探索下一级选择器的错误率很高，这非常不值得。\n\n为了防止此类情况，我们应该尽可能避免选择器嵌套。然而，显然只有少数情况适应这一措施。\n\n\n\n## [Sass]混合宏-声明混合宏\n\n如果你的整个网站中有几处小样式类似，比如颜色，字体等，在 Sass 可以使用变量来统一处理，那么这种选择还是不错的。但当你的样式变得越来越复杂，需要重复使用大段的样式时，使用变量就无法达到我们目了。这个时候 Sass 中的混合宏就会变得非常有意义。在这一节中，主要向大家介绍 Sass 的混合宏。\n\n1、声明混合宏\n\n**不带参数混合宏**：\n\n在 Sass 中，使用“@mixin”来声明一个混合宏。如：\n\n```scss\n@mixin border-radius{\n    -webkit-border-radius: 5px;\n    border-radius: 5px;\n}\n```\n\n其中 @mixin 是用来声明混合宏的关键词，有点类似 CSS 中的 @media、@font-face 一样。border-radius 是混合宏的名称。大括号里面是复用的样式代码。\n\n**带参数混合宏**：\n\n除了声明一个不带参数的混合宏之外，还可以在定义混合宏时带有参数，如：\n\n```scss\n@mixin border-radius($radius:5px){\n    -webkit-border-radius: $radius;\n    border-radius: $radius;\n}\n```\n\n**复杂的混合宏：**\n\n上面是一个简单的定义混合宏的方法，当然， Sass 中的混合宏还提供更为复杂的，你可以在大括号里面写上带有逻辑关系，帮助更好的做你想做的事情,如：\n\n```scss\n@mixin box-shadow($shadow...) {\n  @if length($shadow) >= 1 {\n    @include prefixer(box-shadow, $shadow);\n  } @else{\n    $shadow:0 0 4px rgba(0,0,0,.3);\n    @include prefixer(box-shadow, $shadow);\n  }\n}\n```\n\n这个 box-shadow 的混合宏，带有多个参数，这个时候可以使用“ … ”来替代。简单的解释一下，当 $shadow 的参数数量值大于或等于“ 1 ”时，表示有多个阴影值，反之调用默认的参数值“ 0 0 4px rgba(0,0,0,.3) ”。\n\n注：复杂的混合宏中的逻辑关系（@if...@else）后面小节会有讲解。\n\n\n\n## [Sass]混合宏-调用混合宏\n\n在 Sass 中通过 @mixin 关键词声明了一个混合宏，那么在实际调用中，其匹配了一个关键词“@include”来调用声明好的混合宏。例如在你的样式中定义了一个圆角的混合宏“border-radius”:\n\n```scss\n@mixin border-radius{\n    -webkit-border-radius: 3px;\n    border-radius: 3px;\n}\n```\n\n在一个按钮中要调用定义好的混合宏“border-radius”，可以这样使用：\n\n```scss\nbutton {\n    @include border-radius;\n}\n```\n\n这个时候编译出来的 CSS:\n\n```scss\nbutton {\n  -webkit-border-radius: 3px;\n  border-radius: 3px;\n}\n```\n\n\n\n## [Sass]混合宏的参数--传一个不带值的参数\n\nSass 的混合宏有一个强大的功能，可以传参，那么在 Sass 中传参主要有以下几种情形：\n\nA) 传一个不带值的参数\n\n在混合宏中，可以传一个不带任何值的参数，比如：\n\n```scss\n@mixin border-radius($radius){\n  -webkit-border-radius: $radius;\n  border-radius: $radius;\n}\n```\n\n在混合宏“border-radius”中定义了一个不带任何值的参数“$radius”。\n\n在调用的时候可以给这个混合宏传一个参数值：\n\n```scss\n.box {\n  @include border-radius(3px);\n}\n```\n\n这里表示给混合宏传递了一个“border-radius”的值为“3px”。\n\n编译出来的 CSS:\n\n```scss\n.box {\n  -webkit-border-radius: 3px;\n  border-radius: 3px;\n}\n```\n\n## [Sass]混合宏的参数--传一个带值的参数\n\n在 Sass 的混合宏中，还可以给混合宏的参数传一个默认值，例如：\n\n```scss\n@mixin border-radius($radius:3px){\n  -webkit-border-radius: $radius;\n  border-radius: $radius;\n}\n```\n\n在混合宏“border-radius”传了一个参数“$radius”，而且给这个参数赋予了一个默认值“3px”。\n\n在调用类似这样的混合宏时，会多有一个机会，假设你的页面中的圆角很多地方都是“3px”的圆角，那么这个时候只需要调用默认的混合宏“border-radius”:\n\n```scss\n.btn {\n  @include border-radius;\n}\n```\n\n编译出来的 CSS:\n\n```scss\n.btn {\n  -webkit-border-radius: 3px;\n  border-radius: 3px;\n}\n```\n\n但有的时候，页面中有些元素的圆角值不一样，那么可以随机给混合宏传值，如：\n\n```scss\n.box {\n  @include border-radius(50%);\n}\n```\n\n编译出来的 CSS:\n\n```scss\n.box {\n  -webkit-border-radius: 50%;\n  border-radius: 50%;\n}\n```\n\n \n\n## [Sass]混合宏的参数--传多个参数\n\nSass 混合宏除了能传一个参数之外，还可以传多个参数，如：\n\n```scss\n@mixin center($width,$height){\n  width: $width;\n  height: $height;\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  margin-top: -($height) \u002F 2;\n  margin-left: -($width) \u002F 2;\n}\n```\n\n在混合宏“center”就传了多个参数。在实际调用和其调用其他混合宏是一样的：\n\n```scss\n.box-center {\n  @include center(500px,300px);\n}\n```\n\n编译出来 CSS:\n\n```scss\n.box-center {\n  width: 500px;\n  height: 300px;\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  margin-top: -150px;\n  margin-left: -250px;\n}\n```\n\n 有一个特别的参数“**…**”。当混合宏传的参数过多之时，可以使用参数来替代，如：\n\n```scss\n@mixin box-shadow($shadows...){\n  @if length($shadows) >= 1 {\n    -webkit-box-shadow: $shadows;\n    box-shadow: $shadows;\n  } @else {\n    $shadows: 0 0 2px rgba(#000,.25);\n    -webkit-box-shadow: $shadow;\n    box-shadow: $shadow;\n  }\n}\n```\n\n在实际调用中：\n\n```scss\n.box {\n  @include box-shadow(0 0 1px rgba(#000,.5),0 0 2px rgba(#000,.2));\n}\n```\n\n编译出来的CSS:\n\n```scss\n.box {\n  -webkit-box-shadow: 0 0 1px rgba(0, 0, 0, 0.5), 0 0 2px rgba(0, 0, 0, 0.2);\n  box-shadow: 0 0 1px rgba(0, 0, 0, 0.5), 0 0 2px rgba(0, 0, 0, 0.2);\n}\n```\n\n## [Sass]混合宏的参数--混合宏的不足\n\n混合宏在实际编码中给我们带来很多方便之处，特别是对于复用重复代码块。但其最大的不足之处是会生成冗余的代码块。比如在不同的地方调用一个相同的混合宏时。如：\n\n```scss\n@mixin border-radius{\n  -webkit-border-radius: 3px;\n  border-radius: 3px;\n}\n\n.box {\n  @include border-radius;\n  margin-bottom: 5px;\n}\n\n.btn {\n  @include border-radius;\n}\n```\n\n示例在“.box”和“.btn”中都调用了定义好的“border-radius”混合宏。先来看编译出来的 CSS：\n\n```scss\n.box {\n  -webkit-border-radius: 3px;\n  border-radius: 3px;\n  margin-bottom: 5px;\n}\n\n.btn {\n  -webkit-border-radius: 3px;\n  border-radius: 3px;\n}\n```\n\n上例明显可以看出，Sass 在调用相同的混合宏时，并不能智能的将相同的样式代码块合并在一起。这也是 Sass 的混合宏最不足之处。\n\n## [Sass]扩展\u002F继承\n\n继承对于了解 CSS 的同学来说一点都不陌生，先来看一张图：\n\n\n\n图中代码显示“.col-sub .block li,.col-extra .block li” 继承了 “.item-list ul li”选择器的 “padding : 0;” 和 “ul li” 选择器中的 “list-style : none outside none;”以及 * 选择器中的 “box-sizing:inherit;”。\n\n在 Sass 中也具有继承一说，也是继承类中的样式代码块。在 Sass 中是通过关键词 “@extend”来继承已存在的类样式块，从而实现代码的继承。如下所示：\n\n```scss\n.btn {\n  border: 1px solid #ccc;\n  padding: 6px 10px;\n  font-size: 14px;\n}\n\n.btn-primary {\n  background-color: #f36;\n  color: #fff;\n  @extend .btn;\n}\n\n.btn-second {\n  background-color: orange;\n  color: #fff;\n  @extend .btn;\n}\n```\n\n编译出来之后：\n\n```css\n.btn, .btn-primary, .btn-second {\n  border: 1px solid #ccc;\n  padding: 6px 10px;\n  font-size: 14px;\n}\n\n.btn-primary {\n  background-color: #f36;\n  color: #fff;\n}\n\n.btn-second {\n  background-clor: orange;\n  color: #fff;\n}\n```\n\n从示例代码可以看出，在 Sass 中的继承，可以继承类样式块中所有样式代码，而且编译出来的 CSS 会将选择器合并在一起，形成组合选择器：\n\n```css\n.btn, .btn-primary, .btn-second {\n  border: 1px solid #ccc;\n  padding: 6px 10px;\n  font-size: 14px;\n}\n```\n\n## [Sass]占位符 %placeholder\n\nSass 中的占位符 %placeholder 功能是一个很强大，很实用的一个功能，这也是我非常喜欢的功能。他可以取代以前 CSS 中的基类造成的代码冗余的情形。因为 %placeholder 声明的代码，如果不被 @extend 调用的话，不会产生任何代码。来看一个演示：\n\n```scss\n%mt5 {\n  margin-top: 5px;\n}\n%pt5{\n  padding-top: 5px;\n}\n```\n\n这段代码没有被 @extend 调用，他并没有产生任何代码块，只是静静的躺在你的某个 SCSS 文件中。只有通过 @extend 调用才会产生代码：\n\n```scss\n%mt5 {\n  margin-top: 5px;\n}\n%pt5{\n  padding-top: 5px;\n}\n\n.btn {\n  @extend %mt5;\n  @extend %pt5;\n}\n\n.block {\n  @extend %mt5;\n\n  span {\n    @extend %pt5;\n  }\n}\n```\n\n编译出来的CSS\n\n```css\n.btn, .block {\n  margin-top: 5px;\n}\n\n.btn, .block span {\n  padding-top: 5px;\n}\n```\n\n从编译出来的 CSS 代码可以看出，通过 @extend 调用的占位符，编译出来的代码会将相同的代码合并在一起。这也是我们希望看到的效果，也让你的代码变得更为干净。\n\n\n\n## [Sass]混合宏 VS 继承 VS 占位符\n\n初学者都常常纠结于这个问题“什么时候用混合宏，什么时候用继承，什么时候使用占位符？”其实他们各有各的优点与缺点，先来看看他们使用效果：\n\n**a) Sass 中的混合宏使用**\n\n```scss\n@mixin mt($var){\n  margin-top: $var;  \n}\n\n.block {\n  @include mt(5px);\n\n  span {\n    display:block;\n    @include mt(5px);\n  }\n}\n\n.header {\n  color: orange;\n  @include mt(5px);\n\n  span{\n    display:block;\n    @include mt(5px);\n  }\n}\n```\n\n编译出来的 CSS 见右侧结果窗口。\n\n**总结：**编译出来的 CSS 清晰告诉了大家，他不会自动合并相同的样式代码，如果在样式文件中调用同一个混合宏，会产生多个对应的样式代码，造成代码的冗余，这也是 CSSer 无法忍受的一件事情。不过他并不是一无事处，他可以传参数。\n\n**个人建议**：如果你的代码块中涉及到变量，建议使用混合宏来创建相同的代码块。\n\n**b) Sass 中继承**\n\n同样的，将上面代码中的混合宏，使用类名来表示，然后通过继承来调用：\n\n```scss\n.mt{\n  margin-top: 5px;  \n}\n\n.block {\n  @extend .mt;\n\n  span {\n    display:block;\n    @extend .mt;\n  }\n}\n\n.header {\n  color: orange;\n  @extend .mt;\n\n  span{\n    display:block;\n    @extend .mt;\n  }\n}\n```\n\n**总结：**使用继承后，编译出来的 CSS 会将使用继承的代码块合并到一起，通过组合选择器的方式向大家展现，比如 .mt, .block, .block span, .header, .header span。这样编译出来的代码相对于混合宏来说要干净的多，也是 CSSer 期望看到。但是他不能传变量参数。\n\n**个人建议**：如果你的代码块不需要专任何变量参数，而且有一个基类已在文件中存在，那么建议使用 Sass 的继承。\n\n**c) 占位符**\n\n最后来看占位符，将上面代码中的基类 .mt 换成 Sass 的占位符格式：\n\n```scss\n%mt{\n  margin-top: 5px;  \n}\n\n.block {\n  @extend %mt;\n\n  span {\n    display:block;\n    @extend %mt;\n  }\n}\n\n.header {\n  color: orange;\n  @extend %mt;\n\n  span{\n    display:block;\n    @extend %mt;\n  }\n}\n```\n\n**总结：**编译出来的 CSS 代码和使用继承基本上是相同，只是不会在代码中生成占位符 mt 的选择器。那么占位符和继承的主要区别的，“占位符是独立定义，不调用的时候是不会在 CSS 中产生任何代码；继承是首先有一个基类存在，不管调用与不调用，基类的样式都将会出现在编译出来的 CSS 代码中。”\n\n来看一个表格：\n\n[![img](http:\u002F\u002Fimg.mukewang.com\u002F55263aa30001913307940364.jpg)](http:\u002F\u002Fimg.mukewang.com\u002F55263aa30001913307940364.jpg)\n\n## [Sass]插值#{}\n\n使用 CSS 预处理器语言的一个主要原因是想使用 Sass 获得一个更好的结构体系。比如说你想写更干净的、高效的和面向对象的 CSS。Sass 中的插值(Interpolation)就是重要的一部分。让我们看一下下面的例子：\n\n```scss\n$properties: (margin, padding);\n@mixin set-value($side, $value) {\n    @each $prop in $properties {\n        #{$prop}-#{$side}: $value;\n    }\n}\n.login-box {\n    @include set-value(top, 14px);\n}\n```\n\n@each...in...语句会在《Sass进级篇》中 [1-6 @each循环](http:\u002F\u002Fwww.imooc.com\u002Fcode\u002F8811) 中讲解。\n\n它可以让变量和属性工作的很完美，上面的代码编译成 CSS：\n\n```css\n.login-box {\n    margin-top: 14px;\n    padding-top: 14px;\n}\n```\n\n这是 Sass 插值中一个简单的实例。当你想设置属性值的时候你可以使用字符串插入进来。另一个有用的用法是构建一个选择器。可以这样使用：\n\n```scss\n@mixin generate-sizes($class, $small, $medium, $big) {\n    .#{$class}-small { font-size: $small; }\n    .#{$class}-medium { font-size: $medium; }\n    .#{$class}-big { font-size: $big; }\n}\n@include generate-sizes(\\\"header-text\\\", 12px, 20px, 40px);\n```\n\n编译出来的 CSS:\n\n```css\n.header-text-small { font-size: 12px; }\n.header-text-medium { font-size: 20px; }\n.header-text-big { font-size: 40px; }\n```\n\n一旦你发现这一点，你就会想到超级酷的 mixins，用来生成代码或者生成另一个 mixins。然而，这并不完全是可能的。第一个限制，这可能会很删除用于 Sass 变量的插值。\n\n```scss\n$margin-big: 40px;\n$margin-medium: 20px;\n$margin-small: 12px;\n@mixin set-value($size) {\n    margin-top: $margin-#{$size};\n}\n.login-box {\n    @include set-value(big);\n}\n```\n\n上面的 Sass 代码编译出来，你会得到下面的信息：\n\nerror style.scss (Line 5: Undefined variable: “$margin-\\\".)\n\n所以，#{}语法并不是随处可用，你也不能在 mixin 中调用：\n\n```scss\n@mixin updated-status {\n    margin-top: 20px;\n    background: #F00;\n}\n$flag: \\\"status\\\";\n.navigation {\n    @include updated-#{$flag};\n}\n```\n\n上面的代码在编译成 CSS 时同样会报错：\n\nerror style.scss (Line 7: Invalid CSS after \\\"...nclude updated-\\\": expected \\\"}\\\", was \\\"#{$flag};\\\")\n幸运的是，可以使用 @extend 中使用插值。例如：\n\n```scss\n%updated-status {\n    margin-top: 20px;\n    background: #F00;\n}\n.selected-status {\n    font-weight: bold;\n}\n$flag: \\\"status\\\";\n.navigation {\n    @extend %updated-#{$flag};\n    @extend .selected-#{$flag};\n}\n```\n\n\n上面的 Sass 代码是可以运行的，因为他给了我们力量，可以动态的插入 .class 和 %placeholder。当然他们不能接受像 mixin 这样的参数，上面的代码编译出来的 CSS:\n\n```css\n.navigation {\n    margin-top: 20px;\n    background: #F00;\n}\n.selected-status, .navigation {\n    font-weight: bold;\n}\n```\n\n\n在 Sass 的社区正在积极讨论插值的局限性，谁又知道呢，也许我们很快将能够使用这些技术也说不定呢。\n\n\n\n## [Sass]注释\n\n注释对于一名程序员来说，是极其重要，良好的注释能帮助自己或者别人阅读源码。在 Sass 中注释有两种方式，我暂且将其命名为：\n\n1、类似 CSS 的注释方式，使用 ”\u002F* ”开头，结属使用 ”*\u002F ”\n2、类似 JavaScript 的注释方式，使用“\u002F\u002F”\n\n两者区别，前者会在编译出来的 CSS 显示，后者在编译出来的 CSS 中不会显示，来看一个示例：\n\n```scss\n\u002F\u002F定义一个占位符\n\n%mt5 {\n  margin-top: 5px;\n}\n\n\u002F*调用一个占位符*\u002F\n\n.box {\n  @extend %mt5;\n}\n```\n\n编译出来的CSS\n\n```css\n.box {\n  margin-top: 5px;\n}\n\n\u002F*调用一个占位符*\u002F\n```\n\n## [Sass]数据类型\n\n Sass 和 JavaScript 语言类似，也具有自己的数据类型，在 Sass 中包含以下几种数据类型：\n\n-  数字: 如，1、 2、 13、 10px；\n-  字符串：有引号字符串或无引号字符串，如，\\\"foo\\\"、 'bar'、 baz；\n-  颜色：如，blue、 #04a3f9、 rgba(255,0,0,0.5)；\n-  布尔型：如，true、 false；\n-  空值：如，null；\n-  值列表：用空格或者逗号分开，如，1.5em 1em 0 2em 、 Helvetica, Arial, sans-serif。\n\n\nSassScript 也支持其他 CSS 属性值（property value），比如 Unicode 范围，或 !important 声明。然而，Sass 不会特殊对待这些属性值，一律视为无引号字符串 (unquoted strings)。\n\n后两个小节详细讲解字符串和值列表数据类型，其它类型与JavaScript中的用法一致。[Sass]字符串\n\nSassScript 支持 CSS 的两种字符串类型：\n\n- 有引号字符串 (quoted strings)，如 \\\"Lucida Grande\\\" 、'http:\u002F\u002Fsass-lang.com'；\n- 无引号字符串 (unquoted strings)，如 sans-serifbold。\n\n在编译 CSS 文件时不会改变其类型。只有一种情况例外，使用 #{ }插值语句 (interpolation) 时，有引号字符串将被编译为无引号字符串，这样方便了在混合指令 (mixin) 中引用选择器名。\n\n```scss\n@mixin firefox-message($selector) {\n  body.firefox #{$selector}:before {\n    content: \\\"Hi, Firefox users!\\\";\n  }\n}\n@include firefox-message(\\\".header\\\");\n```\n\n编译为：\n\n```css\nbody.firefox .header:before {\n  content: \\\"Hi, Firefox users!\\\"; }\n```\n\n需要注意的是：当 deprecated = property syntax 时 （暂时不理解是怎样的情况），所有的字符串都将被编译为无引号字符串，不论是否使用了引号。\n\n## [Sass]值列表\n\n所谓值列表 (lists) 是指 Sass 如何处理 CSS 中： \n\n```css\nmargin: 10px 15px 0 0\n```\n\n或者： \n\n```scss\nfont-face: Helvetica, Arial, sans-serif\n```\n\n像上面这样通过空格或者逗号分隔的一系列的值。\n\n事实上，独立的值也被视为值列表——只包含一个值的值列表。\n\nSass列表函数（Sass list functions）赋予了值列表更多功能（Sass进级会有讲解）：\n\n1. nth函数（nth function） 可以直接访问值列表中的某一项；\n2. join函数（join function） 可以将多个值列表连结在一起；\n3. append函数（append function） 可以在值列表中添加值； \n4. @each规则（@each rule） 则能够给值列表中的每个项目添加样式。\n\n值列表中可以再包含值列表，比如 1px 2px, 5px 6px 是包含 1px 2px 与 5px 6px 两个值列表的值列表。如果内外两层值列表使用相同的分隔方式，要用圆括号包裹内层，所以也可以写成 (1px 2px) (5px 6px)。当值列表被编译为 CSS 时，Sass 不会添加任何圆括号，因为 CSS 不允许这样做。(1px 2px) (5px 6px)与 1px 2px 5px 6px 在编译后的 CSS 文件中是一样的，但是它们在 Sass 文件中却有不同的意义，前者是包含两个值列表的值列表，而后者是包含四个值的值列表。\n\n可以用 () 表示空的列表，这样不可以直接编译成 CSS，比如编译 font-family: ()时，Sass 将会报错。如果值列表中包含空的值列表或空值，编译时将清除空值，比如 1px 2px () 3px 或 1px 2px null 3px。\n\n## [Sass运算]加法\n\n程序中的运算是常见的一件事情，但在 CSS 中能做运算的，到目前为止仅有 calc() 函数可行。但在 Sass 中，运算只是其基本特性之一。在 Sass 中可以做各种数学计算，在接下来的章节中，主要和大家一起探讨有关于 Sass 中的数学运算。\n\n（一）、加法\n\n加法运算是 Sass 中运算中的一种，在变量或属性中都可以做加法运算。如：\n\n```scss\n.box {\n  width: 20px + 8in;\n}\n```\n\n编译出来的 CSS:\n\n```css\n.box {\n  width: 788px;\n}\n```\n\n但对于携带不同类型的单位时，在 Sass 中计算会报错，如下例所示：\n\n```css\n.box {\n  width: 20px + 1em;\n}\n```\n\n编译的时候，编译器会报错：“Incompatible units: 'em' and ‘px'.”\n\n## [Sass运算]减法\n\nSass 的减法运算和加法运算类似，我们通过一个简单的示例来做阐述：\n\n```scss\n$full-width: 960px;\n$sidebar-width: 200px;\n\n.content {\n  width: $full-width -  $sidebar-width;\n}\n```\n\n编译出来的 CSS 如下：\n\n```css\n.content {\n  width: 760px;\n}\n```\n\n同样的，运算时碰到不同类型的单位时，编译也会报错，如：\n\n```scss\n$full-width: 960px;\n\n.content {\n  width: $full-width -  1em;\n}\n```\n\n编译的时候，编译器报“Incompatible units: 'em' and ‘px’.”错误。\n\n## [Sass运算]乘法\n\nSass 中的乘法运算和前面介绍的加法与减法运算还略有不同。虽然他也能够支持多种单位（比如 em ,px , %），但当一个单位同时声明两个值时会有问题。比如下面的示例：\n\n```scss\n.box {\n  width:10px * 2px;  \n}\n```\n\n编译的时候报“20px*px isn't a valid CSS value.”错误信息。\n\n如果进行乘法运算时，两个值单位相同时，只需要为一个数值提供单位即可。上面的示例可以修改成：\n\n```scss\n.box {\n  width: 10px * 2;\n}\n```\n\n编译出来的 CSS:\n\n```\n.box {\n  width: 20px;\n}\n```\n\nSass 的乘法运算和加法、减法运算一样，在运算中有不同类型的单位时，也将会报错。如下面的示例：\n\n```scss\n.box {\n  width: 20px * 2em;\n}\n```\n\n编译时报“40em*px isn't a valid CSS value.”错误信息。\n\n\n\n## [Sass运算]除法\n\nSass 的乘法运算规则也适用于除法运算。不过除法运算还有一个特殊之处。众所周知“**\u002F**”符号在 CSS 中已做为一种符号使用。因此在 Sass 中做除法运算时，直接使用“\u002F”符号做为除号时，将不会生效，编译时既得不到我们需要的效果，也不会报错。一起先来看一个简单的示例：\n\n```scss\n.box {\n  width: 100px \u002F 2;  \n}\n```\n\n编译出来的 CSS 如下：\n\n```scss\n.box {\n  width: 100px \u002F 2;\n}\n```\n\n这样的结果对于大家来说没有任何意义。要修正这个问题，只需要给运算的外面添加一个小括号**( )**即可：\n\n```scss\n.box {\n  width: (100px \u002F 2);  \n}\n```\n\n编译出来的 CSS 如下：\n\n```scss\n.box {\n  width: 50px;\n}\n```\n\n除了上面情况带有小括号，“\u002F”符号会当作除法运算符之外，如果“\u002F”符号在已有的数学表达式中时，也会被认作除法符号。如下面示例：\n\n```scss\n.box {\n  width: 100px \u002F 2 + 2in;  \n}\n```\n\n编译出来的CSS：\n\n```scss\n.box {\n  width: 242px;\n}\n```\n\n另外，在 Sass 除法运算中，当用变量进行除法运算时，“\u002F”符号也会自动被识别成除法，如下例所示：\n\n```scss\n$width: 1000px;\n$nums: 10;\n\n.item {\n  width: $width \u002F 10;  \n}\n\n.list {\n  width: $width \u002F $nums;\n}\n```\n\n编译出来的CSS:\n\n```scss\n.item {\n  width: 100px;\n}\n\n.list {\n  width: 100px;\n}\n```\n\n综合上述，”\u002F ”符号被当作除法运算符时有以下几种情况：\n\n•   如果数值或它的任意部分是存储在一个变量中或是函数的返回值。\n•   如果数值被圆括号包围。\n•   如果数值是另一个数学表达式的一部分。\n\n如下所示：\n\n```scss\n\u002F\u002FSCSS\np {\n  font: 10px\u002F8px;             \u002F\u002F 纯 CSS，不是除法运算\n  $width: 1000px;\n  width: $width\u002F2;            \u002F\u002F 使用了变量，是除法运算\n  width: round(1.5)\u002F2;        \u002F\u002F 使用了函数，是除法运算\n  height: (500px\u002F2);          \u002F\u002F 使用了圆括号，是除法运算\n  margin-left: 5px + 8px\u002F2px; \u002F\u002F 使用了加（+）号，是除法运算\n}\n```\n\n编译出来的CSS\n\n```css\np {\n  font: 10px\u002F8px;\n  width: 500px;\n  height: 250px;\n  margin-left: 9px;\n }\n```\n\n\nSass 的除法运算还有一个情况。我们先回忆一下，在乘法运算时，如果两个值带有相同单位时，做乘法运算时，出来的结果并不是我们需要的结果。但在除法运算时，如果两个值带有相同的单位值时，除法运算之后会得到一个不带单位的数值。如下所示：\n\n```css\n.box {\n  width: (1000px \u002F 100px);\n}\n```\n\n编译出来的CSS如下：\n\n```css\n.box {\n  width: 10;\n}\n```\n\n## [Sass运算]变量计算\n\n在 Sass 中除了可以使用数值进行运算之外，还可以使用变量进行计算，其实在前面章节的示例中也或多或少的向大家展示了。在 Sass 中使用变量进行计算，这使得 Sass 的数学运算功能变得更加实用。一起来看一个简单的示例：\n\n```scss\n$content-width: 720px;\n$sidebar-width: 220px;\n$gutter: 20px;\n\n.container {\n  width: $content-width + $sidebar-width + $gutter;\n  margin: 0 auto;\n}\n```\n\n编译出来的CSS\n\n```css\n.container {\n  width: 960px;\n  margin: 0 auto;\n}\n```\n\n## [Sass运算]数字运算\n\n在 Sass 运算中数字运算是较为常见的，数字运算包括前面介绍的：加法、减法、乘法和除法等运算。而且还可以通过括号来修改他们的运算先后顺序。和我们数学运算是一样的，一起来看个示例。\n\n```scss\n.box {\n  width: ((220px + 720px) - 11 * 20 ) \u002F 12 ;  \n}\n```\n\n编译出来的 CSS:\n\n```css\n.box {\n  width: 60px;\n}\n```\n\n上面这个简单示例是一个典型的计算 Grid 单列列宽的运算。\n\n## [Sass运算]颜色运算\n\n所有算数运算都支持颜色值，并且是分段运算的。也就是说，红、绿和蓝各颜色分段单独进行运算。如：\n\n```\np {\n  color: #010203 + #040506;\n}\n```\n\n计算公式为 01 + 04 = 05、02 + 05 = 07 和 03 + 06 = 09， 并且被合成为：\n\n如此编译出来的 CSS 为：\n\n```\np {\n  color: #050709;\n}\n```\n\n算数运算也能将数字和颜色值 一起运算，同样也是分段运算的。如：\n\n```\np {\n  color: #010203 * 2;\n}\n```\n\n计算公式为 01 * 2 = 02、02 * 2 = 04 和 03 * 2 = 06， 并且被合成为：\n\n```\np {\n  color: #020406;\n}\n```\n\n \n\n## [Sass运算]字符运算\n\n在 Sass 中可以通过加法符号“+”来对字符串进行连接。例如：\n\n```scss\n$content: \\\"Hello\\\" + \\\"\\\" + \\\"Sass!\\\";\n.box:before {\n  content: \\\" #{$content} \\\";\n}\n```\n\n编译出来的CSS：\n\n```css\n.box:before {\n  content: \\\" Hello Sass! \\\";\n}\n```\n\n除了在变量中做字符连接运算之外，还可以直接通过 +，把字符连接在一起：\n\n```css\ndiv {\n  cursor: e + -resize;\n}\n```\n\n编译出来的CSS:\n\n```css\ndiv {\n  cursor: e-resize;\n}\n```\n\n注意，如果有引号的字符串被添加了一个没有引号的字符串 （也就是，带引号的字符串在 + 符号左侧）， 结果会是一个有引号的字符串。 同样的，如果一个没有引号的字符串被添加了一个有引号的字符串 （没有引号的字符串在 + 符号左侧）， 结果将是一个没有引号的字符串。 例如：\n\n```css\np:before {\n  content: \\\"Foo \\\" + Bar;\n  font-family: sans- + \\\"serif\\\";\n}\n```\n\n编译出来的 CSS：\n\n```css\np:before {\n  content: \\\"Foo Bar\\\";\n  font-family: sans-serif; }\n```","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F1656596640264.jpeg",786,"2022-06-30T13:44:28.000Z","2026-05-25T04:50:03.000Z",{"id":5,"name":6,"slug":7},{"id":295,"name":296,"avatar":289},[393],{"id":108,"name":6,"slug":7},{"id":395,"title":396,"slug":397,"content":398,"excerpt":396,"coverImage":399,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":400,"publishedAt":401,"createdAt":401,"updatedAt":402,"category":403,"author":404,"tags":405},"17a2a766-aadc-4143-8218-215908d91f07","Rem适配方案","rem适配方案","\n\n### 为什么要用 rem\n\n- 相对于 rem 来说，vw 适配能更好的适配移动端，但如果既需要适配移动也需要适配 PC\n\n- vw 的适配在 pc 显示时会显得格外的大，\n\n- rem 就会相对好些，这里只针对一些简单页面解决方案，复杂项目会有专门的一套解决方案\n\n## 一、安装命令\n\n```shell\nnpm i amfe-flexible postcss-pxtorem autoprefixer -S\n```\n\n### 1、amfe-flexible\n\n> 由于`viewport`单位得到众多浏览器的兼容，`lib-flexible`这个过渡方案已经可以放弃使用，不管是现在的版本还是以前的版本，都存有一定的问题。建议大家开始使用`viewport`来替代此方。\n\n#### 在 main.js 中引入即可\n\n```js\nimport 'amfe-flexible\u002Findex.js'\n```\n\n\n\n### 2、pxtorem\n\n> 将项目中css的px转成rem单位，免去计算烦恼\n\n#### 新建.postcssrc.js或者postcss.config 文件，在文件添加：\n\n```js\n\u002F\u002F.postcssrc.js\nmodule.exports = {\n  plugins: {\n    autoprefixer: {\n      overrideBrowserslist: ['Android >= 4.0', 'iOS >= 7'],\n    },\n    'postcss-pxtorem': {\n      rootValue: 75,\n      propList: ['*', '!font-size'],\n      exclude: \u002Fnode_modules|folder_name\u002Fi,\n    },\n  },\n};\n```\n\n\n\n```js\n\u002F\u002Fpostcss.config\nmodule.exports = {\n    plugins: {\n        autoprefixer: {\n            overrideBrowserslist: [\n                \\\"Android 4.1\\\",\n                \\\"iOS 7.1\\\",\n                \\\"Chrome > 31\\\",\n                \\\"ff > 31\\\",\n                \\\"ie >= 8\\\",\n                \\\"last 10 versions\\\", \u002F\u002F 所有主流浏览器最近10版本用\n            ],\n            grid: true,\n        },\n        'postcss-pxtorem': {\n            \u002F\u002F1rem等于100px\n            rootValue: 100,\n            propList: ['*'],\n            unitPrecision: 5,\n            \u002F\u002F忽略 node_modules 目录，否则会导致 Vant 样式无法被编译。\n            exclude: \u002Fnode_modules|folder_name\u002Fi,\n        }\n    }\n}\n```\n\n\n\n### 3、在head中加入\n\n```html\n\u003Cmeta name=\\\"viewport\\\" content=\\\"width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no\\\">\n```\n\n\n\n## 二、完成检查\n\n> 配置完之后，需要重启服务，运行即可看到都是 rem 的单位了\n\n\n\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F1656596564943.png",564,"2022-06-30T13:43:00.000Z","2026-05-25T07:57:14.000Z",{"id":5,"name":6,"slug":7},{"id":295,"name":296,"avatar":289},[406],{"id":108,"name":6,"slug":7},{"id":408,"title":409,"slug":410,"content":411,"excerpt":409,"coverImage":412,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":413,"publishedAt":414,"createdAt":414,"updatedAt":415,"category":416,"author":417,"tags":418},"25a59d46-6b87-42b7-9c1f-046fc16a0cc2","Normalize.css样式初始化","normalize-css样式初始化","\n\n## 为什么要用？\n\n> Normalize.css 只是一个很小的 css 文件，但它在磨人的 HTML 元素样式上提供了跨浏览器的高度一致性。相比于传统的 CSS reset,Normalize.css 是一种现代的、为 HTML5 准备的优质替代方案。总之，Normalize.css 是一种 CSS reset 的替代方案。\n\n## 作用\n\n- 保护有用的浏览器样式而不是去掉他们。\n- 为大部分 HTML 元素提供一般化的样式\n- 修复浏览器自身的 bug 并保证各浏览器的一致性。\n- 优化 css 可用性\n- 用注释和详细的文档来解释代码\n- Normalize 支持包括手机浏览器在内的超多浏览器，同时对 HTML5 元素、排版、列表、嵌入的内容、表单和表哥都进行了一般化。尽管这个项目基于一般化的原则，但我们还是在合适的地方使用了更实用的默认值。\n\n## 引用方法\n\n### 安装命令\n\n```shell\nnpm i normalize.css -S\n```\n\n## 使用方法\n\n#### 在 vue-cli 中 main.js 引入\n\n```js\nimport 'normalize.css\u002Fnormalize.css';\n```\n\n## 常见问题\n\n#### 1、如果引入报错，可能没有安装 css-loader 和 style-loader\n\n```shell\nnpm i css-loader style-loader -D\n```\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F1656596441101.png",664,"2022-06-30T13:38:23.000Z","2026-05-24T20:36:30.000Z",{"id":5,"name":6,"slug":7},{"id":295,"name":296,"avatar":289},[419],{"id":108,"name":6,"slug":7},{"id":421,"title":422,"slug":422,"content":423,"excerpt":424,"coverImage":425,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":426,"publishedAt":427,"createdAt":427,"updatedAt":428,"category":429,"author":430,"tags":431},"f37f7d9e-58e8-4c08-876e-e60656b0df21","安装scss失败","\n\n## 一、错误描述\n\n在 vue 前端项目的开发中，遇到错误 **ERROR in Cannot find module 'node-sass'：**\n\n![img](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fblog\u002Fsass\u002F01.png)\n\n## 二、解决方案\n\n### 1. 如果你用的是 cmd：(建议用 cnpm 安装或者用淘宝源)\n\n```\nnpm i -g cnpm --registry=https:\u002F\u002Fregistry.npm.taobao.org\n```\n\n### 2、node-sass 版本过高导致的，卸载重装低版本:\n\n```\nnpm uninstall node-sass  sass-loader\n```\n\n```\ncnpm i -S node-sass@4.13.1 sass-loader@7.3.1\n```\n\n## 以下是 node-sass 支持的最低和最高版本的快速指南：\n\n| Node版本 | node-sass版本 | 节点模块 | \n| - | - | - | \n| Node 17 | 7.0+ | 102 | \n| Node 16 | 6.0+| 93 | \n| Node 15 | 5.0+, \u003C7.0 | 88 | \n| Node 14 | 4.14+ | 83 | \n| Node 13 | 4.13+, \u003C5.0 |79 | \n| Node 12 | 4.12+ | 72 | \n","ERROR in Cannot find module 'node-sass'","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F02e87d9bad3a8c33672446111828fac6.png",541,"2022-05-30T03:39:49.000Z","2026-05-24T20:40:02.000Z",{"id":5,"name":6,"slug":7},{"id":295,"name":296,"avatar":289},[432],{"id":108,"name":6,"slug":7},{"id":434,"title":435,"slug":436,"content":437,"excerpt":435,"coverImage":438,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":439,"publishedAt":440,"createdAt":440,"updatedAt":441,"category":442,"author":443,"tags":444},"f7726d2d-fdf4-480a-a42c-328555b67487","移动端Viewport布局","移动端viewport布局","\n\n\n\n## 安装命令\n\n\n```shell\n npm i postcss-px-to-viewport -D\n```\n\n\n\n## 项目根目录下添加.postcssrc.js文件\n\n```js\nmodule.exports = {\n  plugins: {\n    autoprefixer: {}, \u002F\u002F 用来给不同的浏览器自动添加相应前缀，如-webkit-，-moz-等等\n    \\\"postcss-px-to-viewport\\\": {\n      unitToConvert: \\\"px\\\", \u002F\u002F 要转化的单位\n      viewportWidth: 750, \u002F\u002F UI设计稿的宽度\n      unitPrecision: 6, \u002F\u002F 转换后的精度，即小数点位数\n      propList: [\\\"*\\\"], \u002F\u002F 指定转换的css属性的单位，*代表全部css属性的单位都进行转换\n      viewportUnit: \\\"vw\\\", \u002F\u002F 指定需要转换成的视窗单位，默认vw\n      fontViewportUnit: \\\"vw\\\", \u002F\u002F 指定字体需要转换成的视窗单位，默认vw\n      selectorBlackList: [\\\"wrap\\\"], \u002F\u002F 指定不转换为视窗单位的类名，\n      minPixelValue: 1, \u002F\u002F 默认值1，小于或等于1px则不进行转换\n      mediaQuery: true, \u002F\u002F 是否在媒体查询的css代码中也进行转换，默认false\n      replace: true, \u002F\u002F 是否转换后直接更换属性值\n      exclude: [\u002Fnode_modules\u002F], \u002F\u002F 设置忽略文件，用正则做目录名匹配\n      landscape: false \u002F\u002F 是否处理横屏情况\n    }\n  }\n};\n```\n\n\n\n\n\n## 关于兼容第三方UI库,改写`.postcssrc.js`文件\n\n```js\nconst path = require('path');\n\nmodule.exports = ({ file }) => {\n  const designWidth =\n    file &&\n    file.dirname &&\n    file.dirname.includes(path.join('node_modules', 'vant'))\n      ? 375\n      : 750;\n\n  return {\n    plugins: {\n      autoprefixer: {},\n      'postcss-px-to-viewport': {\n        unitToConvert: 'px',\n        viewportWidth: designWidth,\n        unitPrecision: 6,\n        propList: ['*'],\n        viewportUnit: 'vw',\n        fontViewportUnit: 'vw',\n        selectorBlackList: [],\n        minPixelValue: 1,\n        mediaQuery: true,\n        exclude: [],\n        landscape: false\n      }\n    }\n  };\n};\n```\n\n\n\n## 常见问题\n\n- 使用path.join('node_modules', 'vant')是因为适应不同的操作系统，在mac下结果为node_modules\u002Fvant，而在windows下结果为node_modules\\vant\n\n- propList: 当有些属性的单位我们不希望转换的时候，可以添加在数组后面，并在前面加上!号，如propList: [\\\"*\\\",\\\"!letter-spacing\\\"],这表示：所有css属性的属性的单位都进行转化，除了letter-spacing的\n- selectorBlackList：转换的黑名单，在黑名单里面的我们可以写入字符串，只要类名包含有这个字符串，就不会被匹配。比如selectorBlackList: ['wrap']`,它表示形如`wrap`,`my-wrap`,`wrapper`这样的类名的单位，都不会被转换\n\n","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002Fb19bdfe4c17851f48a4a9f2ce14b784e.png",624,"2022-05-25T06:42:47.000Z","2026-05-24T10:42:25.000Z",{"id":5,"name":6,"slug":7},{"id":295,"name":296,"avatar":289},[445],{"id":232,"name":233,"slug":233},{"id":447,"title":448,"slug":449,"content":450,"excerpt":451,"coverImage":452,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":453,"publishedAt":454,"createdAt":454,"updatedAt":455,"category":456,"author":457,"tags":458},"769cd959-e1c4-4cb9-8483-8f87af4d0a19","深度作用选择器 >>>、\u002Fdeep\u002F、:deep","深度作用选择器-deep-deep","\n\n## 解决问题\n\n> vue 组件编译后，会将 template 中的每个元素加入 [data-v-xxxx] 属性来确保 style scoped 仅本组件的元素而不会污染全局，但如果你引用了第三方组件,默认只会对组件的最外层（div）加入这个 [data-v-xxxx] 属性，但第二层开始就没有效果了。\n\n\n\n## 解决方法一\n\n### 新建一个没有 scoped 的 style（一个.vue 文件允许多个 style）,不推荐\n\n```css\n\u003Cstyle scoped>\n    .fuck {\n        \u002F\u002F ...\n    }\n\u003C\u002Fstyle>\n\n\n\u003Cstyle>\n    .fuck .weui-cells {\n        \u002F\u002F ...\n    }\n\u003C\u002Fstyle>\n```\n\n\n\n## 解决方法二\n\n### >>> (css 写法)\n\n```css\n\u003Cstyle scoped>\n    #home >>> .el-button {\n        color: red;\n    }\n\u003C\u002Fstyle>\n```\n\n\n\n### \u002Fdeep\u002F (sass\u002Fscss 写法)\n\n> 注意：要是项目报错 就把\u002Fdeep\u002F替换为 ::v-deep\n\n```less\n\u003Cstyle lang=\\\"scss\\\" scoped>\n    #about {\n        \u002Fdeep\u002F .el-button {\n            color: violet;\n            background: darkblue;\n        }\n    }\n\u003C\u002Fstyle>\n```\n\n\n\n## 最新版本推荐写法\n\n```css\n\u003Cstyle lang=\\\"scss\\\" scoped>\n    #about {\n       :deep(.el-button) {\n            color: violet;\n            background: darkblue;\n        }\n    }\n\u003C\u002Fstyle>\n```\n\n","深度作用选择器 >>>、\u002Fdeep\u002F、:deep1","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F1657103861994.png",825,"2022-05-24T01:49:45.000Z","2026-05-24T21:06:08.000Z",{"id":5,"name":6,"slug":7},{"id":295,"name":296,"avatar":289},[459],{"id":108,"name":6,"slug":7},{"id":461,"title":462,"slug":463,"content":464,"excerpt":465,"coverImage":466,"status":287,"isPinned":288,"pinnedAt":289,"viewCount":467,"publishedAt":468,"createdAt":468,"updatedAt":469,"category":470,"author":471,"tags":472},"4e0c437c-b900-40c5-ab9b-7b459f9a4cbd","Flex 入门到精通教程","flex-入门到精通教程","\n\n## 一、Flex 布局是什么？\n\n- Flex 是 Flexible Box 的缩写，意为\\\"弹性布局\\\"，用来为盒状模型提供最大的灵活性。\n\n- 任何一个容器都可以指定为 Flex 布局。\n\n```css\n.box {\n  display: flex;\n}\n```\n\n### 行内元素也可以使用 Flex 布局。\n\n```css\n.box {\n  display: inline-flex;\n}\n```\n\n### Webkit 内核的浏览器，必须加上-webkit 前缀。\n\n```css\n.box {\n  display: -webkit-flex; \u002F* Safari *\u002F\n  display: flex;\n}\n```\n\n## 注意\n\n### 设为 Flex 布局以后，子元素的 float、clear 和 vertical-align 属性将失效。\n\n## 二、基本概念\n\n### 采用 Flex 布局的元素，称为 Flex 容器（flex container），简称\\\"容器\\\"。它的所有子元素自动成为容器成员，称为 Flex 项目（flex item），简称\\\"项目\\\"。\n\n![01](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fblog\u002Fflex\u002Fimages\u002F01.png)\n\n- 容器默认存在两根轴：水平的主轴（main axis）和垂直的交叉轴（cross axis）。主轴的开始位置（与边框的交叉点）叫做 main start，结束位置叫做 main end；交叉轴的开始位置叫做 cross start，结束位置叫做 cross end。\n\n- 项目默认沿主轴排列。单个项目占据的主轴空间叫做 main size，占据的交叉轴空间叫做 cross size。\n\n## 三、容器的属性\n\n### 以下 6 个属性设置在容器上。\n\n- flex-direction\n\n- flex-wrap\n\n- flex-flow\n\n- justify-content\n\n- align-items\n\n- align-content\n\n### 3.1 flex-direction 属性\n\n#### flex-direction 属性决定主轴的方向（即项目的排列方向）。\n\n```css\n.box {\n  flex-direction: row | row-reverse | column | column-reverse;\n}\n```\n\n![01](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fblog\u002Fflex\u002Fimages\u002F02.jpg)\n\n- row（默认值）：主轴为水平方向，起点在左端。\n\n- row-reverse：主轴为水平方向，起点在右端。\n\n- column：主轴为垂直方向，起点在上沿。\n\n- column-reverse：主轴为垂直方向，起点在下沿。\n\n### 3.2 flex-wrap 属性\n\n默认情况下，项目都排在一条线（又称\\\"轴线\\\"）上。flex-wrap 属性定义，如果一条轴线排不下，如何换行。\n\n```css\n.box {\n  flex-wrap: nowrap | wrap | wrap-reverse;\n}\n```\n\n![01](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fblog\u002Fflex\u002Fimages\u002F03.png)\n\n#### （1）nowrap（默认）：不换行。\n\n![01](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fblog\u002Fflex\u002Fimages\u002F04.png)\n\n#### （2）wrap：换行，第一行在上方。\n\n![01](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fblog\u002Fflex\u002Fimages\u002F05.jpg)\n\n#### （3）wrap-reverse：换行，第一行在下方。\n\n![01](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fblog\u002Fflex\u002Fimages\u002F06.jpg)\n\n### 3.3 flex-flow\n\n#### flex-flow 属性是 flex-direction 属性和 flex-wrap 属性的简写形式，默认值为 row nowrap。\n\n```css\n.box {\n  flex-flow: \u003Cflex-direction> || \u003Cflex-wrap>;\n}\n```\n\n### 3.4 justify-content 属性\n\njustify-content 属性定义了项目在主轴上的对齐方式。\n\n```css\n.box {\n  justify-content: flex-start | flex-end | center | space-between | space-around;\n}\n```\n\n![01](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fblog\u002Fflex\u002Fimages\u002F07.png)\n\n- flex-start（默认值）：左对齐\n\n- flex-end：右对齐\n\n- center： 居中\n\n- space-between：两端对齐，项目之间的间隔都相等。\n\n- space-around：每个项目两侧的间隔相等。所以，项目之间的间隔比项目与边框的间隔大一倍。\n\n### 3.5 align-items 属性\n\n#### align-items 属性定义项目在交叉轴上如何对齐。\n\n```css\n.box {\n  align-items: flex-start | flex-end | center | baseline | stretch;\n}\n```\n\n![01](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fblog\u002Fflex\u002Fimages\u002F08.png)\n\n- flex-start：交叉轴的起点对齐。\n\n- flex-end：交叉轴的终点对齐。\n\n- center：交叉轴的中点对齐。\n\n- baseline: 项目的第一行文字的基线对齐。\n\n- stretch（默认值）：如果项目未设置高度或设为 auto，将占满整个容器的高度。\n\n### 3.6 align-content 属性\n\nalign-content 属性定义了多根轴线的对齐方式。如果项目只有一根轴线，该属性不起作用。\n\n```css\n.box {\n  align-content: flex-start | flex-end | center | space-between | space-around |\n    stretch;\n}\n```\n\n![01](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fblog\u002Fflex\u002Fimages\u002F09.png)\n\n- flex-start：与交叉轴的起点对齐。\n\n- flex-end：与交叉轴的终点对齐。\n\n- center：与交叉轴的中点对齐。\n\n- space-between：与交叉轴两端对齐，轴线之间的间隔平均分布。\n\n- space-around：每根轴线两侧的间隔都相等。所以，轴线之间的间隔比轴线与边框的间隔大一倍。\n\n- stretch（默认值）：轴线占满整个交叉轴。\n\n## 四、项目的属性\n\n- order\n\n- flex-grow\n\n- flex-shrink\n\n- flex-basis\n\n- flex\n\n- align-self\n\n### 4.1 order 属性\n\norder 属性定义项目的排列顺序。数值越小，排列越靠前，默认为 0。\n\n```css\n.item {\n  order: \u003Cinteger>;\n}\n```\n\n![01](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fblog\u002Fflex\u002Fimages\u002F10.png)\n\n### 4.2 flex-grow 属性\n\nflex-grow 属性定义项目的放大比例，默认为 0，即如果存在剩余空间，也不放大。\n\n```css\n.item {\n  flex-grow: \u003Cnumber>; \u002F* default 0 *\u002F\n}\n```\n\n![01](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fblog\u002Fflex\u002Fimages\u002F11.png)\n\n如果所有项目的 flex-grow 属性都为 1，则它们将等分剩余空间（如果有的话）。如果一个项目的 flex-grow 属性为 2，其他项目都为 1，则前者占据的剩余空间将比其他项多一倍。\n\n### 4.3 flex-shrink 属性\n\nflex-shrink 属性定义了项目的缩小比例，默认为 1，即如果空间不足，该项目将缩小。\n\n```css\n.item {\n  flex-shrink: \u003Cnumber>; \u002F* default 1 *\u002F\n}\n```\n\n![01](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fblog\u002Fflex\u002Fimages\u002F12.jpg)\n\n如果所有项目的 flex-shrink 属性都为 1，当空间不足时，都将等比例缩小。如果一个项目的 flex-shrink 属性为 0，其他项目都为 1，则空间不足时，前者不缩小。\n\n负值对该属性无效。\n\n### 4.4 flex-basis 属性\n\nflex-basis 属性定义了在分配多余空间之前，项目占据的主轴空间（main size）。浏览器根据这个属性，计算主轴是否有多余空间。它的默认值为 auto，即项目的本来大小。\n\n```css\n.item {\n  flex-basis: \u003Clength> | auto; \u002F* default auto *\u002F\n}\n```\n\n它可以设为跟 width 或 height 属性一样的值（比如 350px），则项目将占据固定空间。\n\n### 4.5 flex 属性\n\nflex 属性是 flex-grow, flex-shrink 和 flex-basis 的简写，默认值为 0 1 auto。后两个属性可选。\n\n```css\n.item {\n  flex: none | [ \u003C \\\"flex-grow\\\" > \u003C \\\"flex-shrink\\\" >? || \u003C \\\"flex-basis\\\" > ];\n}\n```\n\n该属性有两个快捷值：auto (1 1 auto) 和 none (0 0 auto)。\n\n建议优先使用这个属性，而不是单独写三个分离的属性，因为浏览器会推算相关值。\n\n### 4.6 align-self 属性\n\nalign-self 属性允许单个项目有与其他项目不一样的对齐方式，可覆盖 align-items 属性。默认值为 auto，表示继承父元素的 align-items 属性，如果没有父元素，则等同于 stretch。\n\n![01](https:\u002F\u002Fcdn.xiaolong0418.com\u002Fblog\u002Fflex\u002Fimages\u002F13.png)\n\n该属性可能取 6 个值，除了 auto，其他都与 align-items 属性完全一致。\n\n# 实例篇\n\n### 1.1 单项目\n\n#### Flex 布局默认就是首行左对齐\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071301.png)\n\n```css\n.box {\n  display: flex;\n}\n```\n\n#### 设置项目的对齐方式，就能实现居中对齐和右对齐。\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071302.png)\n\n```css\n.box {\n  display: flex;\n  \u002F\u002F居中对齐\n  justify-content: center;\n}\n```\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071303.png)\n\n```css\n.box {\n  display: flex;\n  \u002F\u002F右对齐\n  justify-content: flex-end;\n}\n```\n\n#### 设置交叉轴对齐方式，可以垂直移动主轴。\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071304.png)\n\n```css\n.box {\n  display: flex;\n  align-items: center;\n}\n```\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071305.png)\n\n```css\n.box {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n}\n```\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071306.png)\n\n```css\n.box {\n  display: flex;\n  justify-content: center;\n  align-items: flex-end;\n}\n```\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071307.png)\n\n```css\n.box {\n  display: flex;\n  justify-content: flex-end;\n  align-items: flex-end;\n}\n```\n\n### 1.2 双项目\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071308.png)\n\n```css\n.box {\n  display: flex;\n  justify-content: space-between;\n}\n```\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071309.png)\n\n```css\n.box {\n  display: flex;\n  flex-direction: column;\n  justify-content: space-between;\n}\n```\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071310.png)\n\n```css\n.box {\n  display: flex;\n  flex-direction: column;\n  justify-content: space-between;\n  align-items: center;\n}\n```\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071311.png)\n\n```css\n.box {\n  display: flex;\n  flex-direction: column;\n  justify-content: space-between;\n  align-items: flex-end;\n}\n```\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071312.png)\n\n```css\n.box {\n  display: flex;\n}\n.item:nth-child(2) {\n  align-self: center;\n}\n```\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071313.png)\n\n```css\n.box {\n  display: flex;\n  justify-content: space-between;\n}\n.item:nth-child(2) {\n  align-self: flex-end;\n}\n```\n\n### 1.3 三项目\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071314.png)\n\n```css\n.box {\n  display: flex;\n}\n.item:nth-child(2) {\n  align-self: center;\n}\n.item:nth-child(3) {\n  align-self: flex-end;\n}\n```\n\n### 1.4 四项目\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071315.png)\n\n```css\n.box {\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: flex-end;\n  align-content: space-between;\n}\n```\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071316.png)\n\n```html\n\u003Cdiv class=\\\"box\\\">\n  \u003Cdiv class=\\\"column\\\">\n    \u003Cspan class=\\\"item\\\">\u003C\u002Fspan>\n    \u003Cspan class=\\\"item\\\">\u003C\u002Fspan>\n  \u003C\u002Fdiv>\n  \u003Cdiv class=\\\"column\\\">\n    \u003Cspan class=\\\"item\\\">\u003C\u002Fspan>\n    \u003Cspan class=\\\"item\\\">\u003C\u002Fspan>\n  \u003C\u002Fdiv>\n\u003C\u002Fdiv>\n```\n\n```css\n.box {\n  display: flex;\n  flex-wrap: wrap;\n  align-content: space-between;\n}\n.column {\n  flex-basis: 100%;\n  display: flex;\n  justify-content: space-between;\n}\n```\n\n### 1.5 六项目\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071317.png)\n\n```css\n.box {\n  display: flex;\n  flex-wrap: wrap;\n  align-content: space-between;\n}\n```\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071318.png)\n\n```css\n.box {\n  display: flex;\n  flex-direction: column;\n  flex-wrap: wrap;\n  align-content: space-between;\n}\n```\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071319.png)\n\nHTML 代码如下。\n\n```html\n\u003Cdiv class=\\\"box\\\">\n  \u003Cdiv class=\\\"row\\\">\n    \u003Cspan class=\\\"item\\\">\u003C\u002Fspan>\n    \u003Cspan class=\\\"item\\\">\u003C\u002Fspan>\n    \u003Cspan class=\\\"item\\\">\u003C\u002Fspan>\n  \u003C\u002Fdiv>\n  \u003Cdiv class=\\\"row\\\">\n    \u003Cspan class=\\\"item\\\">\u003C\u002Fspan>\n  \u003C\u002Fdiv>\n  \u003Cdiv class=\\\"row\\\">\n    \u003Cspan class=\\\"item\\\">\u003C\u002Fspan>\n    \u003Cspan class=\\\"item\\\">\u003C\u002Fspan>\n  \u003C\u002Fdiv>\n\u003C\u002Fdiv>\n```\n\nCSS 代码如下。\n\n```css\n.box {\n  display: flex;\n  flex-wrap: wrap;\n}\n.row {\n  flex-basis: 100%;\n  display: flex;\n}\n.row:nth-child(2) {\n  justify-content: center;\n}\n.row:nth-child(3) {\n  justify-content: space-between;\n}\n```\n\n### 1.6 九项目\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071320.png)\n\n```css\n.box {\n  display: flex;\n  flex-wrap: wrap;\n}\n```\n\n## 二、网格布局\n\n### 2.1 基本网格布局\n\n最简单的网格布局，就是平均分布。在容器里面平均分配空间，跟上面的骰子布局很像，但是需要设置项目的自动缩放。\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071321.png)\n\nHTML 代码如下。\n\n> ```html\n> \u003Cdiv class=\\\"Grid\\\">\n>   \u003Cdiv class=\\\"Grid-cell\\\">...\u003C\u002Fdiv>\n>   \u003Cdiv class=\\\"Grid-cell\\\">...\u003C\u002Fdiv>\n>   \u003Cdiv class=\\\"Grid-cell\\\">...\u003C\u002Fdiv>\n> \u003C\u002Fdiv>\n> ```\n\nCSS 代码如下。\n\n> ```css\n> .Grid {\n>   display: flex;\n> }\n> .Grid-cell {\n>   flex: 1;\n> }\n> ```\n\n### 2.2 百分比布局\n\n某个网格的宽度为固定的百分比，其余网格平均分配剩余的空间。\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071322.png)\n\nHTML 代码如下。\n\n```html\n\u003Cdiv class=\\\"Grid\\\">\n  \u003Cdiv class=\\\"Grid-cell u-1of4\\\">...\u003C\u002Fdiv>\n  \u003Cdiv class=\\\"Grid-cell\\\">...\u003C\u002Fdiv>\n  \u003Cdiv class=\\\"Grid-cell u-1of3\\\">...\u003C\u002Fdiv>\n\u003C\u002Fdiv>\n```\n\n```css\n.Grid {\n  display: flex;\n}\n.Grid-cell {\n  flex: 1;\n}\n.Grid-cell.u-full {\n  flex: 0 0 100%;\n}\n.Grid-cell.u-1of2 {\n  flex: 0 0 50%;\n}\n.Grid-cell.u-1of3 {\n  flex: 0 0 33.3333%;\n}\n.Grid-cell.u-1of4 {\n  flex: 0 0 25%;\n}\n```\n\n## 三、圣杯布局\n\n[圣杯布局](\u003Chttps:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FHoly_Grail_(web_design)>)（Holy Grail Layout）指的是一种最常见的网站布局。页面从上到下，分成三个部分：头部（header），躯干（body），尾部（footer）。其中躯干又水平分成三栏，从左到右为：导航、主栏、副栏。\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071323.png)\n\n```html\n\u003Cbody class=\\\"HolyGrail\\\">\n  \u003Cheader>...\u003C\u002Fheader>\n  \u003Cdiv class=\\\"HolyGrail-body\\\">\n    \u003Cmain class=\\\"HolyGrail-content\\\">...\u003C\u002Fmain>\n    \u003Cnav class=\\\"HolyGrail-nav\\\">...\u003C\u002Fnav>\n    \u003Caside class=\\\"HolyGrail-ads\\\">...\u003C\u002Faside>\n  \u003C\u002Fdiv>\n  \u003Cfooter>...\u003C\u002Ffooter>\n\u003C\u002Fbody>\n```\n\n```css\n.HolyGrail {\n  display: flex;\n  min-height: 100vh;\n  flex-direction: column;\n}\nheader,\nfooter {\n  flex: 1;\n}\n.HolyGrail-body {\n  display: flex;\n  flex: 1;\n}\n.HolyGrail-content {\n  flex: 1;\n}\n.HolyGrail-nav,\n.HolyGrail-ads {\n  \u002F* 两个边栏的宽度设为12em *\u002F\n  flex: 0 0 12em;\n}\n.HolyGrail-nav {\n  \u002F* 导航放到最左边 *\u002F\n  order: -1;\n}\n```\n\n如果是小屏幕，躯干的三栏自动变为垂直叠加。\n\n```css\n@media (max-width: 768px) {\n  .HolyGrail-body {\n    flex-direction: column;\n    flex: 1;\n  }\n  .HolyGrail-nav,\n  .HolyGrail-ads,\n  .HolyGrail-content {\n    flex: auto;\n  }\n}\n```\n\n## 四、输入框的布局\n\n我们常常需要在输入框的前方添加提示，后方添加按钮。\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071324.png)\n\n```html\n\u003Cdiv class=\\\"InputAddOn\\\">\n  \u003Cspan class=\\\"InputAddOn-item\\\">...\u003C\u002Fspan>\n  \u003Cinput class=\\\"InputAddOn-field\\\" \u002F>\n  \u003Cbutton class=\\\"InputAddOn-item\\\">...\u003C\u002Fbutton>\n\u003C\u002Fdiv>\n```\n\n```css\n.InputAddOn {\n  display: flex;\n}\n.InputAddOn-field {\n  flex: 1;\n}\n```\n\n## 五、悬挂式布局\n\n有时，主栏的左侧或右侧，需要添加一个图片栏。\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071325.png)\n\n```html\n\u003Cdiv class=\\\"Media\\\">\n  \u003Cimg class=\\\"Media-figure\\\" src=\\\"\\\" alt=\\\"\\\" \u002F>\n  \u003Cp class=\\\"Media-body\\\">...\u003C\u002Fp>\n\u003C\u002Fdiv>\n```\n\n```css\n.Media {\n  display: flex;\n  align-items: flex-start;\n}\n.Media-figure {\n  margin-right: 1em;\n}\n.Media-body {\n  flex: 1;\n}\n```\n\n### 六、固定的底栏\n\n有时，页面内容太少，无法占满一屏的高度，底栏就会抬高到页面的中间。这时可以采用 Flex 布局，让底栏总是出现在页面的底部。\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071326.png)\n\n```html\n\u003Cbody class=\\\"Site\\\">\n  \u003Cheader>...\u003C\u002Fheader>\n  \u003Cmain class=\\\"Site-content\\\">...\u003C\u002Fmain>\n  \u003Cfooter>...\u003C\u002Ffooter>\n\u003C\u002Fbody>\n```\n\n```css\n.Site {\n  display: flex;\n  min-height: 100vh;\n  flex-direction: column;\n}\n.Site-content {\n  flex: 1;\n}\n```\n\n### 七，流式布局\n\n每行的项目数固定，会自动分行。\n\n![img](http:\u002F\u002Fwww.ruanyifeng.com\u002Fblogimg\u002Fasset\u002F2015\u002Fbg2015071330.png)\n\n```css\n.parent {\n  width: 200px;\n  height: 150px;\n  background-color: black;\n  display: flex;\n  flex-flow: row wrap;\n  align-content: flex-start;\n}\n.child {\n  box-sizing: border-box;\n  background-color: white;\n  flex: 0 0 25%;\n  height: 50px;\n  border: 1px solid red;\n}\n```\n","222","https:\u002F\u002Fcdn.xiaolong0418.com\u002Fmyblog\u002Fimages\u002F42ea524076938b661748cc9367f5a69b.png",709,"2022-05-23T07:07:43.000Z","2026-05-24T20:56:19.000Z",{"id":5,"name":6,"slug":7},{"id":295,"name":296,"avatar":289},[473],{"id":108,"name":6,"slug":7}]