实现一个手势库 - GestureJs

简介

在前端开发中,我们经常需要对用户的手势进行响应,实现一些交互效果,比如滑动、缩放等。本文介绍了如何编写一个通用的手势控制 GestureJs,用于在 PC 和移动端实现手势控制功能。该代码还是纯js的,希望刷到的各位大佬给点优化建议!

背景

在前端开发中,我们常常需要处理用户的手势操作,例如在移动端实现滑动翻页、图片缩放等功能,或者在 PC 端实现鼠标滚轮控制页面滚动、拖拽元素等功能。为了统一处理这些手势操作,我们可以编写一个通用的手势控制库,简化开发流程,提高开发效率。

功能

GestureJs 提供了以下功能:

  • 监听鼠标和触摸事件,实现对用户手势的响应;
  • 支持在 PC 和移动端环境下使用;
  • 实现了上下左右滑动、鼠标滚动、缩放等常见手势操作。

示例代码

html 复制代码
<!doctype html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <style>
            *{
                padding: 0;
                margin: 0;
            }
            #root{
                width: 800px;
                height:800px;
                margin: 20px auto;
                background-color: aquamarine;
            }
        </style>
    </head>
    <body>
        <div id="root"></div>
        <script src="./gesture.js"></script>
        <script>
            const gesture = new GestureJs({
                el:'#root',
                events:{
                    upwardSliding(offset){
                        console.log('上滑了:',offset)
                    },
                    downwardSliding(offset){
                         console.log('下滑了:',offset)
                    },
                    leftSliding(offset){
                          console.log('左滑了:',offset)
                    },
                    rightSliding(offset){
                          console.log('右滑了:',offset)
                    },
                    _upwardScroll(){
                        console.log('鼠标滚动向上')
                    },
                    _downwardScroll(){
                        console.log('鼠标滚动向下')
                    },
                    _pinchZoom(zoomType){
                        if(zoomType=='enlarge'){
                            console.log('放大')
                        }
                         if(zoomType=='narrow'){
                            console.log('缩小')
                        }
                    }
                }
            })

            // 最后记得销毁事件
            gesture.uninstall()
        </script>
    </body>
</html>

GestureJs完整代码

js 复制代码
const defaultOptions = {
    el: window,
    events: {},
    triggerNum: 40,
    config: {
        /**
         * 是否松开手指
         */
        isEnd: true,
        /**
         * 鼠标/手指按下的位置
         */
        startPosition: {
            offsetX: 0,
            offsetY: 0
        },
        /**
         * 手指的个数
         */
        touchFinger: 1,
        /**
         * 两指按下时的距离
         */
        initialDistance: 0
    }
};

// 检查是否包含移动设备的特征字符串
function isMobileBrowser() {
    return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
}

class GestureJs {
    constructor(options) {
        const opt = { ...defaultOptions, ...options };
        this.el = typeof opt.el === 'string' ? document.querySelector(opt.el) : opt.el;
        this.events = opt.events;
        this.triggerNum = opt.triggerNum;
        this.config = opt.config;
        this.init();
    }

    init() {
        if (!Object.keys(this.events).length) {
            throw new Error('请传递手势事件');
        }
        if (isMobileBrowser()) {
            this.observeTouchGesture();
        } else {
            this.observeMouseGesture();
        }
    }

    uninstall() {
        if (isMobileBrowser()) {
            this.unObserveTouchGesture();
        } else {
            this.unObserveMouseGesture();
        }
    }
    unObserveMouseGesture() {}
    unObserveTouchGesture() {}

    /**
     * pc
     */
    observeMouseGesture() {
        this.el.addEventListener('mousedown', event => {
            if (!this.config.isEnd) {
                return;
            }
            this.config = {
                ...this.config,
                isEnd: false,
                startPosition: {
                    offsetX: event.offsetX,
                    offsetY: event.offsetY
                }
            };
        });

        this.el.addEventListener('mouseup', event => {
            if (this.config.isEnd) {
                return;
            }
            const deltaX = Math.floor(event.offsetX - this.config.startPosition.offsetX);
            const deltaY = Math.floor(event.offsetY - this.config.startPosition.offsetY);
            this._detectSwipeDirection(deltaX, deltaY);
            this._resetConfig();
        });

        this.el.addEventListener('mouseleave', event => {
            this.config.isEnd = true;
        });

        this.el.addEventListener('wheel', event => {
            console.log('wheel====', event);
            // 鼠标滚轮事件
            if (event.deltaY > 0) {
                // 向下滚动
                this._downwardScroll();
            } else if (event.deltaY < 0) {
                // 向上滚动
                this._upwardScroll();
            }
        });
    }

