js实现移动端图片预览:手势缩放, 手势拖动,双击放大...
前言
本文將介紹如何通過js實(shí)現(xiàn)移動端圖片預(yù)覽,包括圖片的 預(yù)覽模式,手勢縮放,手勢拖動,雙擊放大等基本功能;
掃碼查看示例效果:
代碼地址http://pangyongsheng.github.io/imgPreview/
一、功能介紹
圖片預(yù)覽主要有以下幾個功能點(diǎn)組成:
- 監(jiān)聽圖片點(diǎn)擊事件,進(jìn)入圖片預(yù)覽模式
- 自定義手勢事件, (雙指縮放,滑動,雙擊。。。)
- 監(jiān)聽圖片手勢事件,通過 transform-matrix 實(shí)現(xiàn)圖片的各種變換;
?
二、實(shí)現(xiàn)方法
1、圖片預(yù)覽模式
圖片預(yù)覽即點(diǎn)擊圖片在頁面中插入一個黑色全屏背景框并將圖片居中顯示。封裝時,為了只對指定圖片添加功能,可通過監(jiān)聽指定類名或添加某種屬性的img標(biāo)簽監(jiān)聽;另外需在對背景框綁定點(diǎn)擊事件,退出預(yù)覽模式。一下是一個簡單示例代碼:
?//點(diǎn)擊圖片進(jìn)入預(yù)覽????var?$Dom?=?document.querySelector(".preview");
????$Dom.onclick?=?function()?{
????????var?temp?=?this.src;
????????var?objE?=?document.createElement("div");
????????objE.innerHTML?=?'<div?class="bgM"?>'?+
????????????????'<img?src="'+temp+'"??id="img_scan"?class="img-custom-img2"/>'?+
????????????'</div>';
????????document.body.appendChild(objE.children[0]);
????????//退出圖片預(yù)覽事件
????????var?$bg?=?document.querySelector(".bgM");
????????$bg.onclick?=?function()?{
????????????var?dm?=?document.querySelector(".bgM");
????????????document.body.removeChild(dm);
????????}
????????//阻止事件冒泡
????????var?$img?=?document.querySelector(".img-custom-img2");
????????$img.onclick?=?function(event)?{
???????????event.stopPropagation();
????????}
????}
css樣式參考
.bgM{????width:?100%;
????height:?100%;
????position:?absolute;
????top:?0;left:?0;right:?0;bottom:?0;
????z-index:?1000;
????background-color:?rgba(0,0,0,0.85);
????overflow:?hidden;
}
.bgM?img{
????width:?100%;
????max-height:100%;
????position:?absolute;
????top:?0;left:?0;right:?0;bottom:?0;
????z-index:?1001;
????margin:?auto;
}
2、自定義手勢事件
這里通過監(jiān)聽移動端touch事件實(shí)現(xiàn)自定義雙指縮放,單指滑動,雙擊事件,并通過事件屬性傳遞相關(guān)參數(shù),如縮放比例,滑動距離等,詳細(xì)實(shí)現(xiàn)方式參考這篇博客:請參考此博文:https://www.cnblogs.com/pangys/p/9119845.html 這里只大概說明;
當(dāng)觸發(fā)touch事件的時候,會生成一個TouchEvent對象,我們可通過其屬性e.touches.length來判斷是否多點(diǎn)觸控,通過e.touches[index].pageX,e.touches[index].pageY獲取去觸點(diǎn)坐標(biāo),通過e.target獲取dom節(jié)點(diǎn);
這里為了方便,直接監(jiān)聽document事件然后對目標(biāo)元素觸發(fā)事件,實(shí)際也可以直接對img監(jiān)聽事件,然后分別處理;
(1)手勢事件
- 監(jiān)聽touchstart事件,若e.touches.length>=2,為雙指事件,獲取觸點(diǎn)坐標(biāo)(觸點(diǎn)坐標(biāo)-目標(biāo)元素.offsetLeft/Top)計算兩個點(diǎn)中點(diǎn) 添加到事件屬性中,改變相關(guān)狀態(tài),觸發(fā)gesturestart事件;
- 監(jiān)聽touchmove事件,若e.touches.length>=2,獲當(dāng)前取觸點(diǎn)坐標(biāo)和gesturestart坐標(biāo),計算出縮放比例及角度,觸發(fā)gesturechange事件;
- 監(jiān)聽touchend事件,根據(jù)前面事件記錄的狀態(tài)觸發(fā)結(jié)束gestureend事件;
(2)滑動事件
- 監(jiān)聽touchstart事件,若e.touches.length<2,為單指事件,獲取觸點(diǎn)坐標(biāo)(觸點(diǎn)坐標(biāo)-目標(biāo)元素.offsetLeft/Top)添加到事件屬性中,記錄事件狀態(tài);
- 監(jiān)聽touchmove事件,若e.touches.length<2,獲當(dāng)前取觸點(diǎn)坐標(biāo)和上一步坐標(biāo),計算出移動距離添加到事件屬性中,觸發(fā)swipeMove事件;
(3)雙擊事件
監(jiān)聽touchstart事件,若e.touches.length<2,為單指事件,獲取觸點(diǎn)坐標(biāo)(觸點(diǎn)坐標(biāo)-目標(biāo)元素.offsetLeft/Top)添加到事件屬性中,獲取當(dāng)前時間挫記錄到相關(guān)變量中,計算本次時間戳與上次事件時間戳之差,若時間差范圍在指定范圍(0~250)則觸發(fā)doubleTouch事件;
參考代碼:
????var?isTouch?=?false;????var?isDoubleTouch?=?false;?//是否為多觸點(diǎn)???
????var?start?=?[];?//存放觸點(diǎn)坐標(biāo)
????var?now,?delta;?//當(dāng)前時間,兩次觸發(fā)事件時間差
????var?startPosition,?movePosition,?endPosition;?//滑動起點(diǎn),移動,結(jié)束點(diǎn)坐標(biāo)
????//事件聲明
????var?gesturestart?=?new?CustomEvent('gesturestart');
????var?gesturechange?=?new?CustomEvent('gesturechange');
????var?gestureend?=?new?CustomEvent('gestureend');
????var?swipeMove?=?new?CustomEvent('swipeMove');
????var?doubleTouch?=?new?CustomEvent("doubleTouch");
????//監(jiān)聽touchstart事件
????document.addEventListener('touchstart',?function(e)?{
????????if?(e.touches.length?>=?2)?{?//判斷是否有兩個點(diǎn)在屏幕上
????????????isDoubleTouch?=?true;
????????????start?=?e.touches;?//得到第一組兩個點(diǎn)
????????????var?screenMinPoint?=?getMidpoint(start[0],?start[1]);?//獲取兩個觸點(diǎn)中心坐標(biāo)
????????????gesturestart.midPoint?=?[screenMinPoint[0]?-?e.target.offsetLeft,?screenMinPoint[1]?-?e.target.offsetTop];?//獲取中心點(diǎn)坐標(biāo)相對目標(biāo)元素坐標(biāo)
????????????e.target.dispatchEvent(gesturestart);
????????}?else?{
????????????delta?=?Date.now()?-?now;?//計算兩次點(diǎn)擊時間差
????????????now?=?Date.now();
????????????startPosition?=?[e.touches[0].pageX,?e.touches[0].pageY];
????????????if?(delta?>?0?&&?delta?<=?250)?{?//雙擊事件
????????????????doubleTouch.position?=?[e.touches[0].pageX?-?e.target.offsetLeft,?e.touches[0].pageY?-?e.target.offsetTop];
????????????????e.target.dispatchEvent(doubleTouch);
????????????}?else?{?//滑動事件
????????????????isTouch?=?true;
????????????}
????????}
????},?false);
????//監(jiān)聽touchmove事件
????document.addEventListener('touchmove',?function(e)?{
????????if?(e.touches.length?>=?2?&&?isDoubleTouch)?{?//手勢事件
????????????var?now?=?e.touches;?//得到第二組兩個點(diǎn)
????????????var?scale?=?getDistance(now[0],?now[1])?/?getDistance(start[0],?start[1]);?//得到縮放比例
????????????var?rotation?=?getAngle(now[0],?now[1])?-?getAngle(start[0],?start[1]);?//得到旋轉(zhuǎn)角度差
????????????gesturechange.scale?=?scale.toFixed(2);
????????????gesturechange.rotation?=?rotation.toFixed(2);
????????????e.target.dispatchEvent(gesturechange);
????????}?else?if?(isTouch)?{
????????????movePosition?=?[e.touches[0].pageX,?e.touches[0].pageY];
????????????endPosition?=?movePosition;
????????????movePosition?=?[movePosition[0]?-?startPosition[0],?movePosition[1]?-?startPosition[1]];
????????????startPosition?=?[e.touches[0].pageX,?e.touches[0].pageY];
????????????swipeMove.distance?=[movePosition[0].toFixed(2)?,?movePosition[1].toFixed(2)];
????????????e.target.dispatchEvent(swipeMove);
????????}
????},?false);
????//監(jiān)聽touchend事件
????document.addEventListener('touchend',?function(e)?{
????????if?(isDoubleTouch)?{
????????????isDoubleTouch?=?false;
????????????gestureend.position?=?endPosition;
????????????e.target.dispatchEvent(gestureend);
????????};
????},?false);
????/*
?????*?兩點(diǎn)的距離
?????*/
????function?getDistance(p1,?p2)?{
????????var?x?=?p2.pageX?-?p1.pageX,
????????????y?=?p2.pageY?-?p1.pageY;
????????return?Math.sqrt((x?*?x)?+?(y?*?y));
????};
????/*
?????*?兩點(diǎn)的夾角
?????*/
????function?getAngle(p1,?p2)?{
????????var?x?=?p1.pageX?-?p2.pageX,
????????????y?=?p1.pageY?-?p2.pageY;
????????return?Math.atan2(y,?x)?*?180?/?Math.PI;
????};
????/*
?????*?獲取中點(diǎn)?
?????*/
????function?getMidpoint(p1,?p2)?{
????????var?x?=?(p1.pageX?+?p2.pageX)?/?2,
????????????y?=?(p1.pageY?+?p2.pageY)?/?2;
????????return?[x,?y];
????}
三、圖片的變換
對于圖片的每次操作都需在上一次操作的基礎(chǔ)上進(jìn)行疊加,如果直接使用width,top,left或scale,translate等css樣式需要每次都記錄當(dāng)前圖片狀態(tài)的全部參數(shù),而且計算較多,這里考慮使用transform-matrix實(shí)現(xiàn)圖片的基本變換,這樣只需創(chuàng)建一個數(shù)組作為變換矩陣,每次操作直接在當(dāng)前變換矩陣上修改相關(guān)參數(shù)即可實(shí)現(xiàn)圖像的變換:
transform-matrix :可配置[a,b,c,d,e,f]6個參數(shù),如下圖所示,x和y是初始的坐標(biāo),x’ 和y’則是通過矩陣變換后得到新的坐標(biāo)。變換矩陣,對原先的坐標(biāo)施加變換,就能得到新的坐標(biāo)了。依據(jù)矩陣變換規(guī)則即可得到: x’=ax+cy+e y’=bx+dy+f。
| 縮放 | a | d |
| 移動 | e | f |
(1) 獲取目標(biāo)元素及相關(guān)參數(shù),綁定事件
????var?$imgs?=?document.querySelector("#img_scan");????var?clientWidth?=?document.body.clientWidth;?//窗口寬
????var?clientHeight?=?document.body.clientHeight;?//窗口高
????var?imgWidth?=?parseInt(window.getComputedStyle($imgs).width);?//圖片寬
????var?imgHeight?=?parseInt(window.getComputedStyle($imgs).height);?//圖片高
????$imgs.addEventListener('gesturestart',?gesturef,?false);
????$imgs.addEventListener('gesturechange',?gesturef,?false);
????$imgs.addEventListener('gestureend',?gesturef,?false);
????$imgs.addEventListener('swipeMove',?gesturef,?false);
????$imgs.addEventListener('doubleTouch',?gesturef,?false);
????var?tMatrix?=?[1,?0,?0,?1,?0,?0];?//x縮放,無,無,y縮放,x平移,y平移
????var?originLast,?maxSwipeLeft,?maxSwipeRight,?maxSwipeTop,?maxSwipeBottom;?//上下左右可拖動距離
(2)監(jiān)聽 gesturestart 設(shè)置 變換中心
?case?"gesturestart":????var?x?=?event.midPoint[0];
????var?y?=?event.midPoint[1];
????originLast?=?event.midPoint;
????$imgs.style.transformOrigin?=?x?+?"px?"?+?y?+?"px";
????break;
(2)監(jiān)聽 gesturechange 進(jìn)行縮放變換,這里設(shè)置了縮放范圍為0.5 ~ 3;
case?"gesturechange":?????var?sc?=?parseFloat(event.scale);
?????tMatrix[0]?=?tMatrix[0]?+?sc?-?1?>?0.5?&&?tMatrix[0]?+?sc?-?1?<?3???tMatrix[0]?+?sc?-?1?:?tMatrix[0];
?????tMatrix[3]?=?tMatrix[3]?+?sc?-?1?>?0.5?&&?tMatrix[3]?+?sc?-?1?<?3???tMatrix[3]?+?sc?-?1?:?tMatrix[3];
?????var?temp?=?tMatrix.join(",");
?????$imgs.style.transform?=?"matrix("?+?temp?+?")";
?????break;
(3)監(jiān)聽 gestureend 獲取移動邊界范圍邊界
case?"gestureend":?????maxMove();
?????break;
可移動邊界范圍的計算:
對于圖片中的任意點(diǎn)可拖動范圍都是相同的,那么以縮放中心點(diǎn)來計算,如下圖所示,對于圖片中的縮放中心點(diǎn)p,有縮放后距離邊距的距離,可移動的范圍均為 縮放后增加或減少的距離 - (縮放中心點(diǎn)距離圖片邊緣的距離),即 |?縮放比例 - 1 |? *? p點(diǎn)距離邊緣的距離;
?
代碼如下:
?function?maxMove(){?????//最大可拖動范圍
?????var?sca?=?tMatrix[0];
?????maxSwipeLeft?=?Math.abs(sca?-?1)?*?originLast[0];
?????maxSwipeRight?=?Math.abs(sca?-?1)?*?(imgWidth?-?originLast[0]);
?????maxSwipeTop?=?Math.abs(sca?-?1)?*?originLast[1];
?????maxSwipeBottom?=?Math.abs(sca?-?1)?*?(imgHeight?-?originLast[1]);
}
(4)監(jiān)聽 swipeMove 拖動圖片,需考慮是否在可拖動范圍
?if?(!maxSwipeLeft?||?!maxSwipeRight?||?!maxSwipeTop?||?!maxSwipeBottom)?return;?????if?(event.distance[0]?>?0?&&?maxSwipeLeft?<?tMatrix[4])?return;
?????if?(event.distance[0]?<?0?&&?maxSwipeRight?<?-tMatrix[4])?return;
?????if?(event.distance[1]?>?0?&&?maxSwipeTop?<?tMatrix[5])?return;
?????if?(event.distance[1]?<?0?&&?maxSwipeBottom?<?-tMatrix[5])?return;
?????tMatrix[4]?=?tMatrix[4]?+?parseInt(event.distance[0]);
?????tMatrix[5]?=?tMatrix[5]?+?parseInt(event.distance[1]);
?????var?temp?=?tMatrix.join(",");
?????$imgs.style.transform?=?"matrix("?+?temp?+?")";
?????break;
(5)監(jiān)聽 doubleTouch 實(shí)現(xiàn)雙擊點(diǎn)縮放
case?"doubleTouch":??????originLast?=?event.position;
??????$imgs.style.transformOrigin?=?event.position[0]?+?"px?"?+?event.position[1]?+?"px";
??????tMatrix[0]?=?2;
??????tMatrix[3]?=?2;
??????var?temp?=?tMatrix.join(",");
??????$imgs.style.transform?=?"matrix("?+?temp?+?")";
??????maxMove();
??????break;
至此一個圖片預(yù)覽的基本功能即可實(shí)現(xiàn) ,? 也可以通過手勢做旋轉(zhuǎn)及上下一張的功能;
總結(jié)
以上是生活随笔為你收集整理的js实现移动端图片预览:手势缩放, 手势拖动,双击放大...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 精读《谈谈 Web Workers》
- 下一篇: Redis(七)分布式锁