javascript實現 京東淘寶等商城的商品圖片大圖預覽功能

  在京東和淘寶等購買東西的時候,咱們會常常預覽左側商品展現圖片,把鼠標放到原圖,右側就會有個大圖顯示出細節。本文將帶領你們寫一個這樣簡單的功能!css

1、實現原理

當鼠標移入某一圖片內部時,圖片上部會出現一個相似於掃描的框,這個框內的圖片部分,會以方大形式展現在右邊,以下圖:html

從圖中能夠推測出一下幾點:git

圖片img上層會有一個父元素(如‘div’),在鼠標移入時,父元素內部添加一個子元素表明掃描框,而且整個body會出現一個固定定位的圖片預覽盒子定位在右側(這個圖片是另一張準備好的大圖),展現着掃描框中掃描到的圖片位置,這個掃描框不能在div內部移動,當鼠標移出圖片,掃描框和展現臺都消失。github

所以咱們獲得如下佈局app

<!--整個盒子-->
<div>
  <!--圖片-->
  <img src="...">
    <!--掃描框-->
  <div class="sweep"></div>        
</div>

<!--掃描展現區域-->
<div class="show">
</div>    

實際狀況下,咱們不會手動寫上 .sweep 和 .show這兩個空div,他是由js來實現的。所以,今天咱們練習的佈局代碼以下dom

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

2、準備工做

今天,博主準備了兩個練習圖片,一個 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。this

js函數參數選定:url

// 將函數命名爲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的大圖作背景圖,控制其背景圖的位置來改變展現圖的圖樣。

3、邏輯分析

1. 執行zoom函數,咱們須要獲取到掃描框和展現臺,若是沒有,就建立。

2. 給圖片添加一個onmouseenter事件,在鼠標移入圖片,觸發函數,顯示展現臺和掃描框,而且展現臺的圖片內容就是掃描框掃描到的圖片區域的放大部分。

3. 鼠標移出,展現臺和掃描框消失。

4. 鼠標在圖片上移動,咱們在這裏給掃描框添加onmousemove事件,鼠標位置始終在掃描框的中心位置(掃描框緊貼圖片一側除外),只有在鼠標移出了圖片區域,鼠標纔會離開掃描框。

5. 展現臺的圖片隨着掃描框的移動而變化到相應的部分。

4、編寫代碼

開始代碼以下:

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

相關文章
相關標籤/搜索