1. 程式人生 > >使用AlloyFinger 的手勢縮放,外部按鈕操控縮放, 3D改為2D減少放大模糊

使用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