之前在做两个相同的页面的事件同步时发现了这个问题,现在把它记录下来。

一、问题描述

  页面中的jqueryui对话框,如果把它拖动到靠近浏览器窗口右侧边缘,并快速从对话框左侧调整对话框窗口大小时,对话框右侧会偏离浏览器窗口右侧边缘,其实就是对话框窗口宽度计算不准确。为了更好地说明问题,下面给出几张示意图。(黑色背景框是浏览器窗口)

  图1、对话框窗口开始放在浏览器右侧边缘,从左侧缓慢调整窗口大小过程中,对话框窗口右侧会发生“抖动”

  

  图2、对话框窗口开始放在浏览器右侧边缘,稍微快一点从左侧调整窗口大小,对话框右侧跟浏览器窗口边缘出现间距

  

  图3、对话框窗口开始放在浏览器右侧边缘,快速从左侧调整窗口大小,对话框右侧的偏离情况更加明显

  

  图4、如果对话框窗口的位置没有靠近浏览器窗口右侧边缘,调整对话框大小的情况正常

  

  以上几张图说明,当对话框初始位置放在浏览器右侧边缘时,从左侧调整对话框大小会出现对话框宽度计算不正确的问题。

  你也可以自己试一试:http://jqueryui.com/dialog/

二、问题分析

  我们知道,鼠标移动的事件并不是连续触发的,即使两次鼠标移动事件之间的间隔很短。所以如果鼠标移动得很快,在两次鼠标移动事件之间鼠标移动的距离会比较大。快速调整对话框大小时,在两次鼠标移动事件触发的间隔中鼠标移动了较长距离。我们可以通过脚本创建并触发事件的方式来模拟快速调整对话框大小的过程,看看在这个过程中执行了什么操作并分析哪里出了问题。

  为了模拟快速从对话框左侧调整大小,下面会依次创建mouseover,mousedown,mousemove事件并在对话框左侧的resize handler上面触发(mousedown和mousemove事件的e.pageX相差较大)。

  图5、对话框的初始宽度和位置

  

  首先在控制台执行下面的代码,依次创建mouseover、mousedown和mousemove事件并触发。

 var mouseover_event = new MouseEvent('mouseover', {
bubbles: true,
clientX: 1389,
clientY: 475,
layerX: 1389,
layerY: 475,
pageX: 1389,
pageY: 475
});
var mousedown_event = new MouseEvent('mousedown', {
bubbles: true,
clientX: 1389,
clientY: 475,
layerX: 1389,
layerY: 475,
pageX: 1389,
pageY: 475
});
var mousemove_event = new MouseEvent('mousemove', {
bubbles: true,
clientX: 1000,
clientY: 475,
layerX: 1000,
layerY: 475,
pageX: 1000,
pageY: 475
});
var resize_handler = document.querySelector('.ui-resizable-handle.ui-resizable-w');
resize_handler.dispatchEvent(mouseover_event);
resize_handler.dispatchEvent(mousedown_event);
resize_handler.dispatchEvent(mousemove_event);

  接着跟踪相应事件处理程序的执行。在jqueryui源代码中,当触发mousemove事件时,下面的的代码片段会执行(添加了实时执行结果的注释):

 _mouseDrag: function(event) {

         var data, props,
smp = this.originalMousePosition, // 鼠标刚按下时的位置(1389, 475)
a = this.axis,
dx = (event.pageX - smp.left) || 0, // -389
dy = (event.pageY - smp.top) || 0,
trigger = this._change[a]; this._updatePrevProperties(); // 设置prevPosition和prevSize,prevPosition.left=1389, prevSize.width=522.6 if (!trigger) {
return false;
} data = trigger.apply(this, [ event, dx, dy ]); // 计算对话框最终应设置的left和width,data={left:1000,width:911.6} this._updateVirtualBoundaries(event.shiftKey);
if (this._aspectRatio || event.shiftKey) {
data = this._updateRatio(data, event);
} data = this._respectSize(data, event); this._updateCache(data); // 更新对话框最终应设置的position和size,执行完之后this.position.left=1000,this.size.width=911.6 this._propagate("resize", event); props = this._applyChanges(); if ( !this._helper && this._proportionallyResizeElements.length ) {
this._proportionallyResize();
} if ( !$.isEmptyObject( props ) ) {
this._updatePrevProperties();
this._trigger( "resize", event, this.ui() );
this._applyChanges();
} return false;
}

  当执行到上面代码的第27行时(this._propagate("resize", event);),用chrome开发者工具跟踪到以下这段代码:

 resize: function( event ) {
var woset, hoset, isParent, isOffsetRelative,
that = $( this ).resizable( "instance" ),
o = that.options,
co = that.containerOffset,
cp = that.position,
pRatio = that._aspectRatio || event.shiftKey,
cop = {
top: 0,
left: 0
},
ce = that.containerElement,
continueResize = true; if ( ce[ 0 ] !== document && ( /static/ ).test( ce.css( "position" ) ) ) {
cop = co;
} if ( cp.left < ( that._helper ? co.left : 0 ) ) {
that.size.width = that.size.width +
( that._helper ?
( that.position.left - co.left ) :
( that.position.left - cop.left ) ); if ( pRatio ) {
that.size.height = that.size.width / that.aspectRatio;
continueResize = false;
}
that.position.left = o.helper ? co.left : 0;
} if ( cp.top < ( that._helper ? co.top : 0 ) ) {
that.size.height = that.size.height +
( that._helper ?
( that.position.top - co.top ) :
that.position.top ); if ( pRatio ) {
that.size.width = that.size.height * that.aspectRatio;
continueResize = false;
}
that.position.top = that._helper ? co.top : 0;
} isParent = that.containerElement.get( 0 ) === that.element.parent().get( 0 );
isOffsetRelative = /relative|absolute/.test( that.containerElement.css( "position" ) ); if ( isParent && isOffsetRelative ) {
that.offset.left = that.parentData.left + that.position.left;
that.offset.top = that.parentData.top + that.position.top;
} else { // 会执行到这里
that.offset.left = that.element.offset().left; // 初始状态的offset,that.offset.left=1389
that.offset.top = that.element.offset().top;
} woset = Math.abs( that.sizeDiff.width +
(that._helper ?
that.offset.left - cop.left :
(that.offset.left - co.left)) ); // 加上that.sizeDiff.width(6.4),woset=1395.4 hoset = Math.abs( that.sizeDiff.height +
(that._helper ?
that.offset.top - cop.top :
(that.offset.top - co.top)) ); if ( woset + that.size.width >= that.parentData.width ) { // 就是这一段出问题,单独把这一段拿出来分析
that.size.width = that.parentData.width - woset;
if ( pRatio ) {
that.size.height = that.size.width / that.aspectRatio;
continueResize = false;
}
} if ( hoset + that.size.height >= that.parentData.height ) {
that.size.height = that.parentData.height - hoset;
if ( pRatio ) {
that.size.width = that.size.height * that.aspectRatio;
continueResize = false;
}
} if ( !continueResize ) {
that.position.left = that.prevPosition.left;
that.position.top = that.prevPosition.top;
that.size.width = that.prevSize.width;
that.size.height = that.prevSize.height;
}
}

  再单独看看上面代码第66行的条件判断语句:

 if ( woset + that.size.width >= that.parentData.width ) {
that.size.width = that.parentData.width - woset;
if ( pRatio ) {
that.size.height = that.size.width / that.aspectRatio;
continueResize = false;
}
}

  that.parentData.width=1920,是指对话框父节点的宽度,也就是document的宽度,

  that.size.width=911.6,前面的代码也有提过,这个是指对话框最终应被设置的宽度,

  woset=1395.4,这个其实就是鼠标一开始按下时鼠标的位置,也就是对话框初始状态时的left值1389,相差的只是that.sizeDiff.width=6.4(上面的代码有提到),

  对话框在初始状态下,因为对话框贴近浏览器右侧边缘,所以  对话框初始left(1389)+对话框初始width(530)=1919(约等于浏览器宽度1920),

  再看看上面条件语句if(woset + that.size.width >= that.parentData.width), that.size.width用的是最新计算出来的对话框最终应被设置的宽度,

  但woset却用了对话框初始left而不是最新计算出来的对话框最终应被设置的left(1000), 这样1395.4 + 911.6 = 2307 > 1920,导致最后这条语句被执行that.size.width = that.parentData.width - woset=1920 - 1395.4 = 524, 对话框最终应被设置的宽度被重置了!

  对话框的宽度最终被设置为524(初始宽度是530),相当于对话框left值改变了,宽度却没有改变,调整大小的操作变成了拖拽的操作。对话框最终的大小和位置如下图6(width和left写反了):

  所以,引起对话框宽度设置不正确的原因是上面那个判断语句中,对话框的宽度用了最新计算出来的宽度,left值却用了之前的值。或者说既然对话框最终应被设置的宽度和位置都已经计算出来了,就不用再做判断和重置对话框宽度了。

  如果慢慢调整对话框大小的话,最新计算出来的left值和之前的left值相差不大,就算执行了宽度重置语句“that.size.width = that.parentData.width - woset;”,最终对话框被设置的宽度也不会相差太大,而且一般情况下鼠标抬起之前的两次mousemove事件鼠标的坐标基本一样或相差很小,也就是说鼠标抬起之前的一次鼠标移动事件中计算出来的对话框应被设置的left值跟前一次被设置的left值基本相等,所以鼠标抬起之前对话框的宽度又被“修正”了。

  在上面的图1中也可以看出,再向左调整对话框大小的过程中,对话框的右侧一直在“抖动”和不断修正的过程,而且随着鼠标移动速度的增大,这种“抖动”过程会更加明显。

  而从上面的图4中我们可以看到,如果对话框的位置不是放在靠近浏览器窗口的右侧边缘,是不会出现宽度设置不正确的问题的。因为这种情况下这个判断语句(woset + that.size.width >= that.parentData.width)的结果不太容易为真,特别是当对话框的位置离浏览器窗口有边缘较远的时候,下面的对话框宽度重置语句也不会执行。

  根据上面的分析,当对话框底部贴近浏览器窗口底部并从对话框上面调整大小,或者对话框右下角贴近浏览器窗口右下角并从左上角方向调整大小时,都会出现对话框宽度或者高度设置不正确的问题。

