再谈弹窗那些事

再谈弹窗那些事

很早之前对 jQuery 略懂的时候,写过一篇简单实现弹窗功能的文章,而且“不害怕”的发表到了我爱水煮鱼上。很基础,很简单,里面的有些做法都是不成熟的,只能说简单的实现了那么一个弹窗效果。

虽然写的很基础很一般,但是从统计数据来看,这篇文章还是有很多人搜索很多人看,再加上到现在过了的这一年多,做过很多遍弹窗功能,对简单的弹窗效果也有了进一步的理解,再次总结一篇更加深入的文章。

弹窗的实现原理和方法

弹窗通常就是两部分,一部分是半透明的背景遮罩,另一部分就是承载主体内容的区域。当点击某个按钮或者某个地方触发,将隐藏的遮罩和内容通过某种动画效果显示出来。然后点击内容中的关闭或者遮罩区域,就会将遮罩和内容通过某种动画效果隐藏起来。

所以,实现弹窗第一步,就是要写结构和 CSS。

弹窗的 HTML 结构和 CSS

既然知道实现方法,一般的结构主要有下面两种(分情况使用):

第一种,将内容块独立出来,再声明一个 .overlay 结构用来做背景遮罩。结构如下:

<div class="box-login">
    ...
</div>

<div class="overlay"></div>

这样的话,就可以为 .overlay 结构设置半透明的背景图片或者使用 rgba 的背景,然后为 .box-login 应用绝对居中的布局模式或者使用 JS 动态计算宽高或者位置。(当然,这种方法的兼容性 IE8+ 特别在乎的,可以使用 JS 来计算位置,以及用半透明的 gif 图片当背景)。

这种结构的优势是,遮罩层可以被复用,任何需要弹窗的场景,都可以直接用 overlay 这个遮罩层,twitter 等用的就是这种,也推荐这种。

具体代码直接看下面的用 jsfiddle 做的 Demo

第二种,将内容块包裹进 .overlay 结构中。结构如下:

<div class="overlay box-login">
    <div class="section">
        ...
    </div>
</div>

这种结构更加独立一些与其他结构相区别,用 JS 来控制也比较统一比较简单,也比较方便用 JS 等插件生成。fancybox 就是生成这种结构来实现弹窗。

但是这里需要注意,由于内容块是被包裹在 overlay 层的,所以要实现点击 overlay 层取消弹窗的效果,需要在内容块中阻止单击或者其他有关事件的冒泡,这样在内容块中的操作,就不会触发取消弹窗的效果了。

具体代码如下,或者打开 Demo

通常还会为弹窗层中添加一个“关闭”按钮,点击关闭按钮就关掉当前弹窗,一般使用下面代码,因为还需要找图标等,就不再上传图片做 Demo 了:

$(‘.close-it‘).on(‘click‘,function(event) {
    $(this).parents(‘.overlay‘).fadeOut(200,function(){
        $(this).removeAttr(‘style‘);
    });
});

还需要注意一个细节,在用 jQuery 做动画的时候,尽量用回调函数清除 jQuery 为其添加的 style 属性,这样可以避免一些意外的问题,流过血的教训。

注意弹窗的内容

其实对于弹窗效果的内容块,初始的时候为其应用 display: none; 是非常简单而合理的,但实际应用还会出现一些意外情况,你需要注意弹窗的内容。

前段时间的项目中,弹窗里面的内容比较复杂,有幻灯片、图表之类的插件,结果出现了问题,幻灯片和图表区域内容会出现错位、变形等情况。后来测试了一下发现了原因,因为这些插件在初始化的时候,需要计算外框或者相关元素的尺寸位置等,如果你为其设置了 display: none; 之后,插件初始化就获取不到正确的数据,就错位或者失效了。

所以使用 display: none; 来隐藏弹窗也是需要分情况讨论的,有些情况下,使用 z-index: -1; 的方式隐藏会更加合理,如果需要做动画,可以设置 opacity 或者当页面加载完之后,相关插件已经获取相关结构的尺寸等之后,使用 setTimeout 函数为其增加 display: none; 相关的类也可以。

提升弹窗的用户体验

上面的弹窗还是比较基础和笼统的,一般来说,根据弹窗内容块的布局,还可以分两类:

弹窗后允许页面滚动

这种方式通常使用 position: absolute; ,可以看看我做的这个 Demo。主要用来应对弹窗内容很大很多的情况,超过了屏幕的宽高需要产生滚动条来方便浏览者查看。有一些图片弹窗插件使用这种方式,使用 JS 动态计算弹窗内容块的位置,这样即便是内容块很大,也不会造成信息缺失。

但是居中往往需要 JS 配合或者进行位置、尺寸处理,会稍微麻烦一些。

弹窗后不允许页面滚动

你可以通过为内容块设置 position: fixed; 使其虽然滚动了,但内容块仍然居中显示,给人一种没有滚动的感觉,还是刚刚的 Demo 注释掉然后修改一下就可以看到。

也可以为 html、body 标签设置 overflow-y: hidden; 属性,使其滚动条消失,无法滚动。

