使用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.推荐阅读文章:

【张鑫旭】理解CSS3 transform中的Matrix(矩阵)

【张鑫旭】好吧,CSS3 3D transform变换,不过如此!

【官方原理解读】超小Web手势库AlloyFinger原理

【Github】AlloyCrop

【Github】AlloyFinger




产品体验( 识榴 欢迎调戏 )

产品链接:暹罗海洋世界




DEMO:

最后的最后codepen demo: 点击查看DEMO

猜你喜欢

转载自blog.csdn.net/Candy_home/article/details/80034534