用jQuery编写放大镜效果以及图片异步加载的测试

自创jQuery放大镜效果,主要运用offset()、width()、height()、e.pageX、e.pageY等关于位置的方法。

核心思想:缩略图的尺寸与放大图尺寸的比值 = 放大镜相对缩略图的坐标X(或Y)/放大图相对包裹它的外框坐标X(或Y)的比值

其中有几个关键点需要注意,还有两个bug,调试过程没少为此绞尽脑汁,算是从坑里爬出来,最终得到这样一套程序。问题值得思考、研究和总结,代码还需不断重构、优化。

关键点

包裹缩略图的元素和包裹放大图的元素宽高都通过js计算获得,以便程序可以复用,换成任意图片都有效。

 

function imgScale($big, $small) {
	// 必须先把元素插入文档才能获取大小
	var big_width = $big.find("img").width();
	var small_width = $small.find("img").width();
	var scaleX = big_width/small_width;
	var big_height = $big.find("img").height();
	var small_height = $small.find("img").height();
	var scaleY = big_height/small_height;
	return [scaleX, scaleY];
}

2. 缩略图宽高用outerWidth()和outerHeight(),从而计算放大镜的位置时,放大镜可以靠到缩略图的最边上。

 

3. 放大图预览区域要三层div,最外层是预览区内容框,设置position: absolute,使预览区域脱离文档流,不会遮盖缩略图旁边的产品介绍;最内层是图片元素img,因为里面的图片要根据放大镜的移动而移动,所以也需要设置position: absolute;问题来了,图片要相对预览区内容框移动,我们都知道absolute是相对于第一个设置了relative的父元素,但预览区内容框也是absolute,所以要在中间加一层div,设置position: realative。

 

var $zoom_window = $("<div id ='zoom-window'><div><img src='images/product_image_big.jpg'></div></div>");
/*放大展示区*/
#shop-details {
	margin-left: 330px;
}
#zoom-window {	
	position: absolute;   /* 不占用文字空间 */
	overflow: hidden;
	border: 1px solid #ccc;
}
#zoom-window>div {
	position: relative;
}
#zoom-window img {
	position: absolute;
}

 

4. offset()方法是获取元素相对文档的偏移,获取缩略图的位置是相对文档的。这个相对位置是会随着浏览器的放大缩小而变化的,所以我们要把获取缩略图的运算放在事件回调函数内。这样,每次触发事件时会同时计算缩略图、放大镜元素的位置以及鼠标的event.pageX和event.pageY(也是相对于文档),保持一致。即使改变浏览器窗口大小,反正每次都会计算,保证不会出错。如果放在全局作用域,在页面加载时就计算出缩略图的位置,定死了,不会响应浏览器窗口的变化。

优化从性能上看,每次鼠标滑入事件都计算位置,性能降低。可以增加一个windown.onresize()事件,实时计算缩略图的位置。

debug

1. 放大镜在缩略图内移动,会不停地闪

经过排查,发现是与放大镜元素插入的节点及mouseleave事件绑定的元素有关。

放大镜元素插入在缩略图主div的下一层级,mouseleave事件要绑定在放大镜的父元素(本例中是缩略图img的最外层div),而不能绑定在放大镜的兄弟元素或子元素(比如绑定在a元素)。

 

<div id="product-zoom">
    <a href="images/product_image_big.jpg" title="毛呢外套中长款过膝呢大衣">
        <img src="images/product_image_small.jpg" alt="毛呢外套中长款过膝呢大衣">
    </a>
</div>
$parent.mouseleave(function() {
		$(this).find("a").attr("title", this.myTitle);
		$m_glass.remove();
		$zoom_window.remove();
	});

 

表面上看,绑定在外层div、a、img都可以,因为它们尺寸一样大,鼠标离开触发放大镜消失的事件效果一样。

实际上,如果绑定在兄弟元素或子元素,鼠标每移动一下,指针还在放大镜元素之内,程序认为它离开了兄弟元素(例如a元素)或子元素(例如image)的,就要触发mouseleave 放大镜消失,放大镜一消失,指针还处于缩略图之内,又触发了mouseenter事件,放大镜出现。如此死循环。

2. 放大区图片加载的问题

放大区图片的src链接根据缩略图的链接href获得(因为用户会随时更换查看缩略图),因此要在mouseenter事件内加入获取放大区图片src这条代码。

最初js内在全局作用域创建放大区元素:

 

var $zoom_window = $("<div id ='zoom-window'><div><img src='images/product_image_big.jpg'></div></div>");

图片是包含了src属性的。

 

后来想着反正每次mouseenter事件会从缩略图获取放大区图片的src,就把这里的src取消了。

