11.弹窗原理详解

在平时中,经常会用组件来定义弹窗,因为弹窗可以用一个div元素浮在页面上。在事实上,弹窗可以简单也可以复杂,简单到只有一个标题,一个文本描述;复杂到可以和一个app一样,拥有前进后退,可以实现复杂业务逻辑。如果按照往常的思路,就会很难扩展。
在弹窗的日常使用中,主要有如下几个情况:

  1. 类似广告弹窗,一直挂在页面上,除非用户手动点击关闭按钮;
  2. 类似提示框,它会停留在页面角落几秒,然后消失。不管页面是否切换它都会在,没有手动关闭按钮;
  3. 类似输入窗口,它会常驻页面上,往往会有个遮罩,告诉用户要把这个窗口完成了才能做其他事情,或者取消,它的优先级很高,无法通过后退键关闭(有时候后退键代表切换页面),后退只会切换页面;
  4. 类似上面的情况,它可以通过用后退键关闭弹窗;
  5. 上面的弹窗拥有随时切换内容的功能,可能是一个弹窗,点击的时候变成一个大表单等;
  6. 弹窗可能会生成弹窗的功能,可以拥有没有上限的弹窗数,通过关闭最底层的弹窗来关闭所有它延伸的弹窗。

上述可能是常见的弹窗例子,还有很多非常规复杂的情况,比如用pc端弹窗模拟移动端窗口等。

在这里我把弹窗分为2类:

  1. 它是由App实例对象弹出来的,不依赖当前显示页面而存在,用户点击后退不会影响到它。它可以通过定时关闭,比如上面的第二种情况,也可以通过用户手动关闭,比如第一种情况。也可以是第三章情况,用户完成任务后才关闭;
  2. 它是由Page实例对象弹出来的,它依赖于当前页面,与history相关的,因此它是可以通过按后退键进行关闭的,上面的3,4,5情况。

弹窗都是可以更改里面的内容的,因此我们把Page页面放在弹窗里面,同时它也有不变的部分。因此,它和之前讨论的App对象是很像的,而且弹窗里面的页面切换,也是会影响整体的history。这样,弹窗就变得灵活性十足。因为Page页面可以弹出弹窗,所以也满足了弹窗弹出新弹窗的需求,而且,很容易应付除此以外的非常复杂的情况(因为弹窗对象和App对象继承于同一个对象,App对象能做的,它都能做);

需求

我们要实现拥有App对象类似功能的PopUp对象,但是它是无法单独存在的,必须依附App实例对象或Page实例对象,它是为了辅助业务开发而存在。但是它拥有管理页面、渲染页面、history对象等功能。

实现思路

我们抽象出一个ReplaceProto对象,它是主要特点就是可以切换页面。然后把App对象和PopUp对象都继承于这个对象。在基类中实现了页面的切换逻辑,页面缓存等基础操作,代码如下

function ReplaceProto(name, staticName, currentName) {
    BaseProto.call(this);
    this.name = name;
    this.history = null; // 无论App还是PopUp都是与History挂钩
    this.options = {};
    this.currentPage = null; // 当前显示的Page对象
    this.staticPage = null; // 布局Page对象

    this.changeArea = null;
    this.data = {};
    // 其它属性
}
// 主要方法
ReplaceProto.prototype = create(BaseProto.prototype, {
    // 渲染页面
    _show: function (bk) {
        var app = this._getApp(), that = this, len = 2;

        function feeback() {
            // 保证是个异步的过程
            requestAnimationFrame(function () {
                bk(that.staticPage, that.currentPage, app)
            });
        }
        [this.staticName, this.currentName].forEach(function (name, index) {
            app.getPageByName(name, function (outPage, opt) {
                var page = new outPage();
                if (index == 0) that.staticPage = page;
                else that.currentPage = page;

                page.baseUrl = getBaseUrl(opt.js);
                for (var key in opt) {
                    if (["title", "js", "name", "url"].indexOf(key) === -1) 
                        page.data[key] = opt[key];
                }
                if (--len === 0) feeback();
            })
        }))
    },
    // 切换页面
    render: function (pagename, isReplace, option) {
        if (this.isRender) return false;  // 防止多次渲染
        this.isRender = true;
        var currentPage = this.currentPage, that = this;
        if (currentPage.popUp) {
            currentPage.popUp.hidden(null, function () {
                that._render(pagename, isReplace, option, 
                    that._renderComplete.bind(that));
            })
        }
        else {
            this._render(pagename, isReplace, option, that._renderComplete.bind(this));
        }
    },
});

由此定义一个PopUp对象,代码如下

function PopUp(name, staticName, currentName) {
    ReplaceProto.call(this, name, staticName, currentName);
    this.history = new HistoryStorage("popup"); // 历史记录
    this.isShow = false; // 是否已弹出,防止多次弹出
    this.hideBack = null; // 关闭后的回调函数,主要是清理历史记录
    this.popDiv = document.createElement("div"); // 弹窗的包围容器
    this.relativeDom = null; 
    this.showTarget = null;  // 是由哪个目标弹出来的
};
// PopUp的主要方法
PopUp.prototype = create(ReplaceProto.prototype, {
    constructor: PopUp,
    // 显示弹窗
    show: function (dom, config, target, isDismisBeforeShow) {
        var that = this, popDiv = this.popDiv
        this.relativeDom = dom;
        this.parent = target;
        this._show(function (staticPage, currentPage, app) {
            if (target.constructor === Page) {
                app.GlobalHistory.addPopUp(that); // 转换为Popup的历史记录
            }
            staticPage.parent = that;
            currentPage.parent = that;
            that.isShow = true;
            dom.parentNode.appendChild(popDiv);
            staticPage.render(function (html) {
                staticPage.initialize(popDiv, html, null, function () {
                    that.changeArea = staticPage.domList.pageContainer || popDiv;
                    currentPage.render(function (htmlstr) {
                        if (target.constructor === Page) 
                            that.history.replaceState(currentPage, config);
                        currentPage.initialize(changeDom, htmlstr);
                    });
                });
            })
        });
    },
    // 弹窗关闭
    hidden: function (option, bk) {
        var that = this;
        if (this.isHidden) return; // 防止多次点关闭
        this.isHidden = true;
        // 如果它有子弹窗,子弹窗先关闭,再关闭它, 保证关闭是一个异步操作
        if (this.currentPage.popUp) {
            this.currentPage.popUp.hidden(null, function () {
                requestAnimationFrame(function () {
                    that._hidden(option, bk);
                });
            });
        }
        else {
            requestAnimationFrame(function () {
                that._hidden(option, bk);
            });
        }
    },
});

因为弹窗只能由Page实例对象和App对象弹出,它们的处理方式不一样的,代码如下

  • Page的showPopUp方法

    showPopUp: function (popupName, data, isDismisBeforeShow, bk) {
        data = data || {};
        if (this.isShowPop) { // 防止一个页面点出多个弹窗
            return false;
        }
        this.isShowPop = true;
        var app = this._getApp(), that = this;
        // 只能通过当前的currentPage弹出
        if (this.parent.currentPage !== this || app.isLock) { 
            this.isShowPop = false;
            return false;
        }
        app.getPopUpByName(popupName, function (popup) {
            var popUp = new popup(data.resetConfig);
            that._showPopUp(app, popUp, data, isDismisBeforeShow, bk);
        });
        return true;
    },
    _showPopUp: function (app, popUp, data, isDismisBeforeShow, bk) {
        var that = this;
        this.isShowPop = false;
        popUp.data = data;
        if (this.popUp) {
            this.popUp.hidden(false, hiddenBack);
        } else {
            hiddenBack();
        }
    
        function hiddenBack() {
            that.popUp = popUp;
            if (popUp.show(app.changeArea || app.staticPage.domList.pageContainer, 
                data.in, that, isDismisBeforeShow)) {
                    
                if (typeof bk === "function") bk(popUp);
                popUp.hideBack = function (bk) {
                    app.removePopUpHistory(bk); // 历史记录清除
                    that.popUp.destroy(); // 弹窗内部引用清除,待垃圾回收
                    that.popUp = null; // 引用弹窗清除
                }
            }
        }
    },
    
  • App的showPopUp方法

    showPopUp: function (popupName, data, isBack, isDismisBeforeShow, bk) {
        var that = this, data = data || {};
        this.getPopUpByName(popupName, function (popup) {
            var popUp = new popup(data.resetConfig);
            that._showPopUp(popUp, data, isBack, isDismisBeforeShow, bk);
        });
        return true;
    },
    _showPopUp: function (popUp, data, isBack, isDismisBeforeShow, bk) {
        var that = this;
        popUp.data = data;
        if (popUp.show(this.changeArea || this.staticPage.domList.pageContainer, 
            data, this, isDismisBeforeShow)) {
            if (typeof bk === "function") bk(popUp);
            // 弹窗列表中添加
            this.showPopups.push({
                back: isBack,
                popUp: popUp
            }); 
            popUp.hideBack = function (bk) {
                popUp.destroy();
                for (var i = 0; i < that.showPopups.length; i++) {
                    if (popUp === that.showPopups[i].popUp) that.showPopups.splice(i, 1);
                }
                if (typeof bk === "function") bk();
            }
        }
    }
    

    Page实例是否在PopUp中,可以通过isInPopUp方法来判断。

实际应用

与当前页面交互
可以在页面中自定义事件,弹窗通过触发自定义事件,并且传递数据进行交互。在页面上定义

this.attachDiyEvent(eventName, handler);

然后在弹窗的页面上触发, this表示当前弹窗的Page对象,parent代表着PopUp对象

this.parent.dispatchEventByName(eventName, data);

弹窗也是按需引入的,因此需要通过配置引入

{
    name: "strdatePicker",
    js: "/public/ui/popup/datePicker/index.js"
}

案例地址

总结

这里主要介绍了PopUp对象的原理,然而弹窗的创建是最复杂的,需要一个PopUp对象和两个Page对象,好在Page对象可以随意切换,有时候创建一个PopUp对象,可以复用不同的Page,灵活性十足,在strui框架中,也有很多不错的弹窗的案例。

推广

底层框架开源地址:https://gitee.com/string-for-100w/string
演示网站: https://www.renxuan.tech/

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个简单的HTML网站弹窗视频播放器,可以兼容移动PC端。 ```html <!DOCTYPE html> <html> <head> <title>弹窗视频播放器</title> <style> .popup { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); z-index: 9999; opacity: 0; transition: opacity 0.5s ease-in-out; display: none; } .popup.show { display: block; opacity: 1; } .popup .content { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; } .popup .content video { max-width: 100%; max-height: 100%; margin: 0 auto; } .close { position: absolute; top: 10px; right: 10px; color: #fff; font-size: 30px; font-weight: bold; cursor: pointer; } </style> </head> <body> <button onclick="openPopup()">点击播放视频</button> <div class="popup" id="popup"> <div class="content"> <video src="video.mp4" controls autoplay></video> <div class="close" onclick="closePopup()">×</div> </div> </div> <script> function openPopup() { document.getElementById("popup").classList.add("show"); } function closePopup() { document.getElementById("popup").classList.remove("show"); } </script> </body> </html> ``` 这个播放器使用了一个按钮来触发弹窗,当用户点击按钮时,弹窗会淡入并显示视频播放器。关闭按钮在视频播放器的顶部右侧,用户可以点击它来关闭视频播放器。 该播放器可以播放MP4格式的视频,并且在移动PC端都能正常显示。同时,播放器使用了CSS渐显效果以增强用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值