Bootstrap 應該仍是目前最流行的前端基礎框架之一。由於架構方面的優點,它的侵入性很低,能夠以各類方式集成到其它項目當中。在我廠的各類產品裏,都有它的用武之地。css
前兩天,老闆抱怨,說 Modal(彈窗)在他的屏幕上過小,浪費他的 5K 顯示器。html
我看了一下,按照 Bootstrap 的設計,超過 1200px 就算是 XL,此時.modal-lg
的寬度固定在 1140px。其實 Bootstrap 這麼設計也有它的道理,由於人眼聚焦後寬度有限,若是彈窗太寬的話,內容一眼看不全,也很差。不過在我廠的產品裏,彈窗要呈現火焰圖,因此寬一些也有好處。前端
那麼,綜合來看,最合適的作法,就給 Modal 添加一個拖拽的功能:用戶以爲夠大了,就這麼着;用戶想看大一點,就本身拉大一些,而後我記錄用戶的選擇,以便複用。vue
看過我《用 `resize` 和 MutationObserver 實現縮放 DOM 並記錄尺寸》的同窗,應該知道resize
這個 CSS 屬性,使用它能夠很方便的給元素添加縮放功能。參考caniuse上面的普及度,大部分新版本的瀏覽器都已經支持,能夠放心使用。bootstrap
使用它的時候要注意兩點:瀏覽器
首先,咱們在縮放元素的同時,也會對它的子元素、父元素同時形成影響。由於在靜態文檔流當中,塊級元素的寬度默認是父元素 content-box
的 100%,而高度由子元素決定。因此,對一個塊級元素的縮放,不可能寬過它的父元素(若是限制了寬度的話),也不可能矮於它的子元素。架構
其次,拖拽手柄的顯示優先級很低,會被子元素蓋住,哪怕子元素沒有填充任何內容。換言之,必定要有 padding
的元素才適合添加 resize
縮放。框架
總而言之,把這個屬性加在哪一個元素上面,頗有講究。具體到本次需求,Bootstrap Modal,最合適添加 resize
屬性的的是 modal-content
,由於它有 1rem
的內邊距。異步
可是限制寬度的是父元素,也就是 modal-dialog
,它是響應式的,會根據顯示器的寬度設置一個最大寬度。若是不修改它的 max-width
,modal-content
的最大寬度就沒法超過它,達不到預期效果。可是也不能改爲 width
,這樣的話,彈窗會失去彈性,分辨率低的時候表現很差。async
因此仍是要在 max-width
上作文章。若是直接去掉它,modal-dialog
的寬度就會是 100%,失去彈窗效果,因此也不能這樣作。最終,個人方案是:
modal-content
的 style
modal-dialog
的 max-width
,此時,由於子元素 modal-content
已經定寬,因此仍然是窗口樣式modal-content
的寬高,保存到 localStorage,以便在全局使用我廠的產品基於 Vue 開發,因此邏輯用 Vue 組件實現。
爲方便在 Codepen 裏呈現,有部分修改。
https://codepen.io/meathill/p...
<template lang="pug"> .modal.simple-modal( :style="{display: visibility ? 'block' : 'none'}", @click="doCloseFromBackdrop", ) .modal-dialog.modal-dialog-scrollable( ref="dialog", :class="dialogClass", ) .modal-content(ref="content", :) .modal-header.p-2 slot(name="header") h4 {{title}} span.close(v-if="canClose", @click="doClose") × .modal-body slot(name="body") </template> <script> import debounce from 'lodash/debounce'; const RESIZED_SIZE = 'resized_width_key'; let sharedSize = null; export default { props: { canClose: { type: Boolean, default: true, }, size: { type: String, default: null, validator: function(value) { return ['sm', 'lg', 'xl'].indexOf(value) !== -1; }, }, resizable: { type: Boolean, default: false, }, backdrop: { type: Boolean, default: true, }, title: { type: String, default: 'Modal title', }, }, computed: { dialogClass() { const classes = []; if (this.size) { classes.push(`modal-${this.size}`); } if (this.resizable) { classes.push('modal-dialog-resizable'); } if (this.resizedSize) { classes.push('ready'); } return classes.join(' '); }, contentStyle() { if (!this.resizable || !this.resizedSize) { return null; } const {width, height} = this.resizedSize; return { width: `${width}px`, height: `${height}px`, }; }, }, data() { return { visibility: false, resizedSize: null, }; }, methods: { async doOpen() { this.visibility = true; this.$emit('open'); if (this.resizable) { // 經過 debounce 節流能夠下降函數運行次數 const onResize = debounce(this.onEditorResize, 100); // 這裏用 MutationObserver 監測元素尺寸 const observer = this.observer = new MutationObserver(onResize); observer.observe(this.$refs.content, { attributes: true, }); if (sharedSize) { this.resizedSize = sharedSize; } // 第一次運行的時候,記錄 Modal 尺寸,避免太大 if (!this.resizedSize) { await this.$nextTick(); // 按照張鑫旭的說法,這裏用 `clientWidth` 有性能問題,不過暫時尚未更好的解決方案 // https://weibo.com/1263362863/ImwIOmamC const width = this.$refs.dialog.clientWidth; this.resizedSize = {width}; // 這裏產生紀錄以後,上面的 computed 屬性就會把 `max-width` 去掉了 } } }, doClose() { this.visibility = false; this.$emit('close'); }, doCloseFromBackdrop({target}) { if (!this.backdrop || target !== this.$el) { return; } this.doClose(); }, onEditorResize([{target}]) { const width = target.clientWidth; const height = target.clientHeight; if (width < 320 || height < 160) { return; } sharedSize = {width, height}; localStorage.setItem(RESIZED_SIZE, JSON.stringify(sharedSize)); }, }, beforeMount() { const size = localStorage.getItem(RESIZED_SIZE); if (size) { this.resizedSize = JSON.parse(size); } }, beforeDestroy() { if (this.observer) { this.observer.disconnect(); this.observer = null; } }, }; </script> <style lang="stylus"> .simple-modal background-color: rgba(0, 0, 0, 0.5) .modal-content padding 1em .close cursor pointer .modal-dialog-resizable &.ready max-width unset !important .modal-content resize both margin 0 auto </style>
由於瀏覽器的異步加載機制,有可能在 modal 打開並完成佈局後,高度和寬度被內容撐開致使記錄不許,或者內容被異常遮蓋。請讀者本身想辦法處理,就當練習題吧。
本次組件開發很是符合我理想的組件模式:
在 MVVM 框架的配合下,這樣的方案很容易實現。另外一方面,每一個項目都有獨特的使用場景,經過長期在特定場景下工做,咱們能夠逐步整理出適用於這個場景的組件庫,不斷改進該項目的開發效率。我認爲這纔是組件化的正道。
同步發於個人博客: