在京东和淘宝等购买东西的时候,我们会经常预览左侧商品展示图片,把鼠标放到原图,右侧就会有个大图显示出细节。本文将带领大家写一个这样简单的功能!

一、实现原理

当鼠标移入某一图片内部时,图片上部会出现一个类似于扫描的框,这个框内的图片部分,会以方大形式展示在右边,如下图:

从图中可以推测出一下几点:

图片img上层会有一个父元素(如‘div’),在鼠标移入时,父元素内部添加一个子元素代表扫描框,并且整个body会出现一个固定定位的图片预览盒子定位在右侧(这个图片是另外一张准备好的大图),展示着扫描框中扫描到的图片位置,这个扫描框不能在div内部移动,当鼠标移出图片,扫描框和展示台都消失。

因此我们得到以下布局

<!--整个盒子-->
<div>
<!--图片-->
<img src="...">
<!--扫描框-->
<div class="sweep"></div>
</div> <!--扫描展示区域-->
<div class="show">
</div>

实际情况下,我们不会手动写上 .sweep 和 .show这两个空div,他是由js来实现的。因此,今天我们练习的布局代码如下

<div>
<img src="..." />
</div>

二、准备工作

今天,博主准备了两个练习图片,一个 200*200用作原图, 一个400*400用作大图展示。

html:

<div id="box" data-big-img="goods-big.gif">
<img src="goods.gif" alt="咖啡" />
</div>
#box {
border: 2px solid #000;
width: 200px;
height: 200px;
margin: 0 auto;
}

我们紧紧需要设置图片盒子的样式就可以了,注意: 图片盒子需要把宽高设定为图片大小200*200。

js函数参数选定:

// 将函数命名为zoom,接收两个参数,
// 第一个参数是原图的盒子#box, 第二个是对大图展示台的设定 function zoom (elem, options) {...} // 记住,一切与图片打交道的,都放在window.onload内
window.onload = function () {
var box = document.querySelector('#box')
// 这里我们把展示台设置为图片大小
zoom(box, {
offsetWidth: 200, // 展示台宽度
offsetHeight: 200, // 展示台高度
offsetX: 10, // 展示台相对图片盒子的横向偏移
offsetY: 0 // 展示台相对图片盒子的纵向偏移
})
}

对各个元素尺寸的解释:

原图200* 200, 大图预览是原图的2倍。

扫描框100*100, 是原图的1/2

展示台与原图大小相同,展示台中显示图片为400*400的大图做背景图,控制其背景图的位置来改变展示图的图样。

三、逻辑分析

1. 执行zoom函数,我们需要获取到扫描框和展示台,如果没有,就创建。

2. 给图片添加一个onmouseenter事件,在鼠标移入图片,触发函数,显示展示台和扫描框,并且展示台的图片内容就是扫描框扫描到的图片区域的放大部分。

3. 鼠标移出,展示台和扫描框消失。

4. 鼠标在图片上移动,我们在这里给扫描框添加onmousemove事件,鼠标位置始终在扫描框的中心位置(扫描框紧贴图片一侧除外),只有在鼠标移出了图片区域,鼠标才会离开扫描框。

5. 展示台的图片随着扫描框的移动而变化到相应的部分。

四、编写代码

开始代码如下:

window.onload = function () {
var box = document.getElementById('box') zoom(box, {
offsetWidth: 300,
offsetHeight: 300,
offsetX: 10,
offsetY: 0
})
} function zoom (elem, options) {
// ..
}

之后的代码都会在zoom函数内部。

首先,我们想一下,扫描框.sweep应该在#box内部,其移动是如何实现的,答案是定位,因此#box需要设定为相对定位给.sweep提供环境

// 将盒子设定为相对定位,供之后内部的扫描框用
elem.style.position = 'relative'

由于扫描框的宽高依据图片所定,所以我们先拿到图片的宽高

var innerImg = elem.querySelector('img'),
width = innerImg.offsetWidth,
height = innerImg.offsetHeight

我们需要获取.sweep (扫描框)和 .show(展示台) 两个dom元素

