Pinch-Zoom 实现移动端手势放大+旋转

3,429 阅读2分钟

优秀创作者-杨光宇.png


本文将介绍如何通过js实现移动端图片的 手势缩放,手势拖动,双击放大, 旋转等功能;

完成后的样例

外部库的使用

pinchzoom.js
优势:这个库已经封装好了手势的放大,缩小,移动,等一系列功能
劣势:需要我们手动添加旋转和重置图片的功能

pinchzoom.js的使用

npm i pinch-zoom-js
&&
yarn add pinch-zoom-js
import PinchZoom from 'pinch-zoom-js'
let el = document.querySelector('#my-id');
let pz = new PinchZoom(el, options);

可配置项

属性说明默认值
tapZoomFactor双击缩放的倍数2
zoomOutFactor缩放低于这个值的时候调整为原始大小1.3
animationDuration动画持续时间(毫秒)300
maxZoom最大放大倍数4
minZoom最小缩小吧倍数0.5
draggableUnzoomed即使图像未缩放,也可以捕获拖动事件true
lockDragAxis将图元的平移锁定到单个轴false
setOffsetsOnce仅计算一次偏移(容器内的图像位置)false(使用'true'在连续的'load'和'resize'上保持偏移)
use2d空闲时返回到二维变换true
verticalPadding图像周围应用的垂直填充0
horizontalPadding图像周围应用的水平填充0
onZoomStart开始缩放的事件null
onZoomEnd停止缩放的事件null
onZoomUpdate更新缩放元素的回调null
onDragStart开始拖动元素null
onDragEnd结束拖动元素null
onDragUpdate更新拖动位置null
onDoubleTap双击图片null

事件

let pz = new PinchZoom(myElement);

pz.enable(); // 启用所有手势捕捉(默认启用)
pz.disable(); // 禁用所有手势捕捉(默认启用)

回调示例

var myElement = document.getElementById("myElement");
var pz = new PinchZoom.default(myElement, {
    draggableUnzoomed: false,
    minZoom: 1,
    onZoomStart: function(object, event){
    
    },
    onZoomEnd: function(object, event){
    
    }
})

添加旋转事件

第一步:我们先写一个方法,给图片元素dom设置一下transform属性,每次旋转90度

//this.vhtmlRotate用于记录旋转的角度默认是0
let vhtmlImage = document.getElementById('vhtmlImage');
let scale = vhtmlImage.offsetHeight / vhtmlImage.offsetWidth;
if (this.vhtmlRotate % 180) {
    scale = 1;
}
vhtmlImage.style.transform = `scale(${scale},${scale}) rotate(${this.vhtmlRotate +90}deg)`

问题:每次旋转的时候图片都会变成原始大小,再被Pinch-Zoom设定缩放,会有一个图片闪烁的问题

解决方案:旋转之前先把图片变成透明,旋转后再放出来

vhtmlImage.style.opacity = `0`;
........
vhtmlImage.style.opacity = `1`;

问题:放大图片后点击旋转,会出现图片位置错误,影响使用,由于Pinch-Zoom并没有重置组件的方法,如果使用v-if重新渲染和new组件,会导致产生多个pinch-zoom-container标签,导致拖动闪烁,具体原因未知
解决方案:在看了组件源码以后发现update方法,它可以更新给组件设置的参数,我们就在组件加载完成后记录图片位置和放大的尺寸(这里默认是1),旋转后手动设置,然后调用update的方法

this.PinchZoom.zoomFactor = 1;
this.PinchZoom.offset = me.offset;
this.PinchZoom.update();

问题:多次快速点击旋转,会导致页面报错,原因是组件并没有渲染完成,就进行了下一次渲染
解决方案:手动防抖事件

// 防止快速多次点击
if (!PinchZoomFinish) {
    return;
}
PinchZoomFinish = false;
.......
PinchZoomFinish = true;
            

完整代码

<template>
    <div class="car-recognition">
        <div id="pinc_zoom_room">
            <div id="image_room">
                <img id="vhtmlImage" src="https://assets.che300.com/feimg/m/banner/banner_weibao2.png" alt="" />
            </div>
        </div>
        <div @click="resetImg" class="reset_img">旋转图片</div>
    </div>
</template>
<script>
import PinchZoom from 'pinch-zoom-js';
let PinchZoomFinish = true;

export default {
    name: 'carRecognition',
    data() {
        return {
            vhtmlRotate: 0,
            offset:{}
        };
    },
    methods: {
        //重置图片
        resetImg() {
            let me = this;
            // 防止快速多次点击
            if (!PinchZoomFinish) {
                return;
            }
            PinchZoomFinish = false;
            let vhtmlImage = document.getElementById('vhtmlImage');
            vhtmlImage.style.opacity = `0`;
            let scale = vhtmlImage.offsetHeight / vhtmlImage.offsetWidth;

            if (me.vhtmlRotate % 180) {  
                scale = 1;
            }
            vhtmlImage.style.opacity = `1`;
            vhtmlImage.style.transform = `scale(${scale},${scale}) rotate(${me.vhtmlRotate +
                90}deg)`;
            me.vhtmlRotate = me.vhtmlRotate + 90;
            me.PinchZoom.zoomFactor = 1;
            me.PinchZoom.offset = me.offset;
            me.PinchZoom.update();
            PinchZoomFinish = true;
        },
    },
    created() {
        setTimeout(() => {
            const el = document.getElementById('image_room');
            this.PinchZoom = new PinchZoom(el, {
                zoomOutFactor: 0.5,
                onZoomStart: function(object, event){
                    console.log(object,event,'onZoomStart')
                },
                onZoomEnd: function(object, event){
                    console.log(object,event,'onZoomEnd')
                },
                onZoomUpdate: function(object, event){
                    // console.log(object,event,'onZoomUpdate')
                },
                onDragStart: function(object, event){
                    console.log(object,event,'onDragStart')
                },
                onDragEnd: function(object, event){
                    console.log(object,event,'onDragEnd')
                },
                onDragUpdate: function(object, event){
                    // console.log(object,event,'onDragUpdate')
                },
                onDoubleTap: function(object, event){
                    console.log(object,event,'onDoubleTap')
                }
            });
            this.$nextTick(()=>{
                this.offset = this.PinchZoom.offset
                console.log(this.offset)
            })
        },200)
    }
};
</script>

<style lang="less">
#pinc_zoom_room {
    text-align: left;
    height: 7rem;
    margin: 0.4rem;
    position: relative;
    background: #cccccc;
    border-radius: 0.066667rem;
    -webkit-touch-callout: none; /*系统默认菜单被禁用*/
    -webkit-user-select: none; /*webkit浏览器*/
    -khtml-user-select: none; /*早期浏览器*/
    -moz-user-select: none; /*火狐*/
    -ms-user-select: none; /*IE10*/
    user-select: none;
    #vhtmlImage {
        max-height: 7rem;
        -webkit-touch-callout: none;
    }
}
</style>