原生JS弹窗通用组件

需求背景

需求对接的原生官网 公司觉得原生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>

写在最后

  1. 该组件会有两种模式回调:一种是全局回调 一种是局部配置回调
  2. 局部回调一定会触发全局回调 但全局回调不一定触发局部回调
  3. 假如全局回调与局部回调都配置了回调函数 那么这两种回调都将触发

弹窗示例图片

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();
	}
}
相关推荐
涛々几秒前
uniapp-vue3-js-vite-pinia-eslint 快速开发模板
javascript·uni-app·uniapp+vue3模板
解道Jdon10 分钟前
最新苹果液体玻璃Liquid Glass效果CSS实现
javascript·reactjs
啊~哈30 分钟前
页面弹窗适配问题
前端·javascript·vue.js
用户3273242098740 分钟前
logger2js - JavaScript日志与调试工具库
javascript
安然dn1 小时前
Interact.js 一个轻量级拖拽库
javascript
FogLetter1 小时前
从"乱炖"到"法式大餐":Promise如何优雅地管理异步流程
前端·javascript
williamdsy1 小时前
【Vue PDF】Vue PDF 组件初始不加载 pdfUrl 问题分析与修复
前端·javascript·vue.js·pdf
我不吃饼干2 小时前
我给掘金写了一个给用户加标签的功能
前端·javascript·cursor
步行cgn3 小时前
Vue 事件修饰符详解
前端·javascript·vue.js
vvilkim3 小时前
Flutter 状态管理基础:深入理解 setState 和 InheritedWidget
前端·javascript·flutter