var showBox  = getShowBox()  // 获取展示台盒子
var sweepBox = getSweepBox() // 获取扫描框盒子

由于我们在html没有手动添加两个元素,我们需要先创建他,getShowBox如下:

function getShowBox () {
var showBox = document.querySelector('.xu-show-box')
if (!showBox) {
showBox = document.createElement('div')
showBox.className = 'xu-show-box'
// 糟糕的样式添加操作
showBox.style.width = (options.offsetWidth || 400) + 'px'
showBox.style.height = (options.offsetHeight || 400) + 'px'
showBox.style.position = 'fixed'
showBox.style.left = elem.offsetLeft + elem.offsetWidth + (options.offsetX || 10) + 'px'
showBox.style.top = elem.offsetTop + (options.offsetY || 0) + 'px'
showBox.style.background = 'url(' + elem.getAttribute('data-big-img') + ')'
showBox.style.display = 'none' document.body.appendChild(showBox)
} return showBox
}

我们先获取到展示台元素,如果没有创建,然后定义了一大串css,然后将它加入到body中,我们可以看到一大串的showBox.style很糟糕,我们需要一个css样式修改函数。

function setStyle(elem, props, value) {
if (typeof props === 'object') {
// 传入的对象
for (var key in props) {
elem.style[key] = props[key]
}
} else {
elem.style[props] = value
}
}

我们接下来用setStyle来设定样式,代码变成了如下:

function getShowBox () {
var showBox = document.querySelector('.xu-show-box')
if (!showBox) {
showBox = document.createElement('div')
showBox.className = 'xu-show-box' setStyle(showBox, {
width: (options.offsetWidth || 400) + 'px',
height: (options.offsetHeight || 400) + 'px',
position: 'fixed',
left: elem.offsetLeft + elem.offsetWidth + (options.offsetX || 10) + 'px',
top: elem.offsetTop + (options.offsetY || 0) + 'px',
background: 'url(' + elem.getAttribute('data-big-img') + ')',
display: 'none'
}) document.body.appendChild(showBox)
} return showBox
}

看起来好多了,我们再获取sweep

function getSweepBox () {
var sweepBox = elem.querySelector('.xu-sweep-box')
if (!sweepBox) {
showBox = document.createElement('div')
showBox.className = 'xu-sweep-box' setStyle(sweepBox, {
border: '1px solid #44f',
width: width / 2 - 2+ 'px',
height: height / 2 - 2 + 'px',
background: '#ff0',
opacity: '.4',
position: 'absolute',
display: 'none',
cursor: 'move'
})
elem.appendChild(sweepBox)
}
return sweepBox
}

目前我们的已经获取到了展示台和扫描框,目前的代码如下:

window.onload = function () {
var box = document.getElementById('box') zoom(box, {
offsetWidth: 300,
offsetHeight: 300,
offsetX: 10,
offsetY: 0
})
} function zoom (elem, options) { elem.style.position = 'relative'

var innerImg = elem.querySelector('img'),
   width = innerImg.offsetWidth,
   height = innerImg.offsetHeight

  var showBox  = getShowBox()
var sweepBox = getSweepBox() getShowBox(){...}
getSweepBox() {...}
setStyle(){...}
}

接下来,我们开始书写鼠标事件的逻辑,在这之前,我们想一下我们的需求,以及元素尺寸的概念:

因为扫描框大小是图片大小的一半,因此扫描框定位取值:

left: 0 到 图片宽度的一半(也就是扫描框的宽度)

top: 0  到 图片高度的一半(也就是扫描框的高度)

鼠标移入图片的位置不同,决定扫描框的出现位置:

从左上角移入:左上角

从左下角移入: 左下角

从右上角移入: 右上角

从右下角移入: 右下角

移入的样子如下:

扫描框的运动是否允许,需要对鼠标位置的判断,拿左上角移入举例:如果鼠标移动到扫描框的中心位置并继续向右移动,此时扫描框才会移动,如果鼠一直在扫描框的左上部分移动,扫描框是不会移动的。

接下来我们需要获取如下数据:

扫描框宽高度,扫描框移动的度量宽高度