三、问题修复

  如果把上面的对话框宽度重置语句去掉,或者把woset的值改为用最新的计算出来的对话框left值,就不会出现对话框宽度或者高度设置不正确的问题。如下:

 woset = event.pageX;  // 添加这一句,或者woset = cp
if ( woset + that.size.width >= that.parentData.width ) {
that.size.width = that.parentData.width - woset; // 或者把这一句注释,二选一
if ( pRatio ) {
that.size.height = that.size.width / that.aspectRatio;
continueResize = false;
}
}

  图7、修改了代码之后,无论怎么拖动调整大小,对话框窗口右侧不会发生偏离。

  

  图8、快速调整大小也正常

  

  图9、用脚本触发的方式也正常

  

  至于jqueryui源代码里面为什么要把对话框宽度重置,以及为什么判断时使用“之前的left值”,还没想明白。

四、其他说明

  使用的jqueryui的版本为1.11.4 。

  可能大家会有这样的疑问,即使jqueryui dialog有这样的问题,但是这根本不影响使用,可以说这根本就不算是什么问题,而且有谁会像我这样快速地对对话框进行拖拽?

  但考虑一下下面的场景,我也是在这种情况下发现的这个问题。

  最近我在做两个相同页面之间的事件同步,例如小屏端和大屏端运行同一个页面,我在小屏上做的操作需要同步到大屏上,其实这也是某个项目的需求。这时候我在小屏调整某个对话框的大小,如果实时同步到大屏的话,将会发送大量的请求。为了避免这个问题,就只把鼠标抬起之前的那次mousemove事件发送过去。所以在小屏端完成一次调整大小的操作,会把mouseover,mousedown,mousemove和mouseup事件的信息各发送一次到大屏端,大屏端收到消息之后再触发一次这些事件。这就跟我上面用事件模拟的过程一样,因为jqueryui对话框的重置宽度的问题,小屏端对对话框的调整大小操作,同步到大屏端之后就像是对话框的拖拽操作一样,只是对话框位置改变而大小没有改变。

  在这种情况下,jqueryui对话框的这个问题就会造成比较大的影响。

  