问题就出现了,鼠标第一次滑入,放大区无显示。

又是debug时间:

打断点,鼠标第一次滑过,查看图片的大小和图片外层包裹框的大小,0! 第二次之后,恢复正常!

分析原因:

获取预览图链接,插入。如果此时才是第一次添加src加载图片,由于是异步加载,根据图片的大小计算预览窗口的尺寸会先于图片完成,此时图片尺寸为0,则计算出的预览窗口的尺寸为0,稍后图片会加载。所以第一次看不到放大区的原因是,计算出的预览窗口的尺寸为0,图片设置了overflow: hidden属性,就一点都看不到了。

第二次mouseenter时图片已加载完成,计算出了需要的预览窗口,图片正常显示。

针对JavaScript(Jquery)图片异步加载的问题,编写了一个测试代码,如下:

得到结论:

1. 如果jQuery对象创建了但还没插入DOM树,使用jQuery的width()获取不到尺寸;

2. jQuery对象(图片链接)插入DOM树之之后,因为图片异步加载的原因,jQuery的width()和原生js的offsetWidth、clientWidth不能立即获取尺寸;

3. 下一线程,jQuery的width()和原生js的offsetWidth、clientWidth可以获取到图片尺寸。

解决方法:事件需要获取图片尺寸,则在之前的页面加载时就创建图片的jQuery对象。