//  扫描框宽高
var sweepW = width / 2,
sweepH = height / 2,
// 扫描框移动的度量宽高
stepW = sweepW / 2,
stepH = sweepH / 2

此时,我们做好了鼠标移入的准备工作,我们可以开始编写移入事件函数了

elem.onmouseenter = function (ev) {
// 根据鼠标的位置,加载扫描框和展示台
load(ev.offsetX, ev.offsetY)
}

load函数如下:

function load (x, y) {
// 扫描框的横纵坐标偏移量
var offsetX = offsetY = 0
// 不知用什么switch表达式好,所以用了如下方法来判断位置,你有没有好方法?
switch ([(x-sweepW) > 0, (y-sweepH) > 0].join(',')) { case 'false,true':
// 左下
offsetY = sweepH
break;
case 'false,false':
// 左上
break;
case 'true,false':
// 右上
offsetX = sweepW break;
case 'true,true':
// 右下
offsetX = sweepW
offsetY = sweepH
break;
} setStyle(sweepBox, {
left: offsetX + 'px',
top: offsetY + 'px',
display: 'block'
})
// 由于我们起初设定的展示图是原图的2倍,所以偏移都*2
setStyle(showBox, {
backgroundPositionX: offsetX * 2 + 'px',
backgroundPositionY: offsetY * 2 + 'px',
display: 'block'
}) }

加载完毕后,再写鼠标移动事件,根据我们的需求,我们需要根据不同方位,不同鼠标坐标,来判断扫描框是否可运动,我们通过需求分析,我们选择的给扫描框加的鼠标移动事件,如下

sweepBox.onmousemove = function (e) {
if (!isMove(e)) {
return
}
// 鼠标移动的距离
var moveX = e.offsetX - stepW
var moveY = e.offsetY - stepH
// 扫描框的偏移量
var offsetL = this.offsetLeft
var offsetT = this.offsetTop
// 计算出移动的最终坐标
var toX, toY
// 沿x轴往右移动,并且扫描框右边界还没有碰到图片右边缘,那么可以移动,并且移动的距离最远到图片右边缘
if (moveX > 0 && offsetL < sweepW) {
toX = Math.min(offsetL + moveX, sweepW) }
// 与之相反,沿x轴往左移动,那么判断左边界未碰到图片左边缘,移动并且移动最左只能到0
if (moveX < 0 && offsetL > 0) {
toX = Math.max(offsetL + moveX , 0)
}
// y轴雷同
if(moveY > 0 && offsetT < sweepH) {
toY = Math.min(offsetT + moveY, sweepH)
} if (moveY < 0 && offsetT > 0) {
toY = Math.max(offsetT + moveY, 0)
}
// 每次移动,分别设置扫描框和展示台的相应数据
setStyle(this, {
left: toX + 'px',
top: toY + 'px'
})
setStyle(showBox, {
backgroundPositionX: -toX * 2 + 'px',
backgroundPositionY: -toY * 2 + 'px'
}) }
sweepBox.onmousemove = function (e) {
if (!isMove(e)) {
return
}
// 鼠标移动的距离
var moveX = e.offsetX - stepW
var moveY = e.offsetY - stepH
// 扫描框的偏移量
var offsetL = this.offsetLeft
var offsetT = this.offsetTop
// 计算出移动的最终坐标
var toX, toY
// 沿x轴往右移动,并且扫描框右边界还没有碰到图片右边缘,那么可以移动,并且移动的距离最远到图片右边缘
if (moveX > 0 && offsetL < sweepW) {
toX = Math.min(offsetL + moveX, sweepW) }
// 与之相反,沿x轴往左移动,那么判断左边界未碰到图片左边缘,移动并且移动最左只能到0
if (moveX < 0 && offsetL > 0) {
toX = Math.max(offsetL + moveX , 0)
}
// y轴雷同
if(moveY > 0 && offsetT < sweepH) {
toY = Math.min(offsetT + moveY, sweepH)
} if (moveY < 0 && offsetT > 0) {
toY = Math.max(offsetT + moveY, 0)
}
// 每次移动,分别设置扫描框和展示台的相应数据
setStyle(this, {
left: toX + 'px',
top: toY + 'px'
})
setStyle(showBox, {
backgroundPositionX: -toX * 2 + 'px',
backgroundPositionY: -toY * 2 + 'px'
}) }

