原生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();
	}
}
相关推荐
空中海3 小时前
01 React Native 基础、核心组件与布局体系
javascript·react native·react.js
前端之虎陈随易5 小时前
2年没用Nodejs了,Bun很香
linux·前端·javascript·vue.js·typescript
好运的阿财6 小时前
OpenClaw工具拆解之host_workspace_write+host_workspace_edit
前端·javascript·人工智能·机器学习·ai编程·openclaw·openclaw工具
XiYang-DING7 小时前
JavaScript
开发语言·javascript·ecmascript
空中海7 小时前
02 React Native状态、导航、数据流与设备能力
javascript·react native·react.js
空中海8 小时前
02 状态、Hooks、副作用与数据流
开发语言·javascript·ecmascript
空中海9 小时前
04 React Native工程化、质量、发布与生态选型
javascript·react native·react.js
杨超凡9 小时前
豆包收费了?我特么自己用“意念”搓了一个!
javascript
threelab10 小时前
Three.js 咖啡杯烟雾效果 | 三维可视化 / AI 提示词
开发语言·javascript·人工智能