<!DOCTYPE html>
<html>
    <head>
        <meta chardelete=utf-8 />
        <title>关于页面图片加载的分析</title>
        <script src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
    </head>
    <style>
        li {
            font-size: 14px;
            line-height: 1.5;
        }
        #img-box1,
        #img-box2,
        #img-box3 {
            position: absolute;
            width: 300px;
            height: 300px;
            overflow: hidden;
        } 
        #img-box1 {
            left: 10%;
        }
        #img-box2 {
            left: 40%;
        }
        #img-box3 {
            left: 70%;
        }
        #img-box1 > div,
        #img-box2 > div,
        #img-box3 > div {
            position: relative;  
        }
        #img-box1 > img,
        #img-box2 > img,
        #img-box3 > img {
            position: absolute;
        }
        .point1,
        .point2,
        .point3 {
            height:100px;
            width: 100px;
            position: absolute;
            bottom: 5%;
            background: red;
            white-space: pre-line;
            color: #000;
            cursor: pointer;
        }
        .point1 {
            left: 10%;
            background: #b2d;
        }
        .point2 {
            left: 40%;
            background: #cd6;
        }
        .point3 {
            left: 70%;
            background: #fcc;
        }
    </style> 
    <body>
        <h3>按以下要求执行$img_box~$img_box10相关的$(function(){})函数</h3>
        <ol>       
            <li>只执行第1个函数(浏览器加载时创建一个临时的div,加载待显示的本地大图),注释掉其余,刷新网页,鼠标滑过3个块,查看图片显示和控制台</li>
            <li>只执行第2个函数(浏览器加载时创建一个临时的div,加载待显示的本地小图),注释掉其余,刷新网页,鼠标滑过3个块,查看图片显示和控制台</li>
            <li>只执行第3个函数(浏览器加载时创建一个临时的div,加载待显示的网络图片),注释掉其余,刷新网页,鼠标滑过3个块,查看图片显示和控制台</li>
            <li>只执行第4个函数(浏览器加载时创建与待显示图片不同路径的图片),注释掉其余,刷新网页,鼠标滑过3个块,查看图片显示和控制台</li>
            <li>只执行第5个函数(浏览器加载时创建一个无src路径的img元素),注释掉其余,刷新网页,鼠标滑过3个块,查看图片显示和控制台</li>
            <li>都不执行</li>
        </ol>
        
        <div id="container"></div>

        <div class='point1'>
            要显示的图片为相对路径,超大尺寸
        </div>
        <div class='point2'>
            要显示的图片为相对路径,小尺寸
        </div>
        <div class='point3'>
            要显示的图片为网络路径
        </div>


        <script>
            $(function () {        
                var $img_box1,
                    $img_box2

                 // mouseenter事件创建并插入相对路径的图片元素__超大尺寸
                $('.point1').mouseenter(function () {
                    $img_box1 = $("<div id ='img-box1'><div><img src='images/superbig-image.jpg'></div></div>");
                    // 如果jQuery对象创建了但还没插入DOM树,使用jQuery的width()获取不到尺寸
                    console.log('插入DOM树之前jQuery对象本地大图的宽度:' + $img_box1.width());  
                    // 插入DOM树之之后,jQuery的width()和原生js的offsetWidth、clientWidth获取尺寸
                    $img_box1.appendTo('#container'); 
                    console.log('插入DOM树之后本地大图的宽度' + document.querySelector('#img-box1 img').clientWidth + '\n');   
                }).mouseleave(function () {
                    $img_box1.remove();
                });    

                 // mouseenter事件创建并插入相对路径的图片元素--小尺寸
                $('.point2').mouseenter(function () {
                    $img_box2 = $("<div id ='img-box2'><div><img src='images/small-image.jpg'></div></div>");
                    console.log('插入DOM树之前jQuery对象本地小图的宽度:' + $img_box2.width());
                    $img_box2.appendTo('#container'); 
                    console.log('插入DOM树之后本地小图的宽度:' + document.querySelector('#img-box2 img').clientWidth + '\n'); 
                }).mouseleave(function () {
                    $img_box2.remove();
                });  

                // mouseenter事件创建并插入网络url的图片元素
                $('.point3').mouseenter(function () {
                    $img_box3 = $("<div id ='img-box3'><div><img src=''></div></div>");
                    $img_box3.find('img').attr('src','http://www.baidu.com/img/bdlogo.gif');
                    console.log('插入DOM树之前jQuery对象网络图片的宽度:' + $img_box3.width());
                    $img_box3.appendTo('#container') 
                    console.log('插入DOM树之后网络图片的宽度:' + document.querySelector('#img-box3 img').clientWidth + '\n');
                }).mouseleave(function () {
                    $img_box3.remove();
                });
            });
                
            
            // 浏览器加载时创建一个临时的div,加载待显示的本地大图
            $(function () {
                var $img_box6 = $("<div id ='img-box6'><img src='images/superbig-image.jpg'></div>");
                console.log('that big size local image loaded');  
                // 第一次插入DOM之后大图有宽度,插入之前没有;其它图都没宽度
                // 第二次所有图插入之前无宽度,插入之后有  
            })

            // 浏览器加载时创建一个临时的div,加载待显示的本地小图
            $(function () {
                var $img_box7 = $("<div id ='img-box7'><img src='images/small-image.jpg'></div>");
                console.log('that small size local image loaded');
                // 第一次插入DOM之后小图有宽度,插入之前没有;其它图都没宽度
                // 第二次所有图插入之前无宽度,插入之后有  
            })

           // 浏览器加载时创建一个临时的div,加载待显示的网络图片*/
            $(function () {
                var $img_box8 = $("<div id ='img-box8'><img src='http://www.baidu.com/img/bdlogo.gif'></div>");
                console.log('that url image loaded');
                // 第一次插入DOM之后网络图有宽度,插入之前没有;其它图都没宽度
                // 第二次所有图插入之前无宽度,插入之后有                
            })

            // 浏览器加载时创建与待显示图片不同路径的图片
            $(function () {
                var $img_box9 = $("<div id ='img-box9'><img src='images/another-image.jpg'></div>");
                console.log('element of image with another src created'); 
                // 第一次插入DOM之后、之前所有图都没宽度
                // 第二次所有图插入之前无宽度,插入之后有                       
            })

            // 浏览器加载时创建一个无src路径的img元素
            $(function () {
                var $img_box10 = $("<div id ='img-box10'><img src=''></div>");
                console.log('element of image with empty src created');
                // 第一次插入DOM之后、之前所有图都没宽度
                // 第二次所有图插入之前无宽度,插入之后有                  
            })   
        </script>       
    </body>   
</html>


最后是放大镜demo程序:

 

HTML如下:

 

 

<!DOCTYPE HTML>
<html>
	<head>
		<meta charset=utf-8 />
		<title>zoom effects</title>
		<link rel="stylesheet" href="style/zoom.css" />
		<script type="text/javascript" src="scripts/jquery.js"></script>
	</head>
	<body>
	    <div id="product-zoom">
	        <a href="images/product_image_big.jpg" title="毛呢外套中长款过膝呢大衣">
	            <img src="images/product_image_small.jpg" alt="毛呢外套中长款过膝呢大衣">
	        </a>
		</div>
		<div id="shop-details">
		</div>
	<script type="text/javascript" src="scripts/zoom.js"></script>
	</body>
</html>

CSS如下:

 

 

a img {
    display: block;
}

/*产品缩略图浏览*/
#product-zoom {
	float: left;
	width: 320px;
	border: 1px solid #bbb;
	cursor: crosshair;
}
#product-zoom img {
	width: 100%;
}
#m-glass {
	width: 120px; 
	height: 120px;
	opacity: 0.5;
	background: #e5e4e2;
	position: absolute;
}

