likes
comments
collection
share

交互优化|实现手势同时缩放 & 平移

作者站长头像
站长
· 阅读数 6

干杂活也要有干杂活的价值,本来标题上想加个「优雅」,但想想算了,整体实现上并不算优雅,甚至有些难以理解。大部分时间还是花费在看懂之前的代码在干嘛,以及如何最小改动实现需求 ~

背景

最近笔者一直投入在各种移动端 Web 编辑器体验优化上,刚好有一个画布交互优化的需求:

  1. 在之前只能双指缩放画布、单指平移画布,而 App 原生编辑器可以双指同时缩放 + 平移画布。
  2. 增加画布在拖拽后归位的动画效果,提高用户体验。

整个过程还是有点意思,值得记录一下。

实现效果

代码解析

关键点总结

技术实现上有以下3个关键点:

  1. 使用腾讯出品的 alloyfinger 手势库提供各类手势监听能力。这库虽然很古早了,最后一次维护都6年前了,但依然好用 ~
  2. 使用 css transfrom 效果实现跟随手势缩放和平移的操作。
  3. 在手势操作结束后,把平移和缩放结果换算后反馈给画布,然后使用 css transition 动画效果实现拖拽归位的动画效果。

本文就基于以上3点进行代码解析,大佬们如果看了关键点说明能理解做了什么,就可以省流不往下看啦 ~

使用 alloyfinger

alloyfinger 提供的手势很全面,但有些手势并没有在 README 中说明,要看源码有完整的:

交互优化|实现手势同时缩放 & 平移

本文用到的是pinch(缩放)和twoFingerPressMove(双指按住移动)。

还有一点,alloyfinger 没有支持 Typescript,需要自行补充下d.ts

简易版本自取:

declare module 'alloyfinger' {
    export type GestureEvent = TouchEvent & { zoom: number; angle: number; rotate: number };

    export default class AlloyFinger {
        constructor(element: HTMLElement, options: object);
        on(eventName: string, callback: (event: GestureEvent) => void): void;
        off(eventName: string, callback: Function): void;
        destroy(): void;
    }
}

使用上很简单,不废话了,直接看官方示例即可,注意的一点是挂载的组件销毁前记得去gestureDetecter.destroy()

缩放 & 平移手势

pinch(缩放)和twoFingerPressMove(双指按住移动)其实都是双指操作,所以它们是同时响应的,但返回的Event对象不同。

我们还是用2个监听事件来分开处理:

this.gestureDetecter.on('touchStart', (e) => {
    ...
    if (e.touches.length === 2) {
        // 表示双指触摸
        this.handleCanvasPinchStart(e);
        this.handleCanvasTwoFingerPressMoveStart(e);
    }
    ...
});
this.gestureDetecter.on('pinch', this.handleCanvasPinch);
this.gestureDetecter.on('twoFingerPressMove', this.handleCanvasTwoFingerPressMove);

this.gestureDetecter.on('touchEnd', (e) => {
    ...
    if (e.defaultPrevented) return;
    ...
    if (isInCanvasTwoFinger) {
        // 双指触摸结束
        this.handleCanvasTwoFingerEnd();
    }
});

handleCanvasPinchStarthandleCanvasTwoFingerPressMoveStart是当双指开始触摸时,将各种记录变量初始化归位,例如:

/**
 * 双指移动位置
 */
let twoFingerTranslate = { x: 0, y: 0 };
/**
 * 双指缩放
 */
let twoFingerZoom = 0;

handleCanvasPinchhandleCanvasTwoFingerPressMove实时监听当前缩放比例和移动距离。

handleCanvasTwoFingerEnd当双指触摸结束时,需要将 css 处理的缩放移动反馈给画布,修改当前画布位置。

tramsform 响应手势

响应手势这部分很好理解,毕竟手势上返回的Event中有e.zoom(缩放比例)和e.deltaX``e.deltaY(当前手势移动的距离),直接看代码:

handleCanvasPinch(e) {
     // 当前缩放比例
     const zoom = e.zoom;
     ...
     // 换算计算缩放的边缘
     ...
     // 更新 transform
     this.updateTransform();
},

handleCanvasTwoFingerPressMove(e) {
    const nextX = twoFingerTranslate.x + e.deltaX;
    const nextY = twoFingerTranslate.y + e.deltaY;
    // 获取平移后的位置
    twoFingerTranslate = { x: nextX, y: nextY };
    ...
    // 更新 transform
    this.updateTransform();
},

// 更新 transform 样式
updateTransform(hasScale = true) {
    // 获取当前画布 div 实例
    const { shell } = this.editor;
    const scale = hasScale ? `scale(${twoFingerZoom}) ` : '';
    // 更新 transform 样式
    shell.style.transform = `${scale}translate(${twoFingerTranslate.x}px, ${twoFingerTranslate.y}px)`;
        },

手势结束时动画效果

手势结束,除了修正画布位置外(本文不具体展开),还需要提供画布位置修正时有一个过渡动画,而不是突兀的闪回屏幕中心。

handleCanvasTwoFingerEnd() {
    ...
    // 修正画布位置
    ...
    // 清除 transform
    this.$nextTick(() => {
        this.clearTransformWithAnimation();
    , 0);
},

clearTransformWithAnimation() {
    const { shell } = this.editor;

    this.updateTransform(false);
    
    requestAnimationFrame(() => {
        // 增加动画效果
        shell.style.transition = 'transform 0.3s ease-out';
        // 清理 transform,动画效果即为位置平移
        shell.style.transform = '';

        function handleTransitionEnd(event) {
            if (event.propertyName === 'transform') {
                shell.style.transition = '';
                shell.removeEventListener('transitionend', handleTransitionEnd);
            }
        }

        shell.addEventListener('transitionend', handleTransitionEnd);
    });
},

其中有几点细节:

  1. 需要先清除可能存在的this.updateTransform(false);,缩放不能存在动画效果上,视觉上会出现变大又变小的效果。
  2. 要先让 css 清除scale生效,我们需要加上requestAnimationFrame让后续操作在下一帧执行。
  3. 在动画结束后,需要清理画布示例上的 css 动画效果,防止造成画布样式污染,这里使用addEventListener('transitionend')监听即可,还需切记记得removeEventListener防止内存泄漏导致的意外。

总结

虽然交互效果上跟原生已一致,但在体验上确实不如 App 原生手势丝滑。还是那句话,用 H5 实现的体验最多最多只能达到原生开发的 80%。这一点从 FPS 上也能看出来,在很多操作上帧数都远远达不到 60,别说现在越来越多的 120 帧高刷屏。

交互优化|实现手势同时缩放 & 平移
转载自:https://juejin.cn/post/7358368402794512434
评论
请登录