贴近浏览器窗口右侧的jqueryui dialog快速从左侧调整大小时对话框大小设置不准确的问题的更多相关文章

  1. 用css实现一个空心圆,并始终放置在浏览器窗口左下角

    用css实现一个空心圆,并始终放置在浏览器窗口左下角         div{                 position:fixed;                 bottom:0;   ...

  2. 理解javascript中的浏览器窗口——窗口基本操作

    × 目录 [1]窗口位置 [2]窗口大小 [3]打开窗口[4]关闭窗口 前面的话 BOM全称是brower object model(浏览器对象模型),主要用于管理窗口及窗口间的通讯,其核心对象是wi ...

  3. angularjs 中state.go 跳转并且打开新的浏览器窗口

    包子最近遇到业务人员提的非常无厘头的需求,就是调页面的时候,一定要打开一个新的浏览器窗口...>o<奇葩!!! 但是我的页面都是state.go跳转的呀,我各种百度,发现,貌似state, ...

  4. css通用小笔记03——浏览器窗口变小 div错位的问题

    我最近写网页的时候,经常碰到一个普遍的问题,经过我的查阅和尝试,终于解决了这一问题,这里有两种方法提供给大家,如果博友还有更好的方法,欢迎补充. 一.使用min-width属性: 我们先看看下面这段代 ...

  5. div在浏览器窗口中居中

    让div相对于浏览器窗口居中. 方案一:纯粹使用CSS实现 <!DOCTYPE html> <html> <head> <meta charset=" ...

  6. javascript中求浏览器窗口可视区域兼容性写法

    1.浏览器窗口可视区域大小 1.1 对于IE9+.Chrome.Firefox.Opera 以及 Safari:•  window.innerHeight - 浏览器窗口的内部高度•  window. ...

  7. 解决Selenium Webdriver执行测试时,每个测试方法都打开一个浏览器窗口的问题

    虽然把WebDriver定义为一个静态变量了,但是每次执行测试都要打开多个窗口,挺浪费时间的. 找了很多中方法,比如使用setUpClass, BeforeSuite都没有完全解决问题.后来无意间发现 ...

  8. js获取浏览器窗口可视区域大小

    获得浏览器窗口的尺寸(浏览器的视口,不包括工具栏和滚动条)的方法: 一.对于IE9+.Chrome.Firefox.Opera 以及 Safari: •  window.innerHeight - 浏 ...

  9. 关于调整浏览器窗口JS

    有时候需要对浏览器窗口的大小进行元素的操控,当调整窗口大小时用window.onresize=function(){} 页面初始化window.onload=function(){} 要注意的是onr ...

随机推荐

  1. 怎么把QQ我的收藏表情图片转移到另一台电脑上

    把收藏的QQ表情从一台电脑转移到另一台电脑的操作步骤如下:    1.在有表情的电脑登陆QQ,随便打开一个聊天窗口,点击[表情],选择[表情设置],点击[导入导出表情包],选择[导出全部表情包]:   ...

  2. Android Timer的使用

    1:服务端使用PHP <?php echo date('Y-m-d H:i:s'); ?> 2:activity_main.xml <RelativeLayout xmlns:and ...

  3. css3 之表格隔行分色显示

    <html> <head> <title></title> <style type="text/css"> table{ ...

  4. USB的逻辑值和用途值有什么区别?

    用途最小值,用途最大值.逻辑最小值,逻辑最大值分别是什么意思?如题: code uint8 ReportDescriptor[]= { //每行开始的第一字节为该条目的前缀,前缀的格式为: //D7~ ...

  5. PullToRefreshListView 内嵌checkbox 数据丢失问题

    在PullToRefreshListView 内部内嵌了Checkbox如下图所示: 原本设计思路是:对CheckBox 进行 setOnCheckedChangeListener 监听 当Check ...

  6. java并发4-单例设计方法

    单例的设计方式: 第一种:非延迟加载单例类 public class Singleton { private Singleton() {} private static final Singleton ...

  7. bzoj3767 A+B Problem加强版

    Description   Input 输入A,B   Output 输出A+B. Sample Input 1 1 Sample Output 2 HINT 对于100%的数据,保证 |A| , | ...

  8. 素数与素性测试(Miller-Rabin测试)

    转载自Matrix大牛的博客 把代码翻译成C++ http://www.matrix67.com/blog/archives/234 题目链接: http://hihocoder.com/proble ...

  9. tomcat+redis实现session共享缓存

    一:linux下redis安装 1.wget http://download.redis.io/releases/redis-3.2.4.tar.gz 2.tar xzf redis-3.2.4.ta ...

  10. maven+spring mvc初尝试

    只是一个可以运行的例子,俺们来看看. 目录结构: pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xml ...