在京東和淘寶等購買東西的時候,我們會經常預覽左側商品展示圖片,把鼠標放到原圖,右側就會有個大圖顯示出細節。本文將帶領大家寫一個這樣簡單的功能!
一、實現原理
當鼠標移入某一圖片內部時,圖片上部會出現一個類似於掃描的框,這個框內的圖片部分,會以方大形式展示在右邊,如下圖:
從圖中可以推測出一下幾點:
圖片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