我们用了isMove函数来判断扫描框是否有权移动,函数如下:

function isMove (e) {
var offsetX = e.offsetX,
offsetY = e.offsetY,
offsetLeft = sweepBox.offsetLeft,
offsetTop = sweepBox.offsetTop
//左上角时,并且鼠标移动的位置小于度量值时,不能移动
if (!offsetLeft && !offsetTop) {
// 左上
if (offsetX < stepW && offsetY < stepH ) {
return false
}
}
// 右上角时,鼠标移动位置x轴方向大于度量值,y轴方向小于度量值,也就是偏右上角,不能移动
if (offsetLeft === sweepW && !offsetTop) {
// 右上
if (offsetY < stepH && offsetX > stepW) {
return false
}
}
// 雷同,鼠标移动偏右下角,不能移动
if (offsetLeft === sweepW && offsetTop === sweepH) {
// 右下
if (offsetX > stepW && offsetY > stepH) {
return false
}
}
// 雷同,鼠标移动偏左下角,不能移动
if(!offsetLeft && offsetTop === sweepH) {
// 左下
if (offsetX < stepW && offsetY > stepH) {
return false
}
}
// 以上条件都不符合,可以移动
return true; }

鼠标移出时,我们需要注销掉这两个事件监听

elem.onmouseleave = function () {
sweepBox.onmousemove = null
elem.onmouseleave = null
unload() // 隐藏展示台和扫描框
}

unload很简单,如下

function unload () {
showBox.style.display = sweepBox.style.display = 'none'
}

到此整个代码完成,实现了2倍关系的图像方大查看函数,并没有提供多的自定义设置,你可以自己修改一下,提供更多的自定义数据来提供更强大的功能。

结尾

本菜只能写到这样了,语言组织能力差,所以你可能没看懂,不过没关系,静下心来默默的想一下,你可能就会写这个功能了,而且一定比博主写的好~~~。

本章示例在github上,https://git.oschina.net/xuazheng/zoomjs.git

javascript实现 京东淘宝等商城的商品图片大图预览功能的更多相关文章

  1. javascript实现 京东淘宝等商城的商品图片大图预览功能(图片放大器)

      在京东和淘宝等购买东西的时候,我们会经常预览左侧商品展示图片,把鼠标放到原图,右侧就会有个大图显示出细节.本文将带领大家写一个这样简单的功能! 一.实现原理 当鼠标移入某一图片内部时,图片上部会出 ...

  2. Android之仿京东淘宝的自动无限轮播控件

    在App的开发中,很多的时候都需要实现类似京东淘宝一样的自动无限轮播的广告栏,所以就自己写了一个,下面是我自定义控件的思路和过程. 一.自定义控件属性 新建自定义控件SliderLayout继承于Re ...

  3. Android高仿京东淘宝自动无限循环轮播控件的实现思路和过程

    在App的开发中,很多的时候都需要实现类似京东淘宝一样的自动无限轮播的广告栏,所以就自己写了一个,下面是我自定义控件的实现思路和过程. 一.自定义控件属性 新建自定义控件SliderLayout继承于 ...

  4. JavaScript实现本地图片上传预览功能(兼容IE、chrome、FF)

    需要解决的问题有:本地图片如何在上传前预览.编辑:最近发现这个功能很多是基于flash实现的,很多JavaScript实现的代码兼容性都很差,特别是在IE和firefox和chrome三个浏览器上不兼 ...

  5. 浅谈android中只使用一个TextView实现高仿京东,淘宝各种倒计时

    今天给大家带来的是只使用一个TextView实现一个高仿京东.淘宝.唯品会等各种电商APP的活动倒计时.近期公司一直加班也没来得及时间去整理,今天难得歇息想把这个分享给大家.只求共同学习,以及自己兴许 ...

  6. 漂亮!Javascript代码模仿淘宝宝贝搜索结果的分页显示效果

    分页按钮思想: 1.少于9页,全部显示 2.大于9页,1.2页显示,中间页码当前页为中心,前后各留两个页码 先看效果图: 01输入框焦点效果 02效果 模仿淘宝的分页按钮效果控件kkpager  JS ...

  7. 关于django 京东淘宝 混合搜索实现原理

    混合搜索在各大网站如京东.淘宝都有应用,他们的原理都是什么呢?本博文将为你介绍它们的实现过程. 混合搜索的原理,用一句话来说就是:关键字id进行拼接. 混合搜索示例: 数据库设计: 视频方向: 1 2 ...

  8. ASP.NET之AdRotator实现淘宝浏览页面的商品随机推荐功能

    如今随便上个网都能够看到淘宝.京东等各大电商平台的双十一购物狂欢宣传,从2009年開始淘宝愣是把11.11这一天打造成了全民购物狂欢节.阿里巴巴的上市更是激发了阿里人的斗志,据说他们今年的目标是100 ...

  9. 利用Selenium+java实现淘宝自动结算购物车商品(附源代码)

    转载请声明原文地址! 本次的主题是利用selenium+java实现结算购买购物车中的商品. 话不多说,本次首先要注意的是谷歌浏览器的版本,浏览器使用的驱动版本,selenium的jar包版本.   ...

