项目中需要手势放大图片,懒得自己写组件库~找了好久,决定用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.推荐阅读文章:
【张鑫旭】理解CSS3 transform中的Matrix(矩阵)
【张鑫旭】好吧,CSS3 3D transform变换,不过如此!
产品体验( 识榴 欢迎调戏 )
产品链接:暹罗海洋世界
DEMO:
最后的最后codepen demo: 点击查看DEMO