通常来说,一般都要使用 hidden 来隐藏滚动条禁止滚动,然后使用 absolute 或者 fixed 就看你自己的需求来选择。但是,对 html 或 body 设置 overflow-y: hidden; 之后,由于滚动条没有了,宽度变宽,页面内容会整体偏移一点,虽然只是一点,但是很明显。这简直太影响用户体验了!!

去掉滚动条但是避免页面内容偏移

知乎上前段时间也提到了这个问题:如何禁止浏览器滚动条滚动,但是又不让它消失?。其中比较简单方便的就是 TQ 学长的回答,但是实际测试并不完美,因为 chrome 浏览器的滚动条是 15px 像素宽,而 firefox 浏览器是 17px 像素。如果统一设置成 17px 的话,chrome 等浏览器中显然还会偏移 2px ,虽然尽力完善了,但还是有点小偏移,受不了。

既然不同浏览器里面滚动条宽度不同,那可否先用 JS 检测操作系统和浏览器,然后再根据判断的浏览器等设置不同的偏移量?大体思路是对的,但是方法是错的。检测操作系统和浏览器,要判断的情况太多,代码太过于复杂。于是这个问题就暂时放在这里了。

偶然刷 twitter 的时候,触发弹窗效果的时候,偶然看到了他们也是采用这种方式隐藏滚动条并且保证不会偏移,经过测试各个浏览器中效果完美。于是分析了一下他们的代码,下面是他们的函数:

function ScrollbarWidth() {
    this.calculateScrollbarWidth = function() {
        if ($("#scrollbar-width").length > 0) return;
        var a = $(‘<div class="modal-measure-scrollbar"/>‘).prependTo($("body")),
        b = $(‘<div class="inner"/>‘).appendTo(a),
        c = a.width() - b.width();
        a.remove(),
        $("head").append(‘<style id="scrollbar-width">        .compensate-for-scrollbar,        .modal-enabled,        .modal-enabled .global-nav-inner,        .profile-editing,        .profile-editing .global-nav-inner,        .overlay-enabled,        .overlay-enabled .global-nav-inner,        .grid-enabled,        .grid-enabled .global-nav-inner,        .gallery-enabled,        .gallery-enabled .global-nav-inner {          margin-right: ‘ + c + "px      }      </style>")
    }
}

精简一下主要代码就是

var a = $(‘<div class="modal-measure-scrollbar"/>‘).prependTo($("body")),
b = $(‘<div class="inner"/>‘).appendTo(a),
c = a.width() - b.width();
a.remove();
$("head").append(‘<style id="scrollbar-width"> .compensate-for-scrollbar{ margin-right:‘ + c + ‘px } </style>‘);

大体意思就是新建了一个包裹的结构,然后两个宽度相减得到滚动条的宽度,然后输出到 head 中。当有弹窗发生之后,就为 html 标签赋予相应的类来 margin-right 一下,避免滚动条消失引起的内容偏移。

Great!这才是正解,显然这两个结构需要赋予一定的 CSS,目的要使 .modal-measure-scrollbar产生滚动条,而 .inner 是不包括滚动条的全宽,他们的差值正好就是滚动条的宽度!

twitter 中对它们应用的样式如下:

.modal-measure-scrollbar{
    position: absolute;
    height: 100px;
    width: 100px;
    top: -300px;
    left: -300px;
    overflow: scroll;
    z-index: 1000;
    overflow-y: scroll;
}
.modal-measure-scrollbar .inner{
    height: 200px;
}

不需要解释了吧。

后来又测试了 fancybox 插件,它的方法更加简单通用,具体代码如下:

w1 = $(window).width();
H.addClass(‘fancybox-lock-test‘);
w2 = $(window).width();
H.removeClass(‘fancybox-lock-test‘);
$("<style type=‘text/css‘>.fancybox-margin{margin-right:" + (w2 - w1) + "px;}</style>").appendTo("head");

其中 H 变量是 $(‘html‘) ,大意就是先检测现有宽度,然后再为 html 增加一个类,再检测,然后复原去掉类得到滚动条宽度。大体想一下也可以知道 .fancybox-lock-test 类的代码是这样的:

.fancybox-lock-test {
    overflow-y: hidden !important;
}

这种方法更加巧妙简单,不需要创建临时的 DOM 结构,所以强烈推荐在项目中使用这种方式判断。

此外,还有第三种方法,是来自 QQ 空间的。QQ 空间的相册之类的,也是采用弹窗的,查看了一下他们的实现方式,就是使用的固定的 17px 的偏移值,在其他浏览器中也是 17px 像素。但是使用中很难看到内容的偏移,原因是背景遮罩层太黑了,透明度不高,所以很多细节就忽略掉了。如果你的背景遮罩不是很透明,显然也就不需要处理这了~

再谈弹窗那些事

时间: 2024-11-12 05:37:29

再谈弹窗那些事的相关文章

从飞信群再谈时间管理

收邮件啊 快收邮件~取消飞信小群的当天晚上,便有几位小组组长跑到某某那如是说.虽然我没有做过调查,但是看到这样的情景,我想她们应该和我有一样的感觉,没有了飞信小群在一定程度上,不 -方 -便- 但是过了这一段时间之后,发现我们日常的学习并没有受到什么影响,反倒是比之前明显的改善了.下面谈谈我对这件事的看法. 首先我们还是有飞信大群的.因为不会经常通知,也就谈不上打扰,而且也保证了紧急情况下的及时性和效率.可小群不一样.小群的人数不多,但通知频繁.这样问题就随之来了.最近我的体会 优点一减少打扰

GoF设计模式三作者15年后再谈模式

Erich Gamma, Richard Helm, 和 Ralph Johnson在GoF设计模式发表15年以后,再谈模式,另外一位作者,也是四色原型的发明者Peter已经过世. 提问者:如今有85,000 iPhone的小应用遍布全球,使用PHP就能够写一个简单的"Hello, World! The time is X"Web网页,那么,面向对象设计是难的,这句话是否还正确呢? Richard Helm: 软件设计总是很难的,尽管大多数现代开发环境已经降低了复杂性,通过重用库和工具

再谈协方差矩阵之主成分分析

再谈协方差矩阵之主成分分析 自从上次谈了协方差矩阵之后,感觉写这种科普性文章还不错,那我就再谈一把协方差矩阵吧.上次那篇文章在理论层次介绍了下协方差矩阵,没准很多人觉得这东西用处不大,其实协方差矩阵在好多学科里都有很重要的作用,比如多维的正态分布,再比如今天我们今天的主角——主成分分析(Principal Component Analysis,简称PCA).结合PCA相信能对协方差矩阵有个更深入的认识~ PCA的缘起 PCA大概是198x年提出来的吧,简单的说,它是一种通用的降维工具.在我们处理

再谈angularJS数据绑定机制及背后原理—angularJS常见问题总结

Angular 的数据绑定采用什么机制,详述原理? 脏检查机制.阐释脏检查机制,必须先了解如下问题. 单向绑定(ng-bind) 和 双向绑定(ng-model) 的区别? ng-bind 单向数据绑定($scope -> view),用于数据显示,简写形式是 {{}}. 两者的区别在于页面没有加载完毕 {{val}} 会直接显示到页面,直到 Angular 渲染该绑定数据(这种行为有可能将 {{val}} 让用户看到):而 ng-bind 则是在 Angular 渲染完毕后将数据显示. ng-

996 马云再谈996:理性讨论比结论更重要!

再谈996:理性讨论比结论更重要,周末愉快! 原文出自:马云微博 前几天我在公司内部关于“996”的观点,引起热议,批评声也是源源不断,和我预期的一样.有人奉劝我不要卷入这样的“不正确”话题,不讨人喜欢,主动招骂,还展示了“资本家的獠牙面目”……是在自毁“形象”. 我看了很多网友的回应,特别是骂帖,很多人很失望是因为从我嘴里说出这些“不正确”的话.我很理解这些看法,其实我完全可以说一些“正确的话”.但今天的社会不缺正确的话,我们缺的是实话.真话.让人思考的话.面对年轻人就是面对未来,面对未来我们

Java继承之再谈构造器

目录 Java继承之再谈构造器 初始化基类 默认构造器 带参数的构造器 子类调用父类构造器 Java继承之再谈构造器 初始化基类 前面提到,继承是子类对父类的拓展.<Thinking in Java>中提到下面一段话: 当创建一个导出类的对象时,该对象包含了一个基类的子对象.这个子对象与你用基类直接创建的对象是一样的.二者区别在于,后者来自于外部,而基类的子对象被包装在导出类的对象内部. 我们在创建子类对象时,调用了父类的构造器,甚至父类的父类构造器.我们知道,构造器用于创建对象,那么突然产生

C++ Primer 学习笔记_73_面向对象编程 --再谈文本查询示例

面向对象编程 --再谈文本查询示例 引言: 扩展第10.6节的文本查询应用程序,使我们的系统可以支持更复杂的查询. 为了说明问题,将用下面的简单小说来运行查询: Alice Emma has long flowing red hair. Her Daddy says when the wind blows through her hair, it looks almost alive, like a fiery bird in flight. A beautiful fiery bird, he

C++ Primer 学习笔记_74_面向对象编程 --再谈文本查询示例[续/习题]

面向对象编程 --再谈文本查询示例[续/习题] //P522 习题15.41 //1 in TextQuery.h #ifndef TEXTQUERY_H_INCLUDED #define TEXTQUERY_H_INCLUDED #include <iostream> #include <fstream> #include <sstream> #include <vector> #include <set> #include <map&g

再谈MySQL全库备份

再谈MySQL全库备份 简介 Part1:写在最前 在很早之前,我写过一个MySQL生产库全库备份脚本,今天有同事问我是不是要再加一个-R参数来备份存储过程,理由的话是由于mysqldump --help中 关于存储过程的默认备份是false. routines                          FALSE MySQL生产库全库备份脚本 http://suifu.blog.51cto.com/9167728/1758022 实战 Part1:写在最前 我备份一般就三个参数 --s