随机推荐

  1. jquery validate remote验证唯一性

    jquery.validate.js 的 remote 后台验证 之前已经有一篇关于jquery.validate.js验证的文章,还不太理解的可以先看看:jQuery Validate 表单验证(这 ...

  2. Mac OSX系统安装和配置Zend Server 6教程(2)

    继上一节安装好Zend Server 6以后,我们需要修改配置文件.首先修改服务器监听端口.默认的情况下Zend Server 6安装以后的端口是10088.一般开发者使用的都是HTTP默认端口80. ...

  3. 《STL源代码剖析》---stl_hash_set.h阅读笔记

    STL仅仅规定接口和复杂度,对于详细实现不作要求.set大多以红黑树实现,但STL在标准规格之外提供了一个所谓的hash_set,以hash table实现.hash_set的接口,hash_tabl ...

  4. 终于有人把O2O、C2C、B2B、B2C的区别讲透了!

    终于有人把O2O.C2C.B2B.B2C的区别讲透了! 一.O2O.C2C.B2B.B2C的区别在哪里? O2O是online to offline分为四种运营模式: 1.online to offl ...

  5. 使用Excel快速发送大量的电子邮件

    使用Excel快速发送大量的电子邮件.两个步骤: 1. 准备发送数据: a.) 打开Excel,新Book1.xlsx b.) 填写以下内容. 第一列:接受者,第二列:邮件标题,第三列:文,第四列:附 ...

  6. [转]ANDROID 探究oom内幕

    从早期G1的192MB RAM开始,到现在动辄1G -2G RAM的设备,为单个App分配的内存从16MB到48MB甚至更多,但OOM从不曾离我们远去.这是因为大部分App中图片内容占据了50%甚至7 ...

  7. Oracle单行函数笔记

    Oracle中单行函数的分类:1.字符函数substr():字符串截取select substr('我爱你,你知道么?',0,4) from dual执行结果:我爱你,length函数:字符串长度se ...

  8. 学SpringMVC收藏

     一个较完整的SpringMVC工程的配置 2014-01-22 17:17:25 标签:java spring springMVC 配置 springSecurity web.xml 原创作品,允许 ...

  9. 《剑指Offer》面试题-从头到尾打印链表

    题目描述: 输入一个链表,从尾到头打印链表每个节点的值. 输入: 每个输入文件仅包含一组测试样例.每一组测试案例包含多行,每行一个大于0的整数,代表一个链表的节点.第一行是链表第一个节点的值,依次类推 ...

  10. 使用Fiddler伪造服务端返回数据,绕过软件试用期验证

    用过一款和visual studio集成非常好的移动端模拟器,有7天的试用期,可惜不支持国内支付,试用到期了怎么办,不想重装系统. 昨天看有人破解admin page,于是尝试自己动手试试,因为这款模 ...