使用AlloyFinger 的手勢縮放,外部按鈕操控縮放, 3D改為2D減少放大模糊
專案中需要手勢放大圖片,懶得自己寫元件庫~找了好久,決定用AlloyFinger,但是吶~光用AlloyFinger不滿足要求,只能中心放大,後續加入了AlloyCrop來達到可以按照手勢點選位置放大,最後發現有“微信”瀏覽器等有部分不相容matrix3d的,會變模糊。最後改動transform.js變成matrix的2d縮放。
1.AlloyFinger的使用【中心放大圖片,太粗糙,達不到PD小姐姐預期】
有react版本的。。但是莫名不好用。。所以用了原生的,引用3個檔案
import AlloyFinger from './src/alloy_finger.js'; import { To } from './src/to.js'; import './src/transform.js';
html頁面
<div id="imgBox"> <img id="testImg" src={this.state.imageUrl} /> </div> <div className="operater-do"> <div className="ratina-bd operater-plus bd-b" onClick={this.tabPlus.bind(this)}> <div className="inline-block plus-icon"> + </div> </div> <div className="operater-minus" onClick={this.tabMinus.bind(this)}> <div className="inline-block minus-icon"> - </div> </div> </div>
初始化 圖片載入
imageLoaded(selector, onload) { var img = new Image(); var dom = document.querySelector(selector); img.onload = function() { //real_width,real_height onload.call(dom, this.width, this.height); img.onload = null; img = null; }; img.src = dom.getAttribute("src"); }
吸附螢幕效果
//吸附螢幕效果
stickEffect(el) {
// 超出螢幕外,吸附動效返回
// 當前螢幕位置
let left = $("#testImg").offset().left;
let top = $("#testImg").offset().top;
// 圖片寬高
let imgWidth = $("#testImg").width();
let imgHeight = $("#testImg").height();
// 螢幕寬高,高度=螢幕高度
let winWidth = window.innerWidth;
let winHeight = window.innerHeight ;
let skWidth = $("#testImg").width() * el.scaleX;
let skHeight = $("#testImg").height() * el.scaleY;
let setLeft = left;
let setTop = top;
if (left > 0 || top > 0 || skWidth + left < winWidth || skHeight + top - bannerHight < winHeight) {
//console.log("超出螢幕範圍");
if (left > 0) {
setLeft = 0;
}
if (top > 0) {
setTop = bannerHight;
}
if (skWidth + left < winWidth) {
setLeft = -(skWidth - winWidth);
}
if (skHeight + top - bannerHight < winHeight) {
setTop = -(skHeight - winHeight - bannerHight);
}
console.log("before,top:" + $("#testImg").offset().top, "left:", $("#testImg").offset().left);
console.log("stickEffect,top:" + Math.ceil(setTop), "left:", Math.ceil(setLeft));
$("#testImg").offset({
top: Math.ceil(setTop),
left: Math.ceil(setLeft)
})
}
}
開始的手勢操作【中心放大,移動圖片】
initImage() {
let self = this;
var topPx;
this.imageLoaded("#testImg", function(w, h) {
document.querySelector("#imgBox").style.display = "block";
// 圖片寬高
let height = this.height;
let width = this.width;
// 瀏覽器窗體尺寸
let winWidth = window.innerWidth;
let winHeight = window.innerHeight;
// 根據寬高調整尺寸
if (height / winHeight > width / innerWidth) {
this.style.width = "100%";
} else if (height / winHeight <= width / innerWidth) {
this.style.height = "100%";
}
});
var el = document.getElementById("testImg");
Transform(el);
var initScale = 1;
var alloyFinger = new AlloyFinger(el, {
// 手勢操作開始
multipointStart: function() {
To.stopAll();
initScale = el.scaleX;
},
// 捏 中心放大效果
pinch: function(evt) {
el.scaleX = el.scaleY = initScale * evt.zoom;
},
// 手勢操作結束
multipointEnd: function() {
To.stopAll();
// 最小為1,小於1沒有意義
if (el.scaleX < 1) {
el.scaleX = el.scaleY = 1;
self.stickEffect(el);
} else {
self.stickEffect(el);
}
},
// 點選移動
pressMove: function(evt) {
el.translateX += evt.deltaX;
el.translateY += evt.deltaY;
evt.preventDefault();
},
});
}
放大縮小操作【呼叫pinch直接改變圖片尺寸】
// 放大操作
tabPlus() {
// 按照1.5倍放大
let evt = {
zoom: this.state.initScale * 1.5
}
this.state.alloyFinger.pinch.handlers[0](evt);
}
// 縮小操作
tabMinus() {
// 判斷圖片尺寸不能小於1,1為初始化大小
let zoom = this.state.initScale / 1.5 >= 1 ? this.state.initScale / 1.5 : 1;
let evt = {
zoom: zoom
}
this.state.alloyFinger.pinch.handlers[0](evt);
// 吸附效果
this.stickEffect(document.getElementById("testImg"));
}
2.AlloyCrop的使用【PD小姐姐較為滿意】
優化手勢操作【雙指觸屏放大,自由縮放圖片】
initImage() {
let self = this;
var topPx;
this.imageLoaded("#testImg", function(w, h) {
document.querySelector("#imgBox").style.display = "block";
topPx = 0;
this.style.top = topPx + "px";
// 圖片寬高
let height = this.height;
let width = this.width;
// 瀏覽器窗體尺寸
let winWidth = window.innerWidth;
let winHeight = window.innerHeight;
// 根據寬高調整尺寸
if (height / winHeight > width / winWidth) {
this.style.width = "100%";
} else if (height / winHeight <= width / winWidth) {
this.style.height = "100%";
}
});
function ease(x) { //自然滑動效果
return Math.sqrt(1 - Math.pow(x - 1, 2));
}
var el = document.getElementById("testImg");
// 使用 transform.js
Transform(el);
// 初始化拉伸為1
var initScale = 1;
var alloyFinger = new AlloyFinger(el, {
// 手勢結束
multipointStart: function(evt) {
// 通過evt.touches拿到前兩個手指的座標去計算中心座標
let centerX = (evt.touches[0].pageX + evt.touches[1].pageX) / 2;
let centerY = (evt.touches[0].pageY + evt.touches[1].pageY) / 2;
// Element.getBoundingClientRect() 方法返回元素的大小及其相對於視口的位置
let cr = el.getBoundingClientRect();
// 重置 originX 和 originY 到兩手指的中心
let img_centerX = cr.left + cr.width / 2;
let img_centerY = cr.top + cr.height / 2;
let offX = centerX - img_centerX;
let offY = centerY - img_centerY;
let preOriginX = el.originX
let preOriginY = el.originY
el.originX = offX / el.scaleX;
el.originY = offY / el.scaleY;
//reset translateX and translateY
//再重置 translateX 和 translateY 去抹平 originX和originY變更帶來的位移
el.translateX += offX - preOriginX * el.scaleX;
el.translateY += offY - preOriginY * el.scaleX;
initScale = el.scaleX;
},
// 捏 操作
pinch: function(evt) {
var cr = el.getBoundingClientRect();
var boxOffY = (document.documentElement.clientHeight - self.height) / 2;
var tempo = evt.zoom;
var dw = (cr.width * tempo - cr.width) / 2;
var dh = (cr.height - cr.height * tempo) / 2;
el.scaleX = el.scaleY = initScale * tempo;
self.setState({
initScale: initScale * tempo
})
},
// 手勢結束
multipointEnd: function() {
To.stopAll();
// 最小為1,小於1沒有意義
if (el.scaleX < 1) {
el.scaleX = el.scaleY = 1;
self.stickEffect(el);
self.setState({
initScale: 1
})
} else {
self.stickEffect(el);
}
},
// 點選移動
pressMove: function(evt) {
el.translateX += evt.deltaX;
el.translateY += evt.deltaY;
evt.preventDefault();
},
// 雙擊放大
doubleTap: function(evt) {
To.stopAll();
initScale = el.scaleX * 1.5;
if (evt.zoom) {
initScale = evt.zoom;
}
var box = el.getBoundingClientRect();
var y = box.height - ((evt.changedTouches[0].pageY - topPx) * 2) - (box.height / 2 - (evt.changedTouches[0].pageY - topPx));
var x = box.width - ((evt.changedTouches[0].pageX) * 2) - (box.width / 2 - (evt.changedTouches[0].pageX));
new To(el, "scaleX", initScale, 100, ease);
new To(el, "scaleY", initScale, 100, ease);
},
});
}
優化 點選加減符號放大縮小圖片【模擬 雙擊放大 效果】
// 放大操作
tabPlus() {
console.log(this.state.alloyFinger);
let el = document.getElementById("testImg");
let cr = el.getBoundingClientRect();
// 螢幕寬高,高度=螢幕高度-banner高度
let winWidth = window.innerWidth;
let winHeight = window.innerHeight;
// // 重置 originX 和 originY 到兩手指的中心
let img_centerX = (cr.left + winWidth) / 2;
let img_centerY = (cr.top + winHeight) / 2;
let zoom = el.scaleX * 1.5;
let evt = {
zoom: zoom,
changedTouches: [{
pageX: img_centerX,
pageY: img_centerY
}, {
pageX: img_centerX,
pageY: img_centerY
}]
}
this.state.alloyFinger.doubleTap.handlers[0](evt);
}
//縮小操作
tabMinus() {
this.tabMinusMethod();
// 延時吸附。。不知道為啥,實時吸附有問題
window.setTimeout(() => {
this.stickEffect(document.getElementById("testImg"));
}, 100);
}
tabMinusMethod() {
let el = document.getElementById("testImg");
let cr = el.getBoundingClientRect();
// 螢幕寬高,高度=螢幕高度-banner高度
let winWidth = window.innerWidth;
let winHeight = window.innerHeight;
// // 重置 originX 和 originY 到兩手指的中心
let img_centerX = (cr.left + winWidth) / 2;
let img_centerY = (cr.top + winHeight) / 2;
let zoom = el.scaleX / 1.5 >= 1 ? el.scaleX / 1.5 : 1;
let evt = {
zoom: zoom,
changedTouches: [{
pageX: img_centerX,
pageY: img_centerY
}, {
pageX: img_centerX,
pageY: img_centerY
}]
}
this.state.alloyFinger.doubleTap.handlers[0](evt);
}
3.matrix3d 改成 matrix,解決微信等WebView放大模糊問題【最終問題】
PD小姐姐來說,為啥微信放大會模糊啊,以為是微信的優化,抓包發現,圖片大小是一樣的,就開始找原因。最後發現是matrix3d的原因,可以先實驗下CSS程式碼,看是否可以清晰。
transform: matrix(3.375,0,0,3.375,30,30) !important;
在 IOS 11.2.5 微信上可以變得清晰,於是,就改動transform.js.
找到這個程式碼:
element.style.transform = element.style.msTransform = element.style.OTransform = element.style.MozTransform = element.style.webkitTransform = "perspective(" + element.perspective + "px) matrix3d(" + Array.prototype.slice.call(mtx.elements).join(",") + ")";
改動程式碼完畢如下:
1.去掉 perspective 屬性
2.把原來 matrix3d中的16個數組,改成 matrix的6個數組
3.移動公式:
matrix(scaleX , skewX ,skewY, scaleY,translateX,translateY)
matrix3d(scaleX ,0,0,0,0, scaleY,0,0,0,0, scaleZ,0, translateX,translateY,translateZ,1)
scale - 縮放, 斜拉 - skew ,旋轉 - rotate , 位移 - translate
4.最終程式碼改動
let matrix = [mtx.elements[0], 0, 0, mtx.elements[5], mtx.elements[12], mtx.elements[13]];
element.style.transform = element.style.msTransform = element.style.OTransform = element.style.MozTransform = element.style.webkitTransform = " matrix(" + matrix.join(",") + ")";
5.可能潛伏的bug
A. 目前 點選20下“放大”按鈕,會有灰屏問題。
主要是因為 放到過大的時候,會導致成為二次冪係數。。。沒辦法解析。。。就會變灰色,正常操作不會有這個問題。
解決程式碼(手勢“捏”和 雙擊“放大” 操作,最多縮放設定為300):
// 手勢:捏 操作
pinch: function(evt) {
let cr = el.getBoundingClientRect();
let boxOffY = (document.documentElement.clientHeight - self.height) / 2;
let tempo = evt.zoom;
// 判斷超過300zoom, 就不能放大了,灰屏是1000,但是基本用不到,所以設定300
if (initScale * tempo > 300) {
this.touchEnd();
return;
}
let dw = (cr.width * tempo - cr.width) / 2;
let dh = (cr.height - cr.height * tempo) / 2;
// if ((initScale * tempo <= 1.6) && (initScale * tempo >= self.originScale) && (dw >= cr.left) && (-dw <= (cr.right - self.width)) && (dh <= (boxOffY - cr.top)) && (dh <= (cr.bottom - boxOffY - self.height))) {
el.scaleX = el.scaleY = initScale * tempo;
self.setState({
initScale: initScale * tempo
})
},
// 手勢: 雙擊放大
// 按鈕:放大 縮小 也呼叫該方法
doubleTap: function(evt) {
To.stopAll();
// initScale = el.scaleX * 1.5 <= 10 ? el.scaleX * 1.5 : 10;
initScale = el.scaleX * 1.5;
if (evt.zoom) {
initScale = evt.zoom;
}
// 解決20下點選以後的bug ,並且可以縮小
// transform 超過 1000 時候會有問題,
if (initScale > 300 && initScale >= el.scaleX) {
return;
}
let box = el.getBoundingClientRect();
let y = box.height - ((evt.changedTouches[0].pageY - topPx) * 2) - (box.height / 2 - (evt.changedTouches[0].pageY - topPx));
let x = box.width - ((evt.changedTouches[0].pageX) * 2) - (box.width / 2 - (evt.changedTouches[0].pageX));
new To(el, "scaleX", initScale, 100, ease);
new To(el, "scaleY", initScale, 100, ease);
// 移到中心點
// new To(el, "translateX", x, 500, ease);
// new To(el, "translateY", y, 500, ease);
},
B.在部分安卓手機上,釘釘開啟還是會有模糊的問題,正在調研
6.推薦閱讀文章:
產品體驗( 識榴 歡迎調戲 )
DEMO:
最後的最後codepen demo: 點選檢視DEMO