需求背景
需求对接的原生官网 公司觉得原生alter不好用 于是就有了这个组件的诞生
废话不多说 开搞
该组件主要分为两种功能:一个是 message信息弹窗 一个是 dialog交互对话弹窗
同时该组件为了引用便捷 会将css与html全部集成到js当中 届时将只需引入js文件即可
既然通用 当然是需要双端兼容 采用媒体查询
1.首先写好目标dom结构与样式
1. message弹窗dom
html
<div class="ppp-message">
<i class="ppp-message-icon"></i>
<div class="content">测试测试测试</div>
<i class="ppp-message-icon"></i>
</div>
2. dialog交互对话框 dom
html
<div class="ppp-modal_Mask"></div>
<div class="ppp-modal_warp">
<div class="ppp-modal">
<div class="ppp-modal_header">
<div class="ppp-modal_header_title">标题标题</div>
<div class="ppp-modal_header_close">
<div style="position: absolute;
width: 15px;
height: 1px;
background-color: #999;
transform: rotateZ(45deg);">
</div>
<div style="position: absolute;
width: 15px;
height: 1px;
background-color: #999;
transform: rotateZ(-45deg);">
</div>
</div>
</div>
<div class="ppp-modal_content">
<p>第一段</p>
<p>第二段</p>
<p>第一段</p>
<p>第二段</p>
<p>第一段</p>
<p>第二段</p>
<p>第一段</p>
<p>第二段</p>
<p>第一段</p>
<p>第二段</p>
<p>第一段</p>
<p>第一段</p>
<p>第二段</p>
<p>第一段</p>
<p>第二段</p>
<p>第一段</p>
<p>第二段</p>
<p>第二段</p>
<p>第一段</p>
<p>第二段</p>
</div>
<div class="ppp-modal_footer">
<div class="ppp-modal_btn_cancel">取消</div>
<div class="ppp-modal_btn_confirm">确认</div>
</div>
</div>
</div>
3. 两个弹窗的css 使用了css变量实现主题切换
css
:root {
--msg-color-info: #2db7f5;
--msg-color-success: #19be6b;
--msg-color-warning: #ff9900;
--msg-color-error: #ed4014;
--msg-color-title-light: #17233d;
--msg-color-title-dark: #eeeeee;
--msg-color-title: var(--msg-color-title-light);
--msg-color-content-light: #515a6e;
--msg-color-content-dark: #ddd;
--msg-color-content: var(--msg-color-content-light);
--msg-color-BG-light: #fff;
--msg-color-BG-dark: #333333;
--msg-color-BG: var(--msg-color-BG-light);
--msg-color-border-light: #eee;
--msg-color-border-dark: #fff;
--msg-color-border: var(--msg-color-border-light);
--msg-color-boxShadow-light: #999;
--msg-color-boxShadow-dark: #777;
--msg-color-boxShadow: var(--msg-color-boxShadow-light);
--dialog-color-title-light: #17233d;
--dialog-color-title-dark: #eeeeee;
--dialog-color-title: var(--dialog-color-title-light);
--dialog-color-content-light: #515a6e;
--dialog-color-content-dark: #ddd;
--dialog-color-content: var(--dialog-color-content-light);
--dialog-color-BG-light: #fff;
--dialog-color-BG-dark: #333333;
--dialog-color-BG: var(--dialog-color-BG-light);
--dialog-color-border-light: #eee;
--dialog-color-border-dark: #fff;
--dialog-color-border: var(--dialog-color-border-light);
--dialog-color-boxShadow-light: #999;
--dialog-color-boxShadow-dark: #777;
--dialog-color-boxShadow: var(--dialog-color-boxShadow-light);
}
.ppp-modal_Mask {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
z-index: 890;
background-color: #000000aa;
opacity: 0;
transition: all .3s;
}
.ppp-message {
min-width: 300px;
min-height: 44px;
position: fixed;
left: 50%;
transform: translateX(-50%);
top: 16px;
border-radius: 4px;
background-color: var(--msg-color-BG);
box-shadow: 0 3px 8px 0 var(--msg-color-boxShadow);
display: flex;
justify-content: space-between;
align-items: center;
color: var(--msg-color-title);
font-weight: bold;
font-size: 16px;
z-index: 999;
box-sizing: border-box;
transition: all .5s;
}
.ppp-message-icon {
flex-shrink: 0;
display: inline-block;
width: 10px;
height: 10px;
margin: 0 20px;
border-radius: 50%;
}
.ppp-modal_warp {
position: fixed;
top: 0;
z-index: 899;
width: 100vw;
height: 100vh;
}
.ppp-modal {
width: 520px;
position: relative;
margin: 0 auto;
top: 40%;
background-color: var(--dialog-color-BG);
border-radius: 4px;
opacity: 0;
transform: translateY(-50%) scale(.9);
transition: all .3s;
flex-shrink: 0;
}
.ppp-modal_header {
border-bottom: 1px solid var(--dialog-color-border);
padding: 14px 0 14px 16px;
box-sizing: border-box;
display: flex;
justify-content: space-between;
}
.ppp-modal_header_title {
font-size: 20px;
color: var(--dialog-color-title);
}
.ppp-modal_header_close {
width: 15px;
display: flex;
justify-content: center;
align-items: center;
position: relative;
cursor: pointer;
padding: 0 15px;
box-sizing: content-box;
}
.ppp-modal_content {
max-height: 364px;
overflow-y:auto;
box-sizing: border-box;
font-size: 16px;
padding: 14px 16px;
color: var(--dialog-color-content);
}
.ppp-modal_footer {
border-top: 1px solid var(--dialog-color-border);
box-sizing: border-box;
padding: 14px 16px;
display: flex;
justify-content: flex-end;
align-items: center;
}
.ppp-modal_btn_cancel,
.ppp-modal_btn_confirm {
height: 32px;
padding: 0 15px;
font-size: 14px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
color: var(--dialog-color-title);
margin-left: 5px;
user-select: none;
cursor: pointer;
}
.ppp-modal_btn_cancel:hover,
.ppp-modal_btn_confirm:hover {
color: var(--msg-color-info);
}
@media (max-width: 576px) {
.ppp-modal {
width: auto;
margin: 0 15px;
}
}
@media (max-width: 310px) {
.ppp-message {
width: auto;
min-width: 260px;
margin: 0 10px;
}
}
@media (max-width:281px) {
.ppp-message {
left: 0;
transform: translateX(0)
}
}
@media (max-height:481px) {
.ppp-modal {
top: 50%
}
}
2. 开始js部分
1. 使用js将css插入到head标签中
javascript
const style = document.createElement("style");
style.textContent = `
:root {
--msg-color-info: #2db7f5;
--msg-color-success: #19be6b;
--msg-color-warning: #ff9900;
--msg-color-error: #ed4014;
--msg-color-title-light: #17233d;
--msg-color-title-dark: #eeeeee;
--msg-color-title: var(--msg-color-title-light);
--msg-color-content-light: #515a6e;
--msg-color-content-dark: #ddd;
--msg-color-content: var(--msg-color-content-light);
--msg-color-BG-light: #fff;
--msg-color-BG-dark: #333333;
--msg-color-BG: var(--msg-color-BG-light);
--msg-color-border-light: #eee;
--msg-color-border-dark: #fff;
--msg-color-border: var(--msg-color-border-light);
--msg-color-boxShadow-light: #999;
--msg-color-boxShadow-dark: #777;
--msg-color-boxShadow: var(--msg-color-boxShadow-light);
--dialog-color-title-light: #17233d;
--dialog-color-title-dark: #eeeeee;
--dialog-color-title: var(--dialog-color-title-light);
--dialog-color-content-light: #515a6e;
--dialog-color-content-dark: #ddd;
--dialog-color-content: var(--dialog-color-content-light);
--dialog-color-BG-light: #fff;
--dialog-color-BG-dark: #333333;
--dialog-color-BG: var(--dialog-color-BG-light);
--dialog-color-border-light: #eee;
--dialog-color-border-dark: #fff;
--dialog-color-border: var(--dialog-color-border-light);
--dialog-color-boxShadow-light: #999;
--dialog-color-boxShadow-dark: #777;
--dialog-color-boxShadow: var(--dialog-color-boxShadow-light);
}
.ppp-modal_Mask {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
z-index: 890;
background-color: #000000aa;
opacity: 0;
transition: all .3s;
}
.ppp-message {
min-width: 300px;
min-height: 44px;
position: fixed;
left: 50%;
transform: translateX(-50%);
top: 16px;
border-radius: 4px;
background-color: var(--msg-color-BG);
box-shadow: 0 3px 8px 0 var(--msg-color-boxShadow);
display: flex;
justify-content: space-between;
align-items: center;
color: var(--msg-color-title);
font-weight: bold;
font-size: 16px;
z-index: 999;
box-sizing: border-box;
transition: all .5s;
}
.ppp-message-icon {
flex-shrink: 0;
display: inline-block;
width: 10px;
height: 10px;
margin: 0 20px;
border-radius: 50%;
}
.ppp-modal_warp {
position: fixed;
top: 0;
z-index: 899;
width: 100vw;
height: 100vh;
}
.ppp-modal {
width: 520px;
position: relative;
margin: 0 auto;
top: 40%;
background-color: var(--dialog-color-BG);
border-radius: 4px;
opacity: 0;
transform: translateY(-50%) scale(.9);
transition: all .3s;
flex-shrink: 0;
}
.ppp-modal_header {
border-bottom: 1px solid var(--dialog-color-border);
padding: 14px 0 14px 16px;
box-sizing: border-box;
display: flex;
justify-content: space-between;
}
.ppp-modal_header_title {
font-size: 20px;
color: var(--dialog-color-title);
}
.ppp-modal_header_close {
width: 15px;
display: flex;
justify-content: center;
align-items: center;
position: relative;
cursor: pointer;
padding: 0 15px;
box-sizing: content-box;
}
.ppp-modal_content {
max-height: 364px;
overflow-y:auto;
box-sizing: border-box;
font-size: 16px;
padding: 14px 16px;
color: var(--dialog-color-content);
}
.ppp-modal_footer {
border-top: 1px solid var(--dialog-color-border);
box-sizing: border-box;
padding: 14px 16px;
display: flex;
justify-content: flex-end;
align-items: center;
}
.ppp-modal_btn_cancel,
.ppp-modal_btn_confirm {
height: 32px;
padding: 0 15px;
font-size: 14px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
color: var(--dialog-color-title);
margin-left: 5px;
user-select: none;
cursor: pointer;
}
.ppp-modal_btn_cancel:hover,
.ppp-modal_btn_confirm:hover {
color: var(--msg-color-info);
}
@media (max-width: 576px) {
.ppp-modal {
width: auto;
margin: 0 15px;
}
}
@media (max-width: 310px) {
.ppp-message {
width: auto;
min-width: 260px;
margin: 0 10px;
}
}
@media (max-width:281px) {
.ppp-message {
left: 0;
transform: translateX(0)
}
}
@media (max-height:481px) {
.ppp-modal {
top: 50%
}
}
`
.replace(/\t|\n|\s/gi, " ")
.replace(/\n|\t|\s(\{|\}|\,|\:|\;)/gi, "$1")
.replace(/(\{|\}|\,|\:|\;)\s/gi, "$1");
document.head.appendChild(style);
3. 封装一个方便插入行内样式的函数和一个禁止页面滚动的函数
javascript
/**
* @param {HTMLElement} dom 需要插入行内样式的dom
* @param {object} object 对象形式的css
* @example
* setCss(div,{width:'100px'})
*/
function setCss(dom,object) {
for (const key in object) {
if (Object.hasOwnProperty.call(object, key)) {
const item = object[key];
dom.style.setProperty(key, item);
}
}
}
/**
* 切换页面滑动
* @param {boolean} value true 开启滑动 false 关闭滑动
* @example
* slidingToggle(true) // 开启滑动
*/
function slidingToggle(value = false) {
setCss(document.documentElement, {
overflow: value ? "auto" : "hidden",
});
}
3. 创建一个通用类 主要用来创建弹窗
javascript
class createPopup {
/**
*
* @param {object} param
* @param {string} [param.themeMode='light'] 主题模式:
* @param {boolean} [param.isShowCancel=true] 是否显示取消按钮 如果不显示(false) 确认按钮可自动关闭弹窗
*/
constructor({ themeMode, isShowCancel }) {
this.themeMode = themeMode || "light";
this.isShowCancel = isShowCancel;
}
/**
* 创建一个消息弹窗
* @function msg
* @param {any} content 消息弹窗内容 类型任意 可为dom
* @param {string} status 消息弹窗状态 ['error','info','success','warning']
*/
msg(content, status) {
const div = document.createElement("div");
const textContent = document.createElement("div");
const icon = document.createElement("i");
const icon2 = document.createElement("i");
icon.classList.add("ppp-message-icon");
icon2.classList.add("ppp-message-icon");
this.toggleThemeMode("msg", this.themeMode);
setCss(icon, {
"background-color": `var(--msg-color-${status})`,
});
setCss(icon2, {
"background-color": `transparent`,
});
div.classList.add("ppp-message");
div.classList.add(status);
setCss(div, {
color:
status === "error"
? `var(--msg-color-error)`
: `var(--msg-color-title)`,
});
textContent.classList.add("content");
textContent.innerHTML = content;
div.appendChild(icon);
div.appendChild(textContent);
div.appendChild(icon2);
return div;
}
/**
* 创建一个交互对话弹窗
* @param {object} contentOpt
* @param {string} contentOpt.title 消息弹窗标题
* @param {any} contentOpt.content 消息弹窗内容
* @param {any} contentOpt.cancelText 取消按钮文本
* @param {any} contentOpt.confirmText 确认按钮文本
* @param {string} contentOpt.themeMode 弹窗主题
* @param {boolean} contentOpt.isShowCancel 是否显示取消按钮
*/
createModal(contentOpt) {
let {
title,
content,
cancelText,
confirmText,
themeMode,
isShowCancel,
} = contentOpt;
themeMode = themeMode || this.themeMode;
isShowCancel = isShowCancel || this.isShowCancel;
const onlyKey = (Date.now() + Math.random() * Date.now())
.toString(16)
.replace(".", "-");
this.toggleThemeMode("dialog", themeMode);
// modal warp
const modal_warp = document.createElement("div");
modal_warp.classList.add("ppp-modal_warp");
// modal main
const modal = document.createElement("div");
modal.classList.add("ppp-modal");
modal.classList.add("ppp-modal-" + onlyKey);
// modal header
const modal_header = document.createElement("div");
modal_header.classList.add("ppp-modal_header");
// header title
const modal_header_title = document.createElement("div");
modal_header_title.classList.add("ppp-modal_header_title");
modal_header_title.innerHTML = title;
// header close btn
const modal_header_close = document.createElement("div");
modal_header_close.classList.add("ppp-modal_header_close");
modal_header_close.innerHTML = `
<div style="position: absolute;
width: 15px;
height: 1px;
background-color: #999;
transform: rotateZ(45deg);"></div>
<div style="position: absolute;
width: 15px;
height: 1px;
background-color: #999;
transform: rotateZ(-45deg);"></div>
`;
// 插入标题
modal_header.appendChild(modal_header_title);
// 插入关闭按钮
modal_header.appendChild(modal_header_close);
// modal content
const modal_content = document.createElement("div");
modal_content.classList.add("ppp-modal_content");
modal_content.innerHTML = content;
// modal footer
const modal_footer = document.createElement("div");
modal_footer.classList.add("ppp-modal_footer");
// 创建footer按钮
// cancel 取消
const modal_btn_cancel = document.createElement("div");
modal_btn_cancel.classList.add("ppp-modal_btn_cancel");
modal_btn_cancel.innerHTML = cancelText;
if (isShowCancel) {
modal_footer.appendChild(modal_btn_cancel);
}
// confirm 确认
const modal_btn_confirm = document.createElement("div");
modal_btn_confirm.classList.add("ppp-modal_btn_confirm");
modal_btn_confirm.innerHTML = confirmText;
modal_footer.appendChild(modal_btn_confirm);
// 插入dom
modal.appendChild(modal_header);
modal.appendChild(modal_content);
modal.appendChild(modal_footer);
modal_warp.appendChild(modal);
return {
modal: modal_warp,
close: modal_header_close,
cancel: modal_btn_cancel,
confirm: modal_btn_confirm,
onlyKey,
};
}
/**
* 切换主题
* @param {string} target 所属目标 ["dialog","msg"]
* @param {string} mode 主题 ["light","dark"]
*/
toggleThemeMode(target, mode) {
let themeObj = {};
themeObj = {
[`--${target}-color-title`]: `var(--${target}-color-title-${mode})`,
[`--${target}-color-content`]: `var(--${target}-color-content-${mode})`,
[`--${target}-color-BG`]: `var(--${target}-color-BG-${mode})`,
[`--${target}-color-boxShadow`]: `var(--${target}-color-boxShadow-${mode})`,
};
setCss(document.documentElement, themeObj);
}
}
4. 创建message弹窗功能实例
javascript
class Message extends createPopup {
/**
* @constructor
* @param {Object} param
* @param {Number} [param.duration=3000] 弹窗显示时间 单位: ms 默认3000ms
* @param {String} [param.themeMode="light"] 色彩主题 ["light","dark"] 默认:light
*/
constructor(
{ duration, themeMode } = { duration: 3000, themeMode: "light" }
) {
super({ themeMode });
this.duration = duration || 3000;
this.msgPool = [];
}
/**
* 该函数会将dom插入到body中
* @param {HTMLElement} dom
*/
append(dom) {
const onlyKey = (Date.now() + Math.random() * Date.now())
.toString(16)
.replace(".", "-");
dom.classList.add(onlyKey);
this.lastTop = -5;
if (this.msgPool.length) {
const lastDom = this.msgPool[this.msgPool.length - 1].dom;
this.lastTop = this.getElTop(lastDom);
}
this.msgPool.push({ dom, onlyKey });
setCss(dom, {
top: `${this.lastTop}px`,
});
document.body.appendChild(dom);
setCss(dom, {
top: `${this.getElHeight(dom) * this.msgPool.length}px`,
});
this.destroyMsg(); // 销毁弹窗
}
info(content) {
const msg = this.msg(content, "info");
this.append(msg);
}
success(content) {
const msg = this.msg(content, "success");
this.append(msg);
}
warning(content) {
const msg = this.msg(content, "warning");
this.append(msg);
}
error(content) {
const msg = this.msg(content, "error");
this.append(msg);
}
/**
* 销毁弹窗
*/
destroyMsg() {
const that = this;
setTimeout(() => {
const { dom } = this.msgPool.shift();
this.msgPool.forEach((item, index) => {
setCss(item.dom, {
top: `${that.getElHeight(item.dom) * (index + 1)}px`,
});
});
setCss(dom, { opacity: 0, top: `-53.6px` });
function transitionEnd() {
dom.remove();
dom.removeEventListener("transitionend", transitionEnd);
}
dom.addEventListener("transitionend", transitionEnd);
}, this.duration);
}
getElHeight(dom) {
return dom.clientHeight + 10;
}
/**
*
* @param {HTMLElement} dom
* @return {number}
*/
getElTop(dom) {
if (typeof dom === "object") {
return dom.getBoundingClientRect().top;
}
}
}
5.创建dialog交互对话弹窗实例
javascript
class Dialog extends createPopup {
/**
* @constructor
* @param {object} param
* @param {function} [param.closeCallback=null] 关闭时回调
* @param {fucntion} [param.cancelCallback=null] 取消时回调
* @param {function} [param.confirmCallback=null] 确认时回调, 注意:确认并不会自动关闭窗口,需要调用回调参数关闭对话框
* @param {string} [param.themeMode='light'] 主题模式,默认为亮色主体 ['light','dark']
* @param {boolean} [param.isShowCancel=true] 是否显示取消按钮 如果不显示(false) 确认按钮可自动关闭弹窗
* @example
* new Dialog({
* confirmCallback:(offDialog)=>{
* offDialog() // 关闭对话框
* }
* })
*
*/
constructor(param) {
const baseParam = {
...{
closeCallback: null,
cancelCallback: null,
confirmCallback: null,
isShowCancel: true,
themeMode: "light",
},
...param,
};
super({
themeMode: baseParam.themeMode,
isShowCancel: baseParam.isShowCancel,
});
this.closeCallback = baseParam.closeCallback;
this.cancelCallback = baseParam.cancelCallback;
this.confirmCallback = baseParam.confirmCallback;
this.isShowCancel = baseParam.isShowCancel;
this.themeMode = baseParam.themeMode;
this.mask = null;
}
/**
* @function
* @param {object} param
* @param {any} [param.title="标题"] 弹窗标题 如不填则显示:'标题', 可自定义填入dom
* @param {any} param.content 弹窗内容, 可自定义内容 包括不限于字符串、dom等
* @param {any} param.cancelText 取消按钮文本 可自定义内容 包括不限于字符串、dom等
* @param {any} param.confirmText 确认按钮文本 可自定义内容 包括不限于字符串、dom等
* @param {boolean} param.isShowCancel 是否显示取消按钮 默认为true
* @param {string} param.themeMode 弹框主题 选项为["light",'dark'] 默认为 light
* @param {function} param.confirmCallback 确认时触发回调
* @param {function} param.cancelCallback 取消时触发回调
* @param {fucntion} param.closeCallback 关闭时触发回调
*/
modal({
title,
content,
cancelText,
confirmText,
isShowCancel,
themeMode,
confirmCallback,
cancelCallback,
closeCallback,
}) {
this.openMask();
const { modal, cancel, confirm, close, onlyKey } = this.createModal({
title: title ? title : "标题",
content,
cancelText: cancelText ? cancelText : "取消",
confirmText: confirmText ? confirmText : "确认",
themeMode,
isShowCancel,
});
document.body.appendChild(modal);
this.modalDom = modal;
setTimeout(() => {
setCss(modal.querySelector(".ppp-modal-" + onlyKey), {
opacity: "1",
transform: "translateY(-50%) scale(1)",
});
}, 0);
const that = this;
cancel.addEventListener("click", () => {
that.cancel({ cancelCallback });
});
confirm.addEventListener("click", () => {
that.confirm({
isShowCancel,
confirmCallback,
});
});
close.addEventListener("click", () => {
that.close({
closeCallback,
});
});
}
offMask() {
if (this.mask) {
setCss(this.mask, {
opacity: "0",
});
const mk = this.mask;
setTimeout(() => {
mk.remove();
}, 300);
slidingToggle(true);
this.mask = null;
}
}
openMask() {
// mask
const modal_mask = document.createElement("div");
modal_mask.classList.add("ppp-modal_Mask");
document.body.appendChild(modal_mask);
this.mask = modal_mask;
slidingToggle(false);
setTimeout(() => {
setCss(this.mask, {
opacity: "1",
});
}, 0);
}
offModal() {
this.offMask();
setCss(this.modalDom.querySelector(".ppp-modal"), {
opacity: "0",
});
setTimeout(() => {
this.modalDom.remove();
}, 0);
}
cancel(opt) {
this.offModal();
if (typeof this.cancelCallback === "function") {
this.cancelCallback();
}
if (typeof opt.cancelCallback === "function") {
opt.cancelCallback();
}
}
confirm(opt) {
const that = this;
if (!this.isShowCancel || !opt.ishowCancel) this.offModal();
if (typeof this.confirmCallback === "function") {
this.confirmCallback(() => {
that.offModal();
});
}
if (typeof opt.confirmCallback === "function") {
opt.confirmCallback(() => {
that.offModal();
});
}
}
close(opt) {
this.offModal();
if (typeof this.closeCallback === "function") this.closeCallback();
if (typeof opt.closeCallback === "function") opt.closeCallback();
}
}
到这里 弹窗功能已经能够正常使用了 试试下方的demo吧
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Message</title>
<style>
* {
margin: 0;
padding: 0;
}
body {
/* background-color: #000; */
}
</style>
</head>
<body>
<button class="infoBtn">info弹窗</button>
<button class="successBtn">success弹窗</button>
<button class="warringBtn">warring弹窗</button>
<button class="errorBtn">error弹窗</button>
<button class="dialogLightBtn">打开亮色dialog弹窗</button>
<button class="dialogDarkBtn">打开暗色dialog弹窗</button>
</body>
<script src="./Message.dev.js"></script>
<script>
const msg = new Message({ themeMode: "light", duration: 10000 });
// const msg = useMessage()
let count = 1
const info = document.querySelector(".infoBtn");
info.addEventListener("click", () => {
msg.info("测试测试测试: " + count);
count++
});
const success = document.querySelector(".successBtn");
success.addEventListener("click", () => {
msg.success("测试测试测试: " + count);
count++
});
const warning = document.querySelector(".warringBtn");
warning.addEventListener("click", () => {
msg.warning("测试测试测试: " + count);
count++
});
const error = document.querySelector(".errorBtn");
error.addEventListener("click", () => {
msg.error("测试测试测试: " + count);
count++
});
const dialog = new Dialog({
confirmCallback: (closeModel) => {
closeModel()
msg.success('全局:点击了确定按钮')
},
cancelCallback: () => {
msg.info('全局:点击了取消按钮')
},
closeCallback: () => {
msg.warning('全局:点击了关闭对话框')
},
themeMode: "light",
isShowCancel: false
});
const dialogLightBtn = document.querySelector(".dialogLightBtn");
dialogLightBtn.addEventListener("click", () => {
dialog.modal({
title: "标题标题",
content: "<p>第一段</p><p>第二段</p><p>第一段</p><p>第二段</p><p>第一段</p><p>第二段</p><p>第一段</p><p>第二段</p><p>第一段</p><p>第二段</p><p>第一段</p><p>第一段</p><p>第二段</p><p>第一段</p><p>第二段</p><p>第一段</p><p>第二段</p><p>第二段</p><p>第一段</p><p>第二段</p>",
isShowCancel: true,
themeMode: 'light',
closeCallback: () => {
msg.info('亮色窗口:点击了关闭按钮')
},
confirmCallback: () => {
msg.info('亮色窗口:点击了确认按钮')
}
});
});
const dialogDarkBtn = document.querySelector('.dialogDarkBtn')
dialogDarkBtn.addEventListener('click', () => {
dialog.modal({
title: '暗色弹窗标题',
content: '暗色弹窗内容测试',
isShowCancel: true,
themeMode: 'dark',
cancelCallback: () => {
msg.info('暗色窗口:点击了取消按钮')
},
confirmCallback: (closeModel) => {
// closeModel()
msg.info('暗色窗口:点击了确认按钮')
},
closeCallback: () => {
msg.info('暗色窗口:点击了关闭按钮')
}
})
})
</script>
</html>
写在最后
- 该组件会有两种模式回调:一种是全局回调 一种是局部配置回调
- 局部回调一定会触发全局回调 但全局回调不一定触发局部回调
- 假如全局回调与局部回调都配置了回调函数 那么这两种回调都将触发
弹窗示例图片
pc: 四种状态message
pc:dialog
移动端




整体源码
javascript
function setCss(dom, object) {
for (const key in object) {
if (Object.hasOwnProperty.call(object, key)) {
const item = object[key];
dom.style.setProperty(key, item);
}
}
}
const style = document.createElement("style");
style.textContent = `
:root {
--msg-color-info: #2db7f5;
--msg-color-success: #19be6b;
--msg-color-warning: #ff9900;
--msg-color-error: #ed4014;
--msg-color-title-light: #17233d;
--msg-color-title-dark: #eeeeee;
--msg-color-title: var(--msg-color-title-light);
--msg-color-content-light: #515a6e;
--msg-color-content-dark: #ddd;
--msg-color-content: var(--msg-color-content-light);
--msg-color-BG-light: #fff;
--msg-color-BG-dark: #333333;
--msg-color-BG: var(--msg-color-BG-light);
--msg-color-border-light: #eee;
--msg-color-border-dark: #fff;
--msg-color-border: var(--msg-color-border-light);
--msg-color-boxShadow-light: #999;
--msg-color-boxShadow-dark: #777;
--msg-color-boxShadow: var(--msg-color-boxShadow-light);
--dialog-color-title-light: #17233d;
--dialog-color-title-dark: #eeeeee;
--dialog-color-title: var(--dialog-color-title-light);
--dialog-color-content-light: #515a6e;
--dialog-color-content-dark: #ddd;
--dialog-color-content: var(--dialog-color-content-light);
--dialog-color-BG-light: #fff;
--dialog-color-BG-dark: #333333;
--dialog-color-BG: var(--dialog-color-BG-light);
--dialog-color-border-light: #eee;
--dialog-color-border-dark: #fff;
--dialog-color-border: var(--dialog-color-border-light);
--dialog-color-boxShadow-light: #999;
--dialog-color-boxShadow-dark: #777;
--dialog-color-boxShadow: var(--dialog-color-boxShadow-light);
}
.ppp-modal_Mask {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
z-index: 890;
background-color: #000000aa;
opacity: 0;
transition: all .3s;
}
.ppp-message {
min-width: 300px;
min-height: 44px;
position: fixed;
left: 50%;
transform: translateX(-50%);
top: 16px;
border-radius: 4px;
background-color: var(--msg-color-BG);
box-shadow: 0 3px 8px 0 var(--msg-color-boxShadow);
display: flex;
justify-content: space-between;
align-items: center;
color: var(--msg-color-title);
font-weight: bold;
font-size: 16px;
z-index: 999;
box-sizing: border-box;
transition: all .5s;
}
.ppp-message-icon {
flex-shrink: 0;
display: inline-block;
width: 10px;
height: 10px;
margin: 0 20px;
border-radius: 50%;
}
.ppp-modal_warp {
position: fixed;
top: 0;
z-index: 899;
width: 100vw;
height: 100vh;
}
.ppp-modal {
width: 520px;
position: relative;
margin: 0 auto;
top: 40%;
background-color: var(--dialog-color-BG);
border-radius: 4px;
opacity: 0;
transform: translateY(-50%) scale(.9);
transition: all .3s;
flex-shrink: 0;
}
.ppp-modal_header {
border-bottom: 1px solid var(--dialog-color-border);
padding: 14px 0 14px 16px;
box-sizing: border-box;
display: flex;
justify-content: space-between;
}
.ppp-modal_header_title {
font-size: 20px;
color: var(--dialog-color-title);
}
.ppp-modal_header_close {
width: 15px;
display: flex;
justify-content: center;
align-items: center;
position: relative;
cursor: pointer;
padding: 0 15px;
box-sizing: content-box;
}
.ppp-modal_content {
max-height: 364px;
overflow-y:auto;
box-sizing: border-box;
font-size: 16px;
padding: 14px 16px;
color: var(--dialog-color-content);
}
.ppp-modal_footer {
border-top: 1px solid var(--dialog-color-border);
box-sizing: border-box;
padding: 14px 16px;
display: flex;
justify-content: flex-end;
align-items: center;
}
.ppp-modal_btn_cancel,
.ppp-modal_btn_confirm {
height: 32px;
padding: 0 15px;
font-size: 14px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
color: var(--dialog-color-title);
margin-left: 5px;
user-select: none;
cursor: pointer;
}
.ppp-modal_btn_cancel:hover,
.ppp-modal_btn_confirm:hover {
color: var(--msg-color-info);
}
@media (max-width: 576px) {
.ppp-modal {
width: auto;
margin: 0 15px;
}
}
@media (max-width: 310px) {
.ppp-message {
width: auto;
min-width: 260px;
margin: 0 10px;
}
}
@media (max-width:281px) {
.ppp-message {
left: 0;
transform: translateX(0)
}
}
@media (max-height:481px) {
.ppp-modal {
top: 50%
}
}
`
.replace(/\t|\n|\s/gi, " ")
.replace(/\n|\t|\s(\{|\}|\,|\:|\;)/gi, "$1")
.replace(/(\{|\}|\,|\:|\;)\s/gi, "$1");
document.head.appendChild(style);
/**
* 切换页面滑动
* @param {boolean} value true 开启滑动 false 关闭滑动
* @example
* slidingToggle(true) // 开启滑动
*/
function slidingToggle(value = false) {
setCss(document.documentElement, {
overflow: value ? "auto" : "hidden",
});
}
class createPopup {
/**
*
* @param {object} param
* @param {string} [param.themeMode='light'] 主题模式:
* @param {boolean} [param.isShowCancel=true] 是否显示取消按钮 如果不显示(false) 确认按钮可自动关闭弹窗
*/
constructor({ themeMode, isShowCancel }) {
this.themeMode = themeMode || "light";
this.isShowCancel = isShowCancel;
}
/**
* 创建一个消息弹窗
* @function msg
* @param {any} content 消息弹窗内容 类型任意 可为dom
* @param {string} status 消息弹窗状态 ['error','info','success','warning']
*/
msg(content, status) {
const div = document.createElement("div");
const textContent = document.createElement("div");
const icon = document.createElement("i");
const icon2 = document.createElement("i");
icon.classList.add("ppp-message-icon");
icon2.classList.add("ppp-message-icon");
this.toggleThemeMode("msg", this.themeMode);
setCss(icon, {
"background-color": `var(--msg-color-${status})`,
});
setCss(icon2, {
"background-color": `transparent`,
});
div.classList.add("ppp-message");
div.classList.add(status);
setCss(div, {
color:
status === "error"
? `var(--msg-color-error)`
: `var(--msg-color-title)`,
});
textContent.classList.add("content");
textContent.innerHTML = content;
div.appendChild(icon);
div.appendChild(textContent);
div.appendChild(icon2);
return div;
}
/**
* 创建一个交互对话弹窗
* @param {object} contentOpt
* @param {string} contentOpt.title 消息弹窗标题
* @param {any} contentOpt.content 消息弹窗内容
* @param {any} contentOpt.cancelText 取消按钮文本
* @param {any} contentOpt.confirmText 确认按钮文本
* @param {string} contentOpt.themeMode 弹窗主题
* @param {boolean} contentOpt.isShowCancel 是否显示取消按钮
*/
createModal(contentOpt) {
let {
title,
content,
cancelText,
confirmText,
themeMode,
isShowCancel,
} = contentOpt;
themeMode = themeMode || this.themeMode;
isShowCancel = isShowCancel || this.isShowCancel;
const onlyKey = (Date.now() + Math.random() * Date.now())
.toString(16)
.replace(".", "-");
this.toggleThemeMode("dialog", themeMode);
// modal warp
const modal_warp = document.createElement("div");
modal_warp.classList.add("ppp-modal_warp");
// modal main
const modal = document.createElement("div");
modal.classList.add("ppp-modal");
modal.classList.add("ppp-modal-" + onlyKey);
// modal header
const modal_header = document.createElement("div");
modal_header.classList.add("ppp-modal_header");
// header title
const modal_header_title = document.createElement("div");
modal_header_title.classList.add("ppp-modal_header_title");
modal_header_title.innerHTML = title;
// header close btn
const modal_header_close = document.createElement("div");
modal_header_close.classList.add("ppp-modal_header_close");
modal_header_close.innerHTML = `
<div style="position: absolute;
width: 15px;
height: 1px;
background-color: #999;
transform: rotateZ(45deg);"></div>
<div style="position: absolute;
width: 15px;
height: 1px;
background-color: #999;
transform: rotateZ(-45deg);"></div>
`;
// 插入标题
modal_header.appendChild(modal_header_title);
// 插入关闭按钮
modal_header.appendChild(modal_header_close);
// modal content
const modal_content = document.createElement("div");
modal_content.classList.add("ppp-modal_content");
modal_content.innerHTML = content;
// modal footer
const modal_footer = document.createElement("div");
modal_footer.classList.add("ppp-modal_footer");
// 创建footer按钮
// cancel 取消
const modal_btn_cancel = document.createElement("div");
modal_btn_cancel.classList.add("ppp-modal_btn_cancel");
modal_btn_cancel.innerHTML = cancelText;
if (isShowCancel) {
modal_footer.appendChild(modal_btn_cancel);
}
// confirm 确认
const modal_btn_confirm = document.createElement("div");
modal_btn_confirm.classList.add("ppp-modal_btn_confirm");
modal_btn_confirm.innerHTML = confirmText;
modal_footer.appendChild(modal_btn_confirm);
// 插入dom
modal.appendChild(modal_header);
modal.appendChild(modal_content);
modal.appendChild(modal_footer);
modal_warp.appendChild(modal);
return {
modal: modal_warp,
close: modal_header_close,
cancel: modal_btn_cancel,
confirm: modal_btn_confirm,
onlyKey,
};
}
/**
* 切换主题
* @param {string} target 所属目标 ["dialog","msg"]
* @param {string} mode 主题 ["light","dark"]
*/
toggleThemeMode(target, mode) {
let themeObj = {};
themeObj = {
[`--${target}-color-title`]: `var(--${target}-color-title-${mode})`,
[`--${target}-color-content`]: `var(--${target}-color-content-${mode})`,
[`--${target}-color-BG`]: `var(--${target}-color-BG-${mode})`,
[`--${target}-color-boxShadow`]: `var(--${target}-color-boxShadow-${mode})`,
};
setCss(document.documentElement, themeObj);
}
}
class Message extends createPopup {
/**
* @constructor
* @param {Object} param
* @param {Number} [param.duration=3000] 弹窗显示时间 单位: ms 默认3000ms
* @param {String} [param.themeMode="light"] 色彩主题 ["light","dark"] 默认:light
*/
constructor(
{ duration, themeMode } = { duration: 3000, themeMode: "light" }
) {
super({ themeMode });
this.duration = duration || 3000;
this.msgPool = [];
}
/**
* 该函数会将dom插入到body中
* @param {HTMLElement} dom
*/
append(dom) {
const onlyKey = (Date.now() + Math.random() * Date.now())
.toString(16)
.replace(".", "-");
dom.classList.add(onlyKey);
this.lastTop = -5;
if (this.msgPool.length) {
const lastDom = this.msgPool[this.msgPool.length - 1].dom;
this.lastTop = this.getElTop(lastDom);
}
this.msgPool.push({ dom, onlyKey });
setCss(dom, {
top: `${this.lastTop}px`,
});
document.body.appendChild(dom);
setCss(dom, {
top: `${this.getElHeight(dom) * this.msgPool.length}px`,
});
this.destroyMsg(); // 销毁弹窗
}
info(content) {
const msg = this.msg(content, "info");
this.append(msg);
}
success(content) {
const msg = this.msg(content, "success");
this.append(msg);
}
warning(content) {
const msg = this.msg(content, "warning");
this.append(msg);
}
error(content) {
const msg = this.msg(content, "error");
this.append(msg);
}
/**
* 销毁弹窗
*/
destroyMsg() {
const that = this;
setTimeout(() => {
const { dom } = this.msgPool.shift();
this.msgPool.forEach((item, index) => {
setCss(item.dom, {
top: `${that.getElHeight(item.dom) * (index + 1)}px`,
});
});
setCss(dom, { opacity: 0, top: `-53.6px` });
function transitionEnd() {
dom.remove();
dom.removeEventListener("transitionend", transitionEnd);
}
dom.addEventListener("transitionend", transitionEnd);
}, this.duration);
}
getElHeight(dom) {
return dom.clientHeight + 10;
}
/**
*
* @param {HTMLElement} dom
* @return {number}
*/
getElTop(dom) {
if (typeof dom === "object") {
return dom.getBoundingClientRect().top;
}
}
}
class Dialog extends createPopup {
/**
* @constructor
* @param {object} param
* @param {function} [param.closeCallback=null] 关闭时回调
* @param {fucntion} [param.cancelCallback=null] 取消时回调
* @param {function} [param.confirmCallback=null] 确认时回调, 注意:确认并不会自动关闭窗口,需要调用回调参数关闭对话框
* @param {string} [param.themeMode='light'] 主题模式,默认为亮色主体 ['light','dark']
* @param {boolean} [param.isShowCancel=true] 是否显示取消按钮 如果不显示(false) 确认按钮可自动关闭弹窗
* @example
* new Dialog({
* confirmCallback:(offDialog)=>{
* offDialog() // 关闭对话框
* }
* })
*
*/
constructor(param) {
const baseParam = {
...{
closeCallback: null,
cancelCallback: null,
confirmCallback: null,
isShowCancel: true,
themeMode: "light",
},
...param,
};
super({
themeMode: baseParam.themeMode,
isShowCancel: baseParam.isShowCancel,
});
this.closeCallback = baseParam.closeCallback;
this.cancelCallback = baseParam.cancelCallback;
this.confirmCallback = baseParam.confirmCallback;
this.isShowCancel = baseParam.isShowCancel;
this.themeMode = baseParam.themeMode;
this.mask = null;
}
/**
* @function
* @param {object} param
* @param {any} [param.title="标题"] 弹窗标题 如不填则显示:'标题', 可自定义填入dom
* @param {any} param.content 弹窗内容, 可自定义内容 包括不限于字符串、dom等
* @param {any} param.cancelText 取消按钮文本 可自定义内容 包括不限于字符串、dom等
* @param {any} param.confirmText 确认按钮文本 可自定义内容 包括不限于字符串、dom等
* @param {boolean} param.isShowCancel 是否显示取消按钮 默认为true
* @param {string} param.themeMode 弹框主题 选项为["light",'dark'] 默认为 light
* @param {function} param.confirmCallback 确认时触发回调
* @param {function} param.cancelCallback 取消时触发回调
* @param {fucntion} param.closeCallback 关闭时触发回调
*/
modal({
title,
content,
cancelText,
confirmText,
isShowCancel,
themeMode,
confirmCallback,
cancelCallback,
closeCallback,
}) {
this.openMask();
const { modal, cancel, confirm, close, onlyKey } = this.createModal({
title: title ? title : "标题",
content,
cancelText: cancelText ? cancelText : "取消",
confirmText: confirmText ? confirmText : "确认",
themeMode,
isShowCancel,
});
document.body.appendChild(modal);
this.modalDom = modal;
setTimeout(() => {
setCss(modal.querySelector(".ppp-modal-" + onlyKey), {
opacity: "1",
transform: "translateY(-50%) scale(1)",
});
}, 0);
const that = this;
cancel.addEventListener("click", () => {
that.cancel({ cancelCallback });
});
confirm.addEventListener("click", () => {
that.confirm({
isShowCancel,
confirmCallback,
});
});
close.addEventListener("click", () => {
that.close({
closeCallback,
});
});
}
offMask() {
if (this.mask) {
setCss(this.mask, {
opacity: "0",
});
const mk = this.mask;
setTimeout(() => {
mk.remove();
}, 300);
slidingToggle(true);
this.mask = null;
}
}
openMask() {
// mask
const modal_mask = document.createElement("div");
modal_mask.classList.add("ppp-modal_Mask");
document.body.appendChild(modal_mask);
this.mask = modal_mask;
slidingToggle(false);
setTimeout(() => {
setCss(this.mask, {
opacity: "1",
});
}, 0);
}
offModal() {
this.offMask();
setCss(this.modalDom.querySelector(".ppp-modal"), {
opacity: "0",
});
setTimeout(() => {
this.modalDom.remove();
}, 0);
}
cancel(opt) {
this.offModal();
if (typeof this.cancelCallback === "function") {
this.cancelCallback();
}
if (typeof opt.cancelCallback === "function") {
opt.cancelCallback();
}
}
confirm(opt) {
const that = this;
if (!this.isShowCancel || !opt.ishowCancel) this.offModal();
if (typeof this.confirmCallback === "function") {
this.confirmCallback(() => {
that.offModal();
});
}
if (typeof opt.confirmCallback === "function") {
opt.confirmCallback(() => {
that.offModal();
});
}
}
close(opt) {
this.offModal();
if (typeof this.closeCallback === "function") this.closeCallback();
if (typeof opt.closeCallback === "function") opt.closeCallback();
}
}