zoukankan      html  css  js  c++  java
  • javascript实现 京东淘宝等商城的商品图片大图预览功能

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

    一、实现原理

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

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

    图片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, {
           (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 / 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

  • 相关阅读:
    取得窗口大小和窗口位置兼容所有浏览器的js代码
    一个简单易用的导出Excel类
    如何快速启动chrome插件
    网页表单设计案例
    Ubuntu下的打包解包
    The source file is different from when the module was built. Would you like the debugger to use it anyway?
    FFisher分布
    kalman filter
    Group delay Matlab simulate
    24位位图格式解析
  • 原文地址:https://www.cnblogs.com/xujiazheng/p/6287172.html
Copyright © 2011-2022 走看看