Vueコンポーネント:Bootstrap Modalにスケーリング機能を追加する


需要
Bootstrapは現在最も流行しているフロントエンドの基礎フレームワークの一つであるべきだ.アーキテクチャの優位性のため、侵入性が低く、さまざまな方法で他のプロジェクトに統合できます.わが工場の各種製品には,みなその活躍の場がある.
先日、ボスはModal(弾窓)が彼のスクリーンに小さすぎて、5 Kディスプレイを浪費していると愚痴をこぼした.
見てみましたが、Bootstrapのデザインで1200 pxを超えるとXLでも.modal-lgの幅が1140 pxに固定されています.実はBootstrapがこのように設計されているのも理にかなっている.人の目がフォーカスして幅が限られているので、弾窓が広すぎると、内容が一目で見えなくなり、よくないからだ.しかし、私の工場の製品では、弾窓が火炎図を見せなければならないので、広くてもメリットがあります.
技術案
では、総合的に見ると、最も適切な方法は、Modalにドラッグ機能を追加します.ユーザーは十分大きいと思っています.そうします.ユーザーは少し大きく見たいので、自分で大きくして、それから私はユーザーの選択を記録して、多重化します.
私の『`resize`とMutationObserverでDOMをスケーリングしサイズを記録する』の学友を見たことがあって、resizeのこのCSSの属性を知っているべきで、それを使ってとても便利な元素にスケーリングの機能を追加することができます.caniuseの普及度を参考に、ほとんどの新バージョンのブラウザがサポートされており、安心して利用できます.
使用するときは、次の点に注意してください.
まず、要素をスケールすると同時に、サブ要素、親要素にも影響します.静的ドキュメント・ストリームでは、ブロック・レベルの要素の幅は、親要素content-boxの100%がデフォルトであり、高さは子要素によって決まるからです.したがって、1つのブロックレベルの要素のスケールでは、親要素(幅を制限する場合)よりも広く、子要素よりも低くすることはできません.
次に、ドラッグハンドルの表示優先度が低く、サブエレメントが何も埋め込まれていなくても、エレメントに覆われます.すなわち、paddingのスケールを追加するには、resizeの要素が必要である.
実施形態
要するに、この属性をどの要素に加えるかは、こだわりがあります.具体的には、Bootstrap Modalは、resizeのプロパティを追加するのに最も適しているのはmodal-contentです.1remの内側の余白があるからです.
ただし、幅を制限するのは親要素、すなわちmodal-dialogであり、応答式であり、ディスプレイの幅に応じて最大幅を設定します.max-widthmodal-contentの最大幅を変更しないと、予想される効果を達成できません.しかしwidthに変更することはできません.そうすると、弾窓は弾性を失い、解像度が低いときに表現が悪くなります.
だからmax-widthで文章を書きます.それを直接取り除くとmodal-dialogの幅が100%になり、弾窓効果が失われるので、そうすることもできません.最終的に、私の案は:
  • ウィンドウが完全に展開すると、幅の高さを取得し、modal-contentstyle
  • に書き込む.
  • はその後、modal-dialogmax-widthを除去するが、このとき、サブエレメントmodal-contentはすでに定幅されているため、依然としてウィンドウスタイル
  • である.
  • は、modal-contentの幅をMutationObserverでモニタし、
  • をグローバルに使用するためにlocalStorageに保存する.
    完全なコード表示
    わが工場の製品はVueに基づいて開発されたので、論理はVueコンポーネントで実現した.
    効果のデモ
    Codepenに表示しやすいように、一部修正があります.
    https://codepen.io/meathill/p...
    コードと解釈
    
    
    
    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;
        }
      },
    };
    
    
    

    に注意
    ブラウザの非同期ロードメカニズムのため、modalが開いてレイアウトが完了した後、高さと幅がコンテンツによって押し開かれて記録が不正になったり、コンテンツが異常に隠されたりする可能性があります.読者自身で何とかして、練習問題にしてください.
    まとめ
    今回のコンポーネント開発は私の理想的なコンポーネントモデルによく合っています.
  • ブラウザオリジナルメカニズム
  • を活用
  • できるだけ少ないJS
  • に合わせる
  • 必要な機能は何でも追加し、大きくなくても
  • である.
    MVVMフレームワークの連携により,このようなスキームは容易に実現できる.一方、各プロジェクトには独自の使用シーンがあり、長期にわたって特定のシーンで作業することで、このシーンに適したコンポーネントライブラリを徐々に整理し、プロジェクトの開発効率を向上させることができます.これこそコンポーネント化の正道だと思います.
    私のブログに同期: