如何用原生js寫一個(gè)彈窗消息提醒插件
1.分析
當(dāng)消息被觸發(fā)的時(shí)候,會(huì)有一個(gè)自上而下的淡入過(guò)程。
在持續(xù)了一段時(shí)間后會(huì)自動(dòng)的消失,或者是需要用戶來(lái)手動(dòng)的點(diǎn)擊關(guān)閉按鈕。
在消息消失的時(shí)候,會(huì)有一個(gè)自下而上的淡出過(guò)程。
消息是可以疊加彈出的,最新的消息會(huì)排在消息列表的最后面。
當(dāng)前面的消息消失后,后面的消息會(huì)有一個(gè)向上滑動(dòng)效果。
然后消息本身是有三部分組成
消息圖標(biāo),用來(lái)區(qū)分不同類型的消息。
消息文本。
關(guān)閉按鈕,并不是所有消息都需要關(guān)閉按鈕。
2. 實(shí)現(xiàn)樣式
那么,不管我們是用原生js還是vue,首先呢,我們都需要把這個(gè)消息的基本樣式給寫出來(lái),然后再通過(guò)js來(lái)控制消息的彈出和關(guān)閉。
所以,我們先來(lái)寫html和css。
<!-- message.html --> <!-- 這個(gè)css是我引用阿里的一些字體圖標(biāo),請(qǐng)戳: https://www.iconfont.cn/ --> <link rel="stylesheet" > <link rel="stylesheet" href="./message.css" rel="external nofollow" > <script src="./message.js"></script> <!-- 消息外層容器,因?yàn)橄⑻嵝鸦旧鲜侨值?,所以這里用id,所有的彈出消息都是需要插入到這個(gè)容器里邊的 --> <div id="message-container"> <div class="message"> <!-- 消息圖標(biāo) icon icon-success對(duì)應(yīng)我的阿里字體圖標(biāo)的font-class --> <div class="type icon icon-success"></div> <!-- 消息文本 --> <div class="text">這是一條正經(jīng)的消息~</div> <!-- 關(guān)閉按鈕 --> <div class="close icon icon-close"></div> </div> <div class="message"> <div class="type icon icon-error"></div> <div class="text">這是一條正經(jīng)的消息~</div> </div> </div> /* message.css */ #message-container { position: fixed; left: 0; top: 0; right: 0; /* 采用flex彈性布局,讓容器內(nèi)部的所有消息可以水平居中,還能任意的調(diào)整寬度 */ display: flex; flex-direction: column; align-items: center; } #message-container .message { background: #fff; margin: 10px 0; padding: 0 10px; height: 40px; box-shadow: 0 0 10px 0 #eee; font-size: 14px; border-radius: 3px; /* 讓消息內(nèi)部的三個(gè)元素(圖標(biāo)、文本、關(guān)閉按鈕)可以垂直水平居中 */ display: flex; align-items: center; } #message-container .message .text { color: #333; padding: 0 20px 0 5px; } #message-container .message .close { cursor: pointer; color: #999; } /* 給每個(gè)圖標(biāo)都加上不同的顏色,用來(lái)區(qū)分不同類型的消息 */ #message-container .message .icon-info { color: #0482f8; } #message-container .message .icon-error { color: #f83504; } #message-container .message .icon-success { color: #06a35a; } #message-container .message .icon-warning { color: #ceca07; } #message-container .message .icon-loading { color: #0482f8; }
3. 實(shí)現(xiàn)動(dòng)畫(huà)
接下來(lái)要做的就是這個(gè)消息的彈出和消失動(dòng)畫(huà),我們還是用css來(lái)實(shí)現(xiàn)。
想要在css里邊實(shí)現(xiàn)自定義的動(dòng)畫(huà),首先需要用@keyframes來(lái)定義一個(gè)動(dòng)畫(huà)規(guī)則,然后再通過(guò)animation屬性把動(dòng)畫(huà)應(yīng)用到某個(gè)元素上就可以了。
所謂的動(dòng)畫(huà)規(guī)則其實(shí)就是一個(gè)動(dòng)畫(huà)序列,或者可以理解為一個(gè)個(gè)的關(guān)鍵幀,而關(guān)鍵幀的內(nèi)部就是你想改變的css屬性,你可以在關(guān)鍵幀里邊寫上幾乎任何的css屬性,當(dāng)動(dòng)畫(huà)被應(yīng)用的時(shí)候,這些css屬性就會(huì)根據(jù)各個(gè)關(guān)鍵幀做出相應(yīng)的變換。
那我們先用@keyframes來(lái)寫一個(gè)動(dòng)畫(huà)規(guī)則吧
/* message.css */ /* 這個(gè)動(dòng)畫(huà)規(guī)則我們就叫做message-move-in吧,隨后我們會(huì)用animation屬性在某個(gè)元素上應(yīng)用這個(gè)動(dòng)畫(huà)規(guī)則。 */ @keyframes message-move-in { 0% { /* 前邊分析過(guò)了,彈出動(dòng)畫(huà)是一個(gè)自上而下的淡入過(guò)程 */ /* 所以在動(dòng)畫(huà)初始狀態(tài)要把元素的不透明度設(shè)置為0,在動(dòng)畫(huà)結(jié)束的時(shí)候再把不透明度設(shè)置1,這樣就會(huì)實(shí)現(xiàn)一個(gè)淡入動(dòng)畫(huà) */ opacity: 0; /* 那么“自上而下”這個(gè)動(dòng)畫(huà)可以用“transform”變換屬性結(jié)合他的“translateY”上下平移函數(shù)來(lái)完成 */ /* translateY(-100%)表示動(dòng)畫(huà)初始狀態(tài),元素在實(shí)際位置上面“自身一個(gè)高度”的位置。 */ transform: translateY(-100%); } 100% { opacity: 1; /* 平移到自身位置 */ transform: translateY(0); } }
然后我們?cè)俣x一個(gè)和message元素同級(jí)的類move-in,把message-move-in這個(gè)動(dòng)畫(huà)規(guī)則給應(yīng)用到move-in類上,這樣我們需要讓哪個(gè)消息彈出,就只需要在消息的類上加一個(gè)move-in就行。
/* message.css */ #message-container .message.move-in { /* animation屬性是用來(lái)加載某個(gè)動(dòng)畫(huà)規(guī)則 請(qǐng)參考 https://developer.mozilla.org/zh-CN/docs/Web/CSS/animation */ animation: message-move-in 0.3s ease-in-out; }
可以看到,只需要在某個(gè)message上追加一個(gè)move-in就能實(shí)現(xiàn)彈出動(dòng)畫(huà)。
那么,消失動(dòng)畫(huà)也是一個(gè)套路,只不過(guò)跟彈出動(dòng)畫(huà)反過(guò)來(lái)而已。
/* message.css */ @keyframes message-move-out { 0% { opacity: 1; transform: translateY(0); } 100% { opacity: 0; transform: translateY(-100%); } } #message-container .message.move-out { animation: message-move-out 0.3s ease-in-out; /* 讓動(dòng)畫(huà)結(jié)束后保持結(jié)束狀態(tài) */ animation-fill-mode: forwards; }
animation-fill-mode: forwards;這個(gè)是干嘛的呢?因?yàn)閯?dòng)畫(huà)結(jié)束后默認(rèn)會(huì)回到元素的最初狀態(tài),在這里表現(xiàn)的是消失后又出現(xiàn)了。
所以animation-fill-mode: forwards;是為了讓動(dòng)畫(huà)結(jié)束后保持這個(gè)結(jié)束狀態(tài),也就是不在顯示了。
4. 編寫js插件
那么,在寫js之前呢,我們先來(lái)思考一下,如果你是插件的使用者,你想怎么來(lái)調(diào)用這個(gè)插件?
我們的插件很簡(jiǎn)單,就是在需要的時(shí)候彈出一個(gè)消息,假設(shè)插件他提供給我們的是一個(gè)類,就叫做Message吧,并且他內(nèi)部有一個(gè)show方法,那么只要使用者實(shí)例化這個(gè)類后,調(diào)用他的show方法,然后傳入不同的參數(shù)就可以彈出一個(gè)消息了。而且我們所實(shí)例化的對(duì)象可以是全局唯一的。
<!-- message.html --> <!-- 省略... --> <script> // message可以定義為全局對(duì)象,項(xiàng)目中可以直接調(diào)用。 const message = new Message(); message.show({ type: 'success', text: '點(diǎn)個(gè)關(guān)注不迷路~' }); </script>
所以呢,我們要先寫一個(gè)Message類,并且必須要實(shí)現(xiàn)一個(gè)show方法。
/* message.js */ class Message { constructor() { } show({ type = 'info', text = '' }) { } }
這里我直接用了es6的class關(guān)鍵詞,其實(shí)他的內(nèi)部還是原型鏈的形式。用class呢,可以讓我們更直觀的了解這個(gè)類。
根據(jù)我們?cè)诘谝徊糠值姆治?,所有的消息元素都是需要在js中創(chuàng)建的,所以我們不需要使用者來(lái)寫任何html代碼,那么我們只需要在對(duì)象被實(shí)例化new Message()的時(shí)候,就去創(chuàng)建消息容器message-container,后續(xù)在調(diào)用show方法時(shí)候,直接把消息插入到message-container內(nèi)部即可。
/* message.js */ class Message { /** * 構(gòu)造函數(shù)會(huì)在實(shí)例化的時(shí)候自動(dòng)執(zhí)行 */ constructor() { const containerId = 'message-container'; // 檢測(cè)下html中是否已經(jīng)有這個(gè)message-container元素 this.containerEl = document.getElementById(containerId); if (!this.containerEl) { // 創(chuàng)建一個(gè)Element對(duì)象,也就是創(chuàng)建一個(gè)id為message-container的dom節(jié)點(diǎn) this.containerEl = document.createElement('div'); this.containerEl.id = containerId; // 把message-container元素放在html的body末尾 document.body.appendChild(this.containerEl); } } show({ type = 'info', text = '' }) { } }
這樣,我們調(diào)用const message = new Message()的時(shí)候會(huì)在dom中自動(dòng)的插入一個(gè)message-container節(jié)點(diǎn)。
那么,最重要的還是我們的show方法:
創(chuàng)建一個(gè)消息節(jié)點(diǎn),并把它追加到message-container容器的末尾。
設(shè)定一個(gè)時(shí)間,在這個(gè)時(shí)間結(jié)束后自動(dòng)的將消息移除。
監(jiān)聽(tīng)“關(guān)閉按鈕”的click事件,來(lái)讓用戶可以手動(dòng)的移除消息。
我們一步一步來(lái)。
4.1 創(chuàng)建一個(gè)消息節(jié)點(diǎn),并把它追加到message-container容器的末尾。
class Message { // 省略... show({ type = 'info', text = '' }) { // 創(chuàng)建一個(gè)Element對(duì)象 let messageEl = document.createElement('div'); // 設(shè)置消息class,這里加上move-in可以直接看到彈出效果 messageEl.className = 'message move-in'; // 消息內(nèi)部html字符串 messageEl.innerHTML = ` <span class="icon icon-${type}"></span> <div class="text">${text}</div> <div class="close icon icon-close"></div> `; // 追加到message-container末尾 // this.containerEl屬性是我們?cè)跇?gòu)造函數(shù)中創(chuàng)建的message-container容器 this.containerEl.appendChild(messageEl); }
我們來(lái)調(diào)用下試試~
<!-- message.html --> <!-- 省略... --> <button class="btn">彈窗消息提醒</button> <script> // message可以定義為全局對(duì)象,項(xiàng)目中可以直接調(diào)用。 const message = new Message(); document.querySelector('.btn').addEventListener('click', () => { message.show({ type: 'success', text: '點(diǎn)個(gè)關(guān)注不迷路~' }); }); </script>
4.2 設(shè)定一個(gè)時(shí)間,在這個(gè)時(shí)間結(jié)束后自動(dòng)的將消息移除。
// message.js class Message { // 省略... show({ type = 'info', text = '', duration = 2000 }) { // 省略... // 用setTimeout來(lái)做一個(gè)定時(shí)器 setTimeout(() => { // Element對(duì)象內(nèi)部有一個(gè)remove方法,調(diào)用之后可以將該元素從dom樹(shù)種移除! messageEl.remove(); }, duration); } }
可以看到,消息在過(guò)了2秒后,自動(dòng)的從dom樹(shù)中移除了,不過(guò)呢并沒(méi)有動(dòng)畫(huà),還記得前邊我們寫了move-out類嗎?這個(gè)類和message是同級(jí)的。現(xiàn)在我們只需要在定時(shí)結(jié)束后把這個(gè)類應(yīng)用到message元素上就行。
// message.js class Message { // 省略... show({ type = 'info', text = '', duration = 2000 }) { // 省略... // 用setTimeout來(lái)做一個(gè)定時(shí)器 setTimeout(() => { // 首先把move-in這個(gè)彈出動(dòng)畫(huà)類給移除掉,要不然會(huì)有問(wèn)題,可以自己測(cè)試下 messageEl.className = messageEl.className.replace('move-in', ''); // 增加一個(gè)move-out類 messageEl.className += 'move-out'; // 這個(gè)地方是監(jiān)聽(tīng)動(dòng)畫(huà)結(jié)束事件,在動(dòng)畫(huà)結(jié)束后把消息從dom樹(shù)中移除。 // 如果你是在增加move-out后直接調(diào)用messageEl.remove,那么你不會(huì)看到任何動(dòng)畫(huà)效果 messageEl.addEventListener('animationend', () => { // Element對(duì)象內(nèi)部有一個(gè)remove方法,調(diào)用之后可以將該元素從dom樹(shù)種移除! messageEl.remove(); }); }, duration); } }
4.3 監(jiān)聽(tīng)“關(guān)閉按鈕”的click事件,來(lái)讓用戶可以手動(dòng)的移除消息。
有時(shí)候呢,我們希望消息能夠一直展示,直到用戶來(lái)手動(dòng)的關(guān)閉掉,那么首先我們要加一個(gè)參數(shù),用來(lái)控制是否展示這個(gè)關(guān)閉按鈕。
// message.js class Message { // 省略... show({ type = 'info', text = '', duration = 2000, closeable = false }) { // 創(chuàng)建一個(gè)Element對(duì)象 let messageEl = document.createElement('div'); // 設(shè)置消息class,這里加上move-in可以直接看到彈出效果 messageEl.className = 'message move-in'; // 消息內(nèi)部html字符串 messageEl.innerHTML = ` <span class="icon icon-${type}"></span> <div class="text">${text}</div> `; // 是否展示關(guān)閉按鈕 if (closeable) { // 創(chuàng)建一個(gè)關(guān)閉按鈕 let closeEl = document.createElement('div'); closeEl.className = 'close icon icon-close'; // 把關(guān)閉按鈕追加到message元素末尾 messageEl.appendChild(closeEl); // 監(jiān)聽(tīng)關(guān)閉按鈕的click事件,觸發(fā)后將調(diào)用我們的close方法 // 我們把剛才寫的移除消息封裝為一個(gè)close方法 closeEl.addEventListener('click', () => { this.close(messageEl) }); } // 追加到message-container末尾 // this.containerEl屬性是我們?cè)跇?gòu)造函數(shù)中創(chuàng)建的message-container容器 this.containerEl.appendChild(messageEl); // 只有當(dāng)duration大于0的時(shí)候才設(shè)置定時(shí)器,這樣我們的消息就會(huì)一直顯示 if (duration > 0) { // 用setTimeout來(lái)做一個(gè)定時(shí)器 setTimeout(() => { this.close(messageEl); }, duration); } } /** * 關(guān)閉某個(gè)消息 * 由于定時(shí)器里邊要移除消息,然后用戶手動(dòng)關(guān)閉事件也要移除消息,所以我們直接把移除消息提取出來(lái)封裝成一個(gè)方法 * @param {Element} messageEl */ close(messageEl) { // 首先把move-in這個(gè)彈出動(dòng)畫(huà)類給移除掉,要不然會(huì)有問(wèn)題,可以自己測(cè)試下 messageEl.className = messageEl.className.replace('move-in', ''); // 增加一個(gè)move-out類 messageEl.className += 'move-out'; // 這個(gè)地方是監(jiān)聽(tīng)動(dòng)畫(huà)結(jié)束事件,在動(dòng)畫(huà)結(jié)束后把消息從dom樹(shù)中移除。 // 如果你是在增加move-out后直接調(diào)用messageEl.remove,那么你不會(huì)看到任何動(dòng)畫(huà)效果 messageEl.addEventListener('animationend', () => { // Element對(duì)象內(nèi)部有一個(gè)remove方法,調(diào)用之后可以將該元素從dom樹(shù)種移除! messageEl.remove(); }); } }我們來(lái)調(diào)用下試試~
<!-- message.html --> <!-- 省略... --> <button class="btn">彈窗消息提醒</button> <script> // message可以定義為全局對(duì)象,項(xiàng)目中可以直接調(diào)用。 const message = new Message(); document.querySelector('.btn').addEventListener('click', () => { message.show({ type: 'warning', text: '點(diǎn)我旁邊的叉叉試試', duration: 0, // 不會(huì)自動(dòng)消失 closeable: true, // 可手動(dòng)關(guān)閉 }); }); </script>
其實(shí)已經(jīng)寫的差不多了,不過(guò)還是有一些小問(wèn)題,比如當(dāng)我們彈出兩個(gè)甚至更多消息的時(shí)候,如果前邊的消息消失后,下面的消息會(huì)直接跳到上面的位置,很僵硬,沒(méi)有任何的滑動(dòng)。
我們可以通過(guò)css的transition屬性來(lái)讓meesage的高度逐漸變小,這樣下面的元素就會(huì)根據(jù)變化來(lái)逐漸上移。
/* message.css */ /* 省略... */ #message-container .message { background: #fff; margin: 10px 0; padding: 0 10px; height: 40px; box-shadow: 0 0 10px 0 #ccc; font-size: 14px; border-radius: 3px; /* 讓消息內(nèi)部的三個(gè)元素(圖標(biāo)、文本、關(guān)閉按鈕)可以垂直水平居中 */ display: flex; align-items: center; /* 增加一個(gè)過(guò)渡屬性,當(dāng)message元素的高度和margin變化時(shí)候?qū)?huì)有一個(gè)過(guò)渡動(dòng)畫(huà) */ transition: height 0.2s ease-in-out, margin 0.2s ease-in-out; } /* 省略... */然后我們只需要在Message類的close方法中做一下改變:
close(messageEl) { // 首先把move-in這個(gè)彈出動(dòng)畫(huà)類給移除掉,要不然會(huì)有問(wèn)題,可以自己測(cè)試下 messageEl.className = messageEl.className.replace('move-in', ''); // 增加一個(gè)move-out類 messageEl.className += 'move-out'; // move-out動(dòng)畫(huà)結(jié)束后把元素的高度和邊距都設(shè)置為0 // 由于我們?cè)赾ss中設(shè)置了transition屬性,所以會(huì)有一個(gè)過(guò)渡動(dòng)畫(huà) messageEl.addEventListener('animationend', () => { messageEl.setAttribute('style', 'height: 0; margin: 0'); }); // 這個(gè)地方是監(jiān)聽(tīng)transition的過(guò)渡動(dòng)畫(huà)結(jié)束事件,在動(dòng)畫(huà)結(jié)束后把消息從dom樹(shù)中移除。 messageEl.addEventListener('transitionend', () => { // Element對(duì)象內(nèi)部有一個(gè)remove方法,調(diào)用之后可以將該元素從dom樹(shù)種移除! messageEl.remove(); }); }
結(jié)尾
好了,基本上已經(jīng)寫好了,不過(guò)為了各個(gè)瀏覽器的兼容性,建議大家用babel轉(zhuǎn)碼,如果想發(fā)布,可以用webpack把js和css一塊打包。
不過(guò)我們還是少考慮了一個(gè)場(chǎng)景,現(xiàn)在的關(guān)閉消息都是對(duì)象內(nèi)部調(diào)用close方法來(lái)實(shí)現(xiàn),如果我們希望外部能夠控制消息的關(guān)閉呢,比如我請(qǐng)求服務(wù)器時(shí)候彈出一個(gè)loading的消息,現(xiàn)在服務(wù)器返回?cái)?shù)據(jù)后,我怎么來(lái)關(guān)閉這個(gè)消息呢。
很簡(jiǎn)單,各位自行實(shí)現(xiàn)吧!
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
JavaScript將對(duì)象數(shù)組按字母順序排序的方法詳解
這篇文章主要介紹了JavaScript如何將對(duì)象數(shù)組按字母順序排序,本文介紹了三種解決方案,if條件語(yǔ)句 + sort(),localeCompare() + sort(),Collator() + sort(),有感興趣的同學(xué)可以跟著小編一起來(lái)看看2023-07-07js剪切板應(yīng)用clipboardData實(shí)例解析
這篇文章主要為大家詳細(xì)介紹了js剪切板應(yīng)用clipboardData實(shí)例,并分享了四種實(shí)現(xiàn)方法,感興趣的小伙伴們可以參考一下2016-05-05Js類的靜態(tài)方法與實(shí)例方法區(qū)分及jQuery拓展的兩種方法
這篇文章主要介紹了Js類的靜態(tài)方法與實(shí)例方法區(qū)分及jQuery拓展的兩種方法 的相關(guān)資料,對(duì)靜態(tài)方法(Static)和實(shí)例方法(非Static)不太理解的朋友可以一起學(xué)習(xí)下2016-06-06微信小程序動(dòng)畫(huà)(Animation)的實(shí)現(xiàn)及執(zhí)行步驟
這篇文章主要介紹了微信小程序動(dòng)畫(huà)(Animation) 的實(shí)現(xiàn)及執(zhí)行步驟,需要的朋友可以參考下2018-10-10js驗(yàn)證真實(shí)姓名與身份證號(hào),手機(jī)號(hào)的簡(jiǎn)單實(shí)例
下面小編就為大家?guī)?lái)一篇js驗(yàn)證真實(shí)姓名與身份證號(hào),手機(jī)號(hào)的簡(jiǎn)單實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-07-07js實(shí)現(xiàn)點(diǎn)擊按鈕隨機(jī)生成背景顏色
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)點(diǎn)擊按鈕隨機(jī)生成背景顏色,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-09-09