/*放大展示区*/
#shop-details {
	margin-left: 330px;
}
#zoom-window {	
	position: absolute;   /* 不占用文字空间 */
	overflow: hidden;
	border: 1px solid #ccc;
}
#zoom-window>div {
	position: relative;
}
#zoom-window img {
	position: absolute;
}

JavaScript如下:

 

 

$(function () {
	var $parent = $("#product-zoom");
	//现有展示栏的位置、宽高,不能只是内容的高度
	var dpWidth =  $parent.outerWidth();
	var dpHeight = $parent.outerHeight();
	//放大镜的大小
	var mSize = 120;
	//创建放大镜
	var $m_glass = $("<div id ='m-glass'></div>");
	//创建预览区域,双层div。因为图片的父元素必须relative,但relative会占据原来位置,所以再在外面加一层div,设为absolute
	//要先把src引入,否则第一次不能出现预览框,为什么????
	var $zoom_window = $("<div id ='zoom-window'><div><img src='images/product_image_big.jpg'></div></div>");
	//可不可以不设为全局变量?
	var dpPos = null;
	var scale = [];	
	$("#product-zoom a").mouseenter(function (e) {
		//获取光标位置
		this.myTitle = this.title;
		this.title = "";
		//不能放在全局变量,不然浏览器加载后就成为固定值,窗口缩小再mouseenter位置不对
		dpPos = $parent.offset();
		//改变放大镜的位置
		showGlass(e, dpPos, dpWidth, dpHeight);
		//插入放大镜
		$m_glass.appendTo("#product-zoom");
		//获取预览图链接,插入
		// 获取预览图链接,插入。如果此时才是第一次添加src加载图片
		// 由于是异步加载,根据图片的大小计算预览窗口的尺寸会先于图片完成
		//  此时图片尺寸为0,则计算出的预览窗口的尺寸为0
		//  第二次mouseenter时图片已加载完成,计算出需要的预览窗口
		$zoom_window.find('img').attr('src',this.href);
		$zoom_window.appendTo("#shop-details");
		// 先插入文档,才能获得图片大小。设置预览图的大小
		scale = imgScale($zoom_window, $parent);
		$zoom_window.width(scale[0] * mSize + "");
		$zoom_window.height(scale[1] * mSize + "");
	});

	$("body").on("mousemove", "#m-glass", function(e) {
		//改变放大镜的位置
		showGlass(e, dpPos, dpWidth, dpHeight);
		var left = dpPos.left;
		var top = dpPos.top;
		//设置预览图片的位置
		var absLeft = -(e.pageX - mSize/2 - left)*scale[0];
		var absTop = -(e.pageY - mSize/2 - top)*scale[1];
		var ultRight = -($zoom_window.find("img").width() - $zoom_window.width());
		var ultBottom = -($zoom_window.find("img").height() - $zoom_window.height());
		$zoom_window.find("img").css({
		    "left": (Math.max(Math.min(0, absLeft), ultRight)) + "px",
		    "top": (Math.max(Math.min(0, absTop), ultBottom)) + "px"
		});
	});

	// mouseleave事件要绑定在放大镜的父元素,不能绑定在兄弟元素或子元素。
	// 这样的话,鼠标每移动一下,程序认为它在放大镜$m_glass之内,
	// 是离开了兄弟元素(例如a元素)或子元素(例如image)的,就要触发mouseleave
	// 放大镜消失,放大镜一消失,就触发了mouseenter事件,放大镜出现。如此死循环。

	// 由于用函数给框框设置了界限,鼠标怎么动框框都在图片范围内,这样鼠标指针就可以脱离框框,实现解除绑定
	$parent.mouseleave(function() {
			$(this).find("a").attr("title", this.myTitle);
			$m_glass.remove();
			$zoom_window.remove();
		});

	// 改变放大镜位置的函数
	function showGlass(e, dpPos, dpWidth, dpHeight) {
		var mLeft = Math.min(Math.max((e.pageX - mSize/2), dpPos.left), 
			                          (dpPos.left + dpWidth - mSize));
		var mTop = Math.min(Math.max((e.pageY - mSize/2), dpPos.top), 
			                         (dpPos.top + dpHeight - mSize));
		$m_glass.css({
		    "left": mLeft + "px",
		    "top": mTop + "px"
		});
	}
	//预览区大小:大图/小图*放大镜尺寸
	function imgScale($big, $small) {
 		// 必须先把元素插入文档才能获取大小
 		var big_width = $big.find("img").width();
		var small_width = $small.find("img").width();
		var scaleX = big_width/small_width;
		var big_height = $big.find("img").height();
		var small_height = $small.find("img").height();
		var scaleY = big_height/small_height;
		return [scaleX, scaleY];
	}
});

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值