使用純JavaScript封裝一個(gè)消息提示條功能示例詳解
介紹
一個(gè)類似Element UI
、Ant-Design UI
等 UI 框架的消息提示功能,方便在任何網(wǎng)頁環(huán)境中直接調(diào)用函數(shù)使用;區(qū)別在不依賴 js 及 css 引用,而是使用純 js 進(jìn)行封裝實(shí)現(xiàn),代碼更精簡,同時(shí)保持和 UI 框架一樣的視覺效果(可自行修改成自己喜歡的樣式)
在線預(yù)覽效果(點(diǎn)擊【登錄】、【點(diǎn)擊復(fù)制】按鈕時(shí)觸發(fā)提示效果)
思路&布局
- 先來寫單個(gè)提示條,并實(shí)現(xiàn)想要的過渡效果,最后再用邏輯操作輸出節(jié)點(diǎn)即可;這里不需要父節(jié)點(diǎn)包裹,直接輸出到
<body>
中,保證提示條的代碼結(jié)構(gòu)位置永遠(yuǎn)在最上層。
<style> .msg-box { position: fixed; top: 0; left: 50%; display: flex; padding: 12px 16px; border-radius: 2px; background-color: #fff; box-shadow: 0 3px 3px -2px rgba(0,0,0,.2),0 3px 4px 0 rgba(0,0,0,.14),0 1px 8px 0 rgba(0,0,0,.12); transform: translate3d(-50%, 0%, 0); } </style> <body> <div class="msg-box"></div> </body>
- 最基礎(chǔ)的樣式寫好之后,來定義進(jìn)入的過渡動(dòng)畫,這里使用
animation
作為進(jìn)入動(dòng)畫,因?yàn)楣?jié)點(diǎn)一輸出就會(huì)執(zhí)行了
.msg-box { ...省略以上代碼 animation: msg-move .4s; } @keyframes msg-move { 0% { opacity: 0; transform: translate3d(-50%, -100%, 0); } 100% { opacity: 1; transform: translate3d(-50%, 0%, 0); } }
- 最后就是過渡結(jié)束動(dòng)畫,這里使用
transition
的過渡方式,即定義一個(gè).hide
,之后通過 js 去控制切換 class 去實(shí)現(xiàn)過渡切換
.msg-box { ...省略以上代碼 opacity: 1; transition: .4s all; // 保持和 animation 的過渡時(shí)間一致 } .msg-box.hide { opacity: 0; transform: translate3d(-50%, -100%, 0); }
這里樣式布局就全部完成了,剩下的交給 js 去處理對應(yīng)的操作邏輯。
操作邏輯
- 因?yàn)檎{(diào)用時(shí),消息條是多個(gè),并且為往下疊加的效果,且節(jié)點(diǎn)是散布在
<body>
下,有可能給其他dom
操作插入節(jié)點(diǎn)。所以在輸出節(jié)點(diǎn)的時(shí)候要將其存放起來,然后通過循環(huán)的方式去設(shè)置每一個(gè)節(jié)點(diǎn)的padding-top
,這樣就能保證視覺排列效果和代碼操作的順序保持一致了,之后所做的刪除操作也是通過循環(huán)去設(shè)置每一個(gè)節(jié)點(diǎn)的padding-top
。
/** * 消息隊(duì)列 * @type {Array<HTMLElement>} */ const messageList = []; /** * 獲取指定`item`的定位`top` * @param {HTMLElement=} el */ function getItemTop(el) { let top = 10; // 起始的邊距 for (let i = 0; i < messageList.length; i++) { const item = messageList[i]; if (el && el === item) { break; } top += item.clientHeight + 20; // 兩個(gè)消息條的間距為20 } return top; } /** * 刪除指定列表項(xiàng) * @param {HTMLElement} el */ function removeItem(el) { for (let i = 0; i < messageList.length; i++) { const item = messageList[i]; if (item === el) { messageList.splice(i, 1); break; } } el.classList.add(".hide"); messageList.forEach(function (item) { item.style.top = `${getItemTop(item)}px`; }); }
- 輸出節(jié)點(diǎn),并監(jiān)聽 動(dòng)畫進(jìn)入過渡、持續(xù)時(shí)間、動(dòng)畫退出過渡 ;
- 進(jìn)入的過渡使用
addEventListener("animationend", fn)
- 持續(xù)時(shí)間使用
setTimeout
(延遲 N 秒之后為節(jié)點(diǎn)添加.hide
) - 退出的過渡使用
addEventListener("transitionend", fn)
/** 一直累加的定位層級 */ let zIndex = 1000; /** * 顯示一條消息 * @param {string} content 內(nèi)容 * @param {number} duration 持續(xù)時(shí)間,優(yōu)先級比默認(rèn)值高 */ function show(content, duration) { const el = document.createElement("div"); el.style.top = `${getItemTop()}px`; el.style.zIndex = zIndex; el.innerHTML = content; zIndex++; messageList.push(el); document.body.appendChild(el); // 添加動(dòng)畫監(jiān)聽事件 function animationEnd() { el.removeEventListener("animationend", animationEnd); setTimeout(removeItem, duration || 3000, el); } el.addEventListener("animationend", animationEnd); function transitionEnd() { if (getComputedStyle(el).opacity !== "0") return; el.removeEventListener("transitionend", transitionEnd); el.remove(); } el.addEventListener("transitionend", transitionEnd); }
整個(gè)消息輸出功能就完成了,最后只需要把對應(yīng)的方法封裝起來并暴露需要調(diào)用的函數(shù),并把css
樣式寫進(jìn)方法里就可以在任意地方使用了;css
寫進(jìn)js
里其實(shí)就是把上面寫好的樣式,通過字符串模板的方式用變量接收并賦值給動(dòng)態(tài)輸入的<style>
標(biāo)簽就完事,另外樣式隔離可以通過模擬css.modeule
的方案去實(shí)現(xiàn),具體看下面完整代碼。
完整代碼
/** * 消息提示條 * @param {object} params * @param {number} params.duration 持續(xù)時(shí)間(毫秒),默認(rèn)`3000` * @param {number} params.zIndex 起始定位層級,默認(rèn)`1000` */ function useMessage(params = {}) { const doc = document; const cssModule = `__${Math.random().toString(36).slice(2, 7)}`; const className = { box: `msg-box${cssModule}`, hide: `hide${cssModule}`, text: `msg-text${cssModule}`, icon: `msg-icon${cssModule}` } const style = doc.createElement("style"); style.textContent = ` .${className.box}, .${className.icon}, .${className.text} { padding: 0; margin: 0; box-sizing: border-box; } .${className.box} { position: fixed; top: 0; left: 50%; display: flex; padding: 12px 16px; border-radius: 2px; background-color: #fff; box-shadow: 0 3px 3px -2px rgba(0,0,0,.2),0 3px 4px 0 rgba(0,0,0,.14),0 1px 8px 0 rgba(0,0,0,.12); white-space: nowrap; animation: ${className.box}-move .4s; transition: .4s all; transform: translate3d(-50%, 0%, 0); opacity: 1; overflow: hidden; } .${className.box}::after { content: ""; position: absolute; left: 0; top: 0; height: 100%; width: 4px; } @keyframes ${className.box}-move { 0% { opacity: 0; transform: translate3d(-50%, -100%, 0); } 100% { opacity: 1; transform: translate3d(-50%, 0%, 0); } } .${className.box}.${className.hide} { opacity: 0; /* transform: translate3d(-50%, -100%, 0); */ transform: translate3d(-50%, -100%, 0) scale(0); } .${className.icon} { display: inline-block; width: 18px; height: 18px; border-radius: 50%; overflow: hidden; margin-right: 6px; position: relative; } .${className.text} { font-size: 14px; line-height: 18px; color: #555; } .${className.icon}::after, .${className.icon}::before { position: absolute; content: ""; background-color: #fff; } .${className.box}.info .${className.icon}, .${className.box}.info::after { background-color: #1890ff; } .${className.box}.success .${className.icon}, .${className.box}.success::after { background-color: #52c41a; } .${className.box}.warning .${className.icon}, .${className.box}.warning::after { background-color: #faad14; } .${className.box}.error .${className.icon}, .${className.box}.error::after { background-color: #ff4d4f; } .${className.box}.info .${className.icon}::after, .${className.box}.warning .${className.icon}::after { top: 15%; left: 50%; margin-left: -1px; width: 2px; height: 2px; border-radius: 50%; } .${className.box}.info .${className.icon}::before, .${className.box}.warning .${className.icon}::before { top: calc(15% + 4px); left: 50%; margin-left: -1px; width: 2px; height: 40%; } .${className.box}.error .${className.icon}::after, .${className.box}.error .${className.icon}::before { top: 20%; left: 50%; width: 2px; height: 60%; margin-left: -1px; border-radius: 1px; } .${className.box}.error .${className.icon}::after { transform: rotate(-45deg); } .${className.box}.error .${className.icon}::before { transform: rotate(45deg); } .${className.box}.success .${className.icon}::after { box-sizing: content-box; background-color: transparent; border: 2px solid #fff; border-left: 0; border-top: 0; height: 50%; left: 35%; top: 13%; transform: rotate(45deg); width: 20%; transform-origin: center; } `.replace(/(\n|\t|\s)*/ig, "$1").replace(/\n|\t|\s(\{|\}|\,|\:|\;)/ig, "$1").replace(/(\{|\}|\,|\:|\;)\s/ig, "$1"); doc.head.appendChild(style); /** 一直累加的定位層級 */ let zIndex = params.zIndex || 1000; /** * 消息隊(duì)列 * @type {Array<HTMLElement>} */ const messageList = []; /** * 獲取指定`item`的定位`top` * @param {HTMLElement=} el */ function getItemTop(el) { let top = 10; for (let i = 0; i < messageList.length; i++) { const item = messageList[i]; if (el && el === item) { break; } top += item.clientHeight + 20; } return top; } /** * 刪除指定列表項(xiàng) * @param {HTMLElement} el */ function removeItem(el) { for (let i = 0; i < messageList.length; i++) { const item = messageList[i]; if (item === el) { messageList.splice(i, 1); break; } } el.classList.add(className.hide); messageList.forEach(function(item) { item.style.top = `${getItemTop(item)}px`; }); } /** * 顯示一條消息 * @param {string} content 內(nèi)容 * @param {"info"|"success"|"warning"|"error"} type 消息類型 * @param {number} duration 持續(xù)時(shí)間,優(yōu)先級比默認(rèn)值高 */ function show(content, type = "info", duration) { const el = doc.createElement("div"); el.className = `${className.box} ${type}`; el.style.top = `${getItemTop()}px`; el.style.zIndex = zIndex; el.innerHTML = ` <span class="${className.icon}"></span> <span class="${className.text}">${content}</span> `; zIndex++; messageList.push(el); doc.body.appendChild(el); // 添加動(dòng)畫監(jiān)聽事件 function animationEnd() { el.removeEventListener("animationend", animationEnd); setTimeout(removeItem, duration || params.duration || 3000, el); } el.addEventListener("animationend", animationEnd); function transitionEnd() { if (getComputedStyle(el).opacity !== "0") return; el.removeEventListener("transitionend", transitionEnd); el.remove(); } el.addEventListener("transitionend", transitionEnd); } return { show, /** * 普通描述提示 * @param {string} msg */ info(msg) { show(msg, "info"); }, /** * 成功提示 * @param {string} msg */ success(msg) { show(msg, "success"); }, /** * 警告提示 * @param {string} msg */ warning(msg) { show(msg, "warning"); }, /** * 錯(cuò)誤提示 * @param {string} msg */ error(msg) { show(msg, "error"); } } }
以上就是使用純JavaScript封裝一個(gè)消息提示條功能示例詳解的詳細(xì)內(nèi)容,更多關(guān)于JavaScript封裝消息提示條的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
微信小程序 數(shù)據(jù)遍歷的實(shí)現(xiàn)
這篇文章主要介紹了微信小程序 數(shù)據(jù)遍歷的實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2017-04-04arrify 轉(zhuǎn)數(shù)組實(shí)現(xiàn)示例源碼解析
這篇文章主要為大家介紹了arrify 轉(zhuǎn)數(shù)組實(shí)現(xiàn)示例源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12fs-extra實(shí)現(xiàn)yarn?create?tlist創(chuàng)建示例詳解
這篇文章主要為大家介紹了fs-extra實(shí)現(xiàn)yarn?create?tlist創(chuàng)建示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01PHP:微信小程序 微信支付服務(wù)端集成實(shí)例詳解及源碼下載
這篇文章主要介紹了微信小程序 微信支付服務(wù)端集成實(shí)例詳解及源碼下載的相關(guān)資料,需要的朋友可以參考下2017-01-01