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


在做個人項目的時候需要做一個類似於電子相冊瀏覽的控件,實現過程中首先要實現全局遮罩,結合自己的思路並閱讀了(餓了么)element-ui中el-message的實現,來總結一下Vue中比較好的一種全局遮罩的實現方式。

調用遮罩的方式

一般由兩種寫法:

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

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

 

.mask {
position: fixed;

left: 0;

right: 0;

top: 0;

bottom: 0;

background: rgba(0, 0, 0, .5);

z-index: 999;

}

 

比如在上述結構中,通過控制mask的顯示與隱藏來實現全局遮罩,mask的樣式如上,通過position:fixed定位脫離文檔流來實現占據全屏空間。可以適用於大部分場景。

但是,position:fixed有他自己的特性

position:fixed:

不為元素預留空間,而是通過指定元素相對於屏幕視口(viewport)的位置來指定元素位置。元素的位置在屏幕滾動時不會改變。打印時,元素會出現在的每頁的固定位置。fixed 屬性會創建新的層疊上下文。當元素祖先的 transform 屬性非 none 時,容器由視口改為該祖先。(引自MDN)

也就是說,如果父元素樣式中具有transform時,不會做到全局遮罩哦。

在此我制作了2個demo.

正常全局遮罩

非正常全局遮罩(父元素container有transform)(chrome,firefox,edge打開)

非正常全局遮罩樣式:

非正常全局遮罩

.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;

}

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

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

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

document.body.appendChild(mask);

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

Vue如何優雅的動態添加

這里我們需要用到vue的實例化,首先我們來看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()中。

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主要是繼承父類的各種屬性來產生一個子類構造器.詳細請看源碼。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM