如何實現全屏遮罩(附Vue.extend和el-message源碼學習)

[Vue]如何實現全屏遮罩(附Vue.extend和el-message源碼學習)

在作我的項目的時候須要作一個相似於電子相冊瀏覽的控件,實現過程當中首先要實現全局遮罩,結合本身的思路並閱讀了(餓了麼)element-ui中el-message的實現,來總結一下Vue中比較好的一種全局遮罩的實現方式。html

調用遮罩的方式

通常由兩種寫法:vue

1.(相似el-dialog的一種寫法)

在html文件中寫好結構,控制元素的顯示與隱藏的實現遮罩。git

<div class="container">
    <div class="mask">。。。。。。。。。。</div>
</div>
<style>
    .mask {
        position: fixed;
        left: 0;
        right: 0;
        top: 0;
        bottom: 0;
        background: rgba(0, 0, 0, .5);
        z-index: 999;
    }
</style>

好比在上述結構中,經過控制mask的顯示與隱藏來實現全局遮罩,mask的樣式如上,經過position:fixed定位脫離文檔流來實現佔據全屏空間。能夠適用於大部分場景。
可是,position:fixed有他本身的特性
position:fixed:
不爲元素預留空間,而是經過指定元素相對於屏幕視口(viewport)的位置來指定元素位置。元素的位置在屏幕滾動時不會改變。打印時,元素會出如今的每頁的固定位置。fixed 屬性會建立新的層疊上下文。當元素祖先的 transform 屬性非 none 時,容器由視口改成該祖先。(引自MDN)
也就是說,若是父元素樣式中具備transform時,不會作到全局遮罩哦。
在此我製做了2個demo.
正常全局遮罩
非正常全局遮罩(父元素container有transform)(chrome,firefox,edge打開)
非正常全局遮罩樣式:es6

<div class="container">
    <div class="mask"><h1>非正常全局遮罩</h1></div>
</div>
<style>
    .container {
        height: 111px;
        transform: translateX(1px);
    }

    .mask {
        position: fixed;
        left: 0;
        right: 0;
        top: 0;
        bottom: 0;
        background: rgba(0, 0, 0, .5);
        z-index: 999;
        color: white;
    }
</style>

2. 動態添加(document.body.appendChild)

this.$message.success('登陸成功')

第二種就像原生的alert同樣,如el-meaasge,經過命令的方式來調用。(雖然提示信息未全局遮罩,添加思路相同)github

document.body.appendChild(mask);

在document.body動態添加,以此來利用position:fixed來實現,通常對body咱們不會加上transform這種屬性,所以避免了上述問題,因此適用性更廣一些,element-ui也是這種思路。chrome

Vue如何優雅的動態添加

這裏咱們須要用到vue的實例化,首先咱們來看element-ui的思路,貼一段源碼element-ui

let MessageConstructor = Vue.extend(Main);//使用基礎 Vue 構造器,建立一個「子類」。

let instance;//當前message
let instances = [];//正在顯示的全部message
let seed = 1;//至關於id,用於標記message

const Message = function (options) {
  if (Vue.prototype.$isServer) return;//當前 Vue 實例是否運行於服務器。
  options = options || {};
  if (typeof options === 'string') {
    options = {
      message: options
    };
  }
  let userOnClose = options.onClose;
  let id = 'message_' + seed++;

  // 簡單包裝一下
  options.onClose = function () {
    Message.close(id, userOnClose);//關閉第id個message,並調用回調
  };
  instance = new MessageConstructor({
    data: options
  });
  instance.id = id;
  if (isVNode(instance.message)) {
    instance.$slots.default = [instance.message];//html模板 TODO
    instance.message = null;
  }
  instance.vm = instance.$mount();
  instance.vm.visible = true;
  document.body.appendChild(instance.vm.$el);
  instance.dom = instance.vm.$el;
  instance.dom.style.zIndex = PopupManager.nextZIndex();//統一管理 z-index
  instances.push(instance);//加入本實例
  return instance.vm;
};

['success', 'warning', 'info', 'error'].forEach(type => {
  Message[type] = options => {
    if (typeof options === 'string') {
      options = {
        message: options
      };
    }
    options.type = type;
    return Message(options);
  };
});

Message.close = function (id, userOnClose) {
  for (let i = 0, len = instances.length; i < len; i++) {
    if (id === instances[i].id) {
      if (typeof userOnClose === 'function') {
        userOnClose(instances[i]);
      }
      instances.splice(i, 1);//從正在顯示的全部message中移除id這個message
      break;
    }
  }
};

Message.closeAll = function () {
  for (let i = instances.length - 1; i >= 0; i--) {
    instances[i].close();// 關閉全部message
  }
};

export default Message;

閱讀代碼咱們能夠知道,經過Vue.extend咱們獲取到一個子類的構造器。
在初始化並mount()(掛載)以後,將該message動態的加載document.body()中。api

this.$el.parentNode.removeChild(this.$el);//移除dom節點

注意,message關閉的時候會把咱們添加的el移除哦。
若要了解main.vue,完整的註釋代碼見此處緩存

Vue.extend