    /**
     * 移动端
     */
    observeTouchGesture() {
        // 监听触摸事件
        this.el.addEventListener('touchstart', event => {
            if (!this.config.isEnd) {
                return;
            }
            this.config.touchFinger = event.touches.length;
            // 单指触摸
            if (event.touches.length === 1) {
                this.config = {
                    ...this.config,
                    isEnd: false,
                    startPosition: {
                        offsetX: event.touches[0].clientX,
                        offsetY: event.touches[0].clientY
                    }
                };
                return;
            }
            // 两指触摸,可能进行放大缩小操作
            if (event.touches.length === 2) {
                const touch1 = event.touches[0];
                const touch2 = event.touches[1];
                this.config = {
                    ...this.config,
                    initialDistance: Math.sqrt(
                        Math.pow(touch1.screenX - touch2.screenX, 2) + Math.pow(touch1.screenY - touch2.screenY, 2)
                    )
                };
                return;
            }
        });
        /**
        * TODO: 添加防抖
        */
        this.el.addEventListener('touchmove', event => {
            if (this.config.isEnd || event.touches.length !== this.config.touchFinger) {
                return;
            }
            if (event.touches.length === 2) {
                const touch1 = event.touches[0];
                const touch2 = event.touches[1];
                const currentDistance = Math.sqrt(
                    Math.pow(touch1.screenX - touch2.screenX, 2) + Math.pow(touch1.screenY - touch2.screenY, 2)
                );
                const deltaDistance = currentDistance - this.config.initialDistance;
                if (deltaDistance > 0) {
                    // 缩放放大
                    this._pinchZoom('enlarge');
                } else if (deltaDistance < 0) {
                    // 缩放缩小
                    this._pinchZoom('narrow');
                }
            }
        });

        this.el.addEventListener('touchend', event => {
            if (this.config.isEnd || event.touches.length !== this.config.touchFinger) {
                return;
            }
            if (event.touches.length === 1) {
                const deltaX = Math.floor(event.touches[0].clientX - this.config.startPosition.offsetX);
                const deltaY = Math.floor(event.touches[0].clientY - this.config.startPosition.offsetY);
                this._detectSwipeDirection(deltaX, deltaY);
            }
            this._resetConfig();
        });
    }

    /**
     * 向上滑动
     */
    _upwardSliding(offset) {
        this.events.upwardSliding && this.events.upwardSliding(offset);
    }

    /**
     * 向下滑动
     */
    _downwardSliding(offset) {
        this.events.downwardSliding && this.events.downwardSliding(offset);
    }

    /**
     * 向左滑动
     */
    _leftSliding(offset) {
        this.events.leftSliding && this.events.leftSliding(offset);
    }

    /**
     * 向右滑动
     */
    _rightSliding(offset) {
        this.events.rightSliding && this.events.rightSliding(offset);
    }

    _pinchZoom(zoomType) {
        this.events.pinchZoom && this.events.pinchZoom(zoomType);
    }

    /**
     * 鼠标滚动向上
     */
    _upwardScroll() {
        this.events.upwardScroll && this.events.upwardScroll();
    }

    /**
     * 鼠标滚动向下
     */
    _downwardScroll() {
        this.events.downwardScroll && this.events.downwardScroll();
    }

    /**
     * @param {*} deltaX x轴方向开始-结束的距离
     * @param {*} deltaY y轴方向开始-结束的距离
     * @returns
     */
    _detectSwipeDirection(deltaX, deltaY) {
        if (Math.abs(deltaX) < this.triggerNum && Math.abs(deltaY) < this.triggerNum) {
            console.log('移动的值不足以达到触发事件的条件!');
            return;
        }
        if (Math.abs(deltaX) - Math.abs(deltaY) > 0) {
            // 左右滑动
            if (deltaX > 0) {
                this._rightSliding(Math.abs(deltaX));
            } else {
                this._leftSliding(Math.abs(deltaX));
            }
        } else {
            // 上下滑动
            if (deltaY > 0) {
                this._downwardSliding(Math.abs(deltaY));
            } else {
                this._upwardSliding(Math.abs(deltaY));
            }
        }
    }

    _resetConfig() {
        this.config = JSON.parse(JSON.stringify(defaultOptions));
    }
}

总结

GestureJs是针对公司的业务需求进行的封装,因为公司的官网对样式的要求较高,没有使用ui组件库,所以大部分功能只能自己实现。本人前端菜鸟,以上代码还望大佬给点优化建议。

相关推荐
真的很上进2 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
噢,我明白了5 小时前
同源策略:为什么XMLHttpRequest不能跨域请求资源?
javascript·跨域
sanguine__5 小时前
APIs-day2
javascript·css·css3
关你西红柿子6 小时前
小程序app封装公用顶部筛选区uv-drop-down
前端·javascript·vue.js·小程序·uv
济南小草根6 小时前
把一个Vue项目的页面打包后再另一个项目中使用
前端·javascript·vue.js
小木_.6 小时前
【python 逆向分析某有道翻译】分析有道翻译公开的密文内容,webpack类型,全程扣代码,最后实现接口调用翻译,仅供学习参考
javascript·python·学习·webpack·分享·逆向分析
Aphasia3117 小时前
一次搞懂 JS 对象转换,从此告别类型错误!
javascript·面试
m0_748256567 小时前
Vue - axios的使用
前端·javascript·vue.js
m0_748256347 小时前
QWebChannel实现与JS的交互
java·javascript·交互
胡西风_foxww7 小时前
【es6复习笔记】函数参数的默认值(6)
javascript·笔记·es6·参数·函数·默认值