這裏再學習一些Vue.extend的知識。主要是我在染陌大神的註釋的基礎上加了一點點註釋,見染陌大神github服務器

export function initExtend (Vue: GlobalAPI) {
  /**
   * Each instance constructor, including Vue, has a unique
   * cid. This enables us to create wrapped "child
   * constructors" for prototypal inheritance and cache them.
   */
  /*
    每一個構造函數實例(包括Vue自己)都會有一個惟一的cid
    它爲咱們可以創造繼承建立自構造函數並進行緩存創造了可能
  */
  Vue.cid = 0
  let cid = 1

  /**
   * Class inheritance
   */
   /*
   使用基礎 Vue 構造器,建立一個「子類」。
   其實就是擴展了基礎構造器,造成了一個可複用的有指定父類組件功能的子構造器。
   參數是一個包含組件option的對象。  https://cn.vuejs.org/v2/api/#Vue-extend-options
   */
  Vue.extend = function (extendOptions: Object): Function {
    extendOptions = extendOptions || {}//繼承
    /*父類的構造*/
    const Super = this
    /*父類的cid*/
    const SuperId = Super.cid
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    /*若是構造函數中已經存在了該cid,則表明已經extend過了,直接返回*/
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }

    //組件name
    const name = extendOptions.name || Super.options.name
    if (process.env.NODE_ENV !== 'production') {
      /*name只能包含字母與連字符*/
      if (!/^[a-zA-Z][\w-]*$/.test(name)) {
        warn(
          'Invalid component name: "' + name + '". Component names ' +
          'can only contain alphanumeric characters and the hyphen, ' +
          'and must start with a letter.'
        )
      }
    }

    /*
      Sub構造函數其實就一個_init方法,這跟Vue的構造方法是一致的,在_init中處理各類數據初始化、生命週期等。
      由於Sub做爲一個Vue的擴展構造器,因此基礎的功能仍是須要保持一致,跟Vue構造器同樣在構造函數中初始化_init。
    */
    const Sub = function VueComponent (options) {
      this._init(options)//和vue初始化相同,再次再也不詳述
    }
    /*繼承父類*///好比_init就今後繼承而來
    Sub.prototype = Object.create(Super.prototype)
    /*構造函數*/
    Sub.prototype.constructor = Sub
    /*建立一個新的cid*/
    Sub.cid = cid++
    /*將父組件的option與子組件的合併到一塊兒(Vue有一個cid爲0的基類,即Vue自己,會將一些默認初始化的option何入)*/
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    /*es6語法,super爲父類構造*/
    Sub['super'] = Super

    // For props and computed properties, we define the proxy getters on
    // the Vue instances at extension time, on the extended prototype. This
    // avoids Object.defineProperty calls for each instance created.
    /*在擴展時,咱們將計算屬性以及props經過代理綁定在Vue實例上(也就是vm),這也避免了Object.defineProperty被每個實例調用*/
    if (Sub.options.props) {
      /*初始化props,將option中的_props代理到vm上*/
      initProps(Sub)
    }
    if (Sub.options.computed) {
      /*處理計算屬性,給計算屬性設置defineProperty並綁定在vm上*/
      initComputed(Sub)
    }

    // allow further extension/mixin/plugin usage
    /*加入extend、mixin以及use方法,容許未來繼續爲該組件提供擴展、混合或者插件*/
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use

    // create asset registers, so extended classes
    // can have their private assets too.
    /*使得Sub也會擁有父類的私有選項(directives、filters、components)*/
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    // enable recursive self-lookup
    /*把組件自身也加入components中,爲遞歸自身提供可能(遞歸組件也會查找components是否存在當前組件,也就是自身)*/
    if (name) {
      Sub.options.components[name] = Sub
    }

    // keep a reference to the super options at extension time.
    // later at instantiation we can check if Super's options have
    // been updated.
    /*保存一個父類的options,此後咱們能夠用來檢測父類的options是否已經被更新*///_init時檢查
    Sub.superOptions = Super.options
    /*extendOptions存儲起來*/
    Sub.extendOptions = extendOptions
    /*保存一份option,extend的做用是將Sub.options中的全部屬性放入{}中*/
    Sub.sealedOptions = extend({}, Sub.options)

    // cache constructor
    /*緩存構造函數(用cid),防止重複extend*/
    cachedCtors[SuperId] = Sub
    return Sub
  }
}

/*初始化props,將option中的_props代理到vm上*/
function initProps (Comp) {
  const props = Comp.options.props
  for (const key in props) {
    proxy(Comp.prototype, `_props`, key)
  }
}

/*處理計算屬性,給計算屬性設置defineProperty並綁定在vm上*/
function initComputed (Comp) {
  const computed = Comp.options.computed
  for (const key in computed) {
    defineComputed(Comp.prototype, key, computed[key])
  }
}

染陌大神註釋很詳盡,Vue.extend主要是繼承父類的各類屬性來產生一個子類構造器.詳細請看源碼。

demo

最後展現一下demo吧,比較簡陋,隨意看一下就好。
lightbox在線預覽

相關文章
相關標籤/搜索