首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手势魅力-设置一个触摸菜单

手势魅力-设置一个触摸菜单

作者头像
itclanCoder
发布2020-10-28 11:24:37
1.8K0
发布2020-10-28 11:24:37
举报
文章被收录于专栏:itclanCoderitclanCoder

序言

本篇为一移动端博文,个人觉得这篇外文还可以,就翻译了一下,最终实现的一个效果是:用手势创建一个本地菜单(点击一菜单按钮,实现设置一个触摸侧滑,滑动滑出效果,如下文中的gif图所示),主要涉及的知识点有移动端三大触摸事件(touchstart,touchmove,touchend),触摸属性,以及实现侧边栏动画,在处理移动端点击,拖动,滑动时,是不得要考虑用户的触摸手势,判断手指在页面上到底是点击还是滑动的,利用原生js的方法封装点击,移动,抬起功能函数,尽管移动(手机)端与pc端有很多相似之处,但还是有很多要注意的地方的,如果你想获得该Demo的源码,复制该标题后台回复[手势魅力-设置一个触摸菜单]就可以了的,初次翻译,如果有误导的地方,欢迎路过的老师,多提意见和指正,如果你想阅读英文原文,扫文末下方二维码或者跳转到指定链接就可以了的

正文从这里开始~

最终代码实现效果图所示:

前戏

触摸和手势驱动设备的兴起,极大地改变了我们思考交互的方式。手势不仅仅是娱乐性的,它们非常有用,也很熟悉

移动触摸手势已成为每个应用程序的重要组成部分,大多数用户甚至没有意识到的一部分。谁不喜欢(流畅)的互动应用程序?

然而至今,憎恨可能伴随的混乱和数学是非常容易的。我知道,令人震惊的是,尤其是当你不是第一次码代码的人,或者你只是在那里维护它的时候

有时候,这可能是一个吃力不讨好的工作。那种让你用一只手盯着屏幕,另一只手放在你的额头上,另一只手放在鼠标上滚动的时间 有 - 我敢说呢? - 如丝般流畅的手势触摸手势和动画可能是一个挑战,并随着时间的推移变得越来越突出。但这是另一天的战斗。或另一篇文章。或两者

今天,我们要告诉你如何用手势创建一个本地菜单

让羊驼走上舞台! 所以,在我转向实际的代码之前,在那里有一些我想要经历的事情,所以请耐心等待

HTML结构

<!-- layout开始 -->
    <div class="layout">
        <!-- 头部开始 -->
        <div class="header">
            <div class="header-top">
                <!-- 菜单按钮 -->
                <div data-link="" class="app-menu-burger OSFillParent" href="#" id="b2-Menu">
                    <!-- 三条横岗线,这里其实完全用一小图片或者icon字体图标代替 -->
                    <div data-container="" class="app-menu-line"></div>
                    <div data-container="" class="app-menu-line OSAutoMarginTop"></div>
                    <div data-container="" class="app-menu-line OSAutoMarginTop"></div>
                </div>
            </div>
        </div>
        <!-- 头部结束 -->
        <!-- 中间部分开始 -->
        <div data-container="" class="center-content">
            <!-- 中间图像小图片 -->
            <div class="center-content-header ph" id="b3-Top"></div>
            <div class="center-content-container ph" id="b3-Center">
                <!--中间图片 -->
                <div data-container="">
                    <div data-container="" style="text-align: center;"><img data-image="" src="https://s11.postimg.org/409mhl043/icon.png">
                    </div>
                    <div data-container="" class="OSAutoMarginTop" style="text-align: center;">
                        <h1>Welcome to itclanCoder!<br></h1>
                        <h3>手势魅力:设置一个触摸侧滑菜单<br>阅读原文即可</h3>
                    </div>
                </div>
            </div>
            <div class="center-content-bottom ph" id="b3-Bottom"></div>
        </div>
        <!-- 中间部分结束 -->
        <!-- 左侧带单栏开始 -->
        <div class="menu">
            <!-- 黑色遮罩 -->
            <div class="menu-background" style="cursor: pointer;"></div>
            <div class="app-menu-container">
                <div class="app-menu">
                    <!-- 侧栏顶部开始 -->
                    <div class="top">
                        <!-- 侧栏顶部图片 -->
                        <div class="top-overlay">
                            <img src="https://s11.postimg.org/409mhl043/icon.png" width=80>
                        </div>
                    </div>
                    <!-- 侧栏顶部结束 -->
                    <!-- 侧栏列表内容开始 -->
                    <div class="bottom">
                        <!-- 欢迎关注微信itclanCoder公众号,个人微信号:suibichuanji -->
                        <div class="list-item">Welcome itclanCoder</div>
                        <div class="list-item">Fancy Time</div>
                        <div class="list-item">Tea Time</div>
                        <div class="list-item">Adventure Time</div>
                        <div class="list-item">Puzzle Time</div>
                        <div class="list-item">Sports Time</div>
                        <div class="list-item">Star Wars Time</div>
                        <div class="list-item">Internet Time</div>
                        <div class="list-item">Sushi Time</div>
                        <div class="list-item">weChatPublicId:itclanCoder</div>
                        <div class="list-item">personId:suibichuanji</div>
                    </div>
                    <!-- 侧栏列表内容 -->
                </div>
            </div>
        </div>
        <!-- 左侧菜单栏结束 -->
    </div>
    <!-- layout 结束 -->

由于css有点多,这里就不贴代码了的,本文着重在于l理解js,但这并不代表css就不重要,只是这里权重没那么大,相信对于css还是较为容易看懂的,如果你想获得该Demo的源码,复制该标题后台回复[手势魅力-设置一个触摸菜单]就可以了的,我对js,css中的代码也做了一些简要的注释,其实看到命名,j在结合文章内容,就应该很容易理解各个变量是什么意思了

所有你需要了解的JavaScript触摸事件

我将使用JavaScript事件来检测我的移动触摸手势。在这种情况下在那里是:

  • touchstart:当你触摸DOM元素时触发
  • touchmove:当你沿着DOM元素拖动手指时触发
  • touchend:当你从DOM元素中移除手指时触发

在这些事件中,我将使用触摸属性(在那里还有两个属性,但这是我现在关心的)。触摸属性列出当前在屏幕上的所有手指:

  • PageX:返回手指放置在DOM中的x坐标。从左边开始计算,如果适用,则考虑水平滚动
  • PageY:返回手指放置在DOM中的y坐标。它是从顶部边缘测量的,并考虑垂直滚动(如果适用)

而你也需要知道关于requestAnimationFrame

requestAnimationFrame函数告诉浏览器你要执行一个动画。它要求浏览器调用指定的函数,在下一次重绘之前更新动画。这有什么好处呢

  • 浏览器将尝试匹配显示刷新,以允许流畅的动画
  • 非活动选项卡中的动画将停止(在CPU上花费的更少)
  • 它不会耗尽你的电池寿命

拖动,点击和滑动:额外的东西要考虑移动触摸手势

这些事件需要能够检测和区分拖拽,点击和移动,并相应地做不同的事情。所以,当你玩手机触摸手势,想想:

  • 限制:你想要什么元素停止?您希望它在每次拖动时移动多远?
  • 这个手势的方向:你想只能水平移动,或者还是垂直移动?也许是两个?
  • 拖动完成后你想要发生什么?它会回到开始还是结束,取决于它在哪里结束?它是否考虑到速度?
  • 详情:我们是否正在用这个手势记住速度?你想在菜单后面加一个遮罩,当你打开它时会变得越来越暗吗?

在我的情况下,我只希望手势的方向是水平的,因为我希望滚动功能正常。我有限制,并且我希望它回到开始或结束。这取决于用户拖动了多少以及手指在屏幕上的速度

你不知道你想知道的关于 - 是超级重要的部分

我知道你想要了解移动触摸手势的有趣部分,但是我必须先介绍这一点,因为它会影响到你的代码。是的,现在是讨论变量的时候了。这好消息是,我也要解释为什么要设置它们的价值。这些功能将使代码看起来更清洁

全局变量和设置默认值

啊,是如此的好玩!看看所需要的变量数量;正是大多数人倾向于跳过的东西。(不要,你会后悔的)

var trackableElement;  // 可追踪元素
    var menu = document.querySelector(".menu");// querSelector是h5新增查找元素方法,返回匹配指定 CSS 选择器元素的第一个子元素,侧边栏菜单元素
    var appMenu = document.querySelector(".app-menu-container");// 左侧边栏app-menu-container应用程序菜单容器
    var overlay = document.querySelector(".menu-background"); // 左侧菜单栏黑色背景遮罩,覆盖
    var burger = document.querySelector(".app-menu-burger");   // 三条横岗菜单按钮
var touchingElement = false;   //触摸元素,开关
    var startTime;                  // 开始时间
    var startX = 0,
       startY = 0;
    var currentX = 0,
       currentY = 0;
    var isOpen = false;
    var isMoving = false;
    var menuWidth = 0;
    var lastX = 0;
    var lastY = 0;
    var moveX = 0; // 屏幕上的哪个位置是当前的菜单where in the screen is the menu currently

    var dragDirection = "";  // 拖拽方向
    var maxOpacity = 0.5;    
    // 最大透明度,如果你想改变这个,不要忘记改变CSS类中不透明的值if you want to change this, don’t forget to change the opacity value 
    // of the ‘.menu--visible .menu-background’ CSS class

    var init = function(element, start, move, end) {
             trackableElement = element;

             startTime = new Date().getTime(); // 开始时间的触摸start time of the touch

             addEventListeners();  // 元素触发,函数事件的调用
     }

  var addEventListeners = function() {
      trackableElement.addEventListener("touchstart", onTouchStart, false);
      trackableElement.addEventListener("touchmove", onTouchMove, false);
      trackableElement.addEventListener("touchend", onTouchEnd, false);

      overlay.addEventListener("click", closeMenuOverlay, false); 
      // 我希望能够点击覆盖,并立即关闭菜单(在实际的菜单和它后面的页面之间的空间I want to be able to click the overlay and immediately close the menu
      // 点击三缸线按钮,并立即打开菜单 open the menu(in the space between the actual menu and the page behind it)
  }

非常简单,真的。按照这个顺序,代码不那么混乱,不那么可怕,而且更容易消化

函数中的函数

这些函数被 EventListener调用,即使它们不是做实际的动画或者使菜单工作所必需的计算

// onTouchStart手指按下功能函数
function onTouchStart(evt) {
  startTime = new Date().getTime();  // 开始时间
  startX = evt.touches[0].pageX;     // 手指点下距离x轴的坐标
  startY = evt.touches[0].pageY;     // 手指点下距离y轴的坐标 

  touchingElement = true;           

  touchStart(startX, startY);       // touchStart功能函数调用,并设置两个实际参数startX,startY
}// 手指移动函数

function onTouchMove(evt) {
  if (!touchingElement)
    return;

    currentX = evt.touches[0].pageX;
    currentY = evt.touches[0].pageY;
    const translateX = currentX - startX; // distance moved in the x axis
    const translateY = currentY - startY; // distance moved in the y axis

    touchMove(evt, currentX, currentY, translateX, translateY); // touchStart功能函数调用,并设置两个实际参数startX,startY
}
// 手指抬起功能函数

function onTouchEnd(evt) {

  if (!touchingElement)
    return;

    touchingElement = false;
    const translateX = currentX - startX; // 距离在x轴上移动distance moved in the x axis
    const translateY = currentY - startY; // 距离在y轴上移动distance moved in the y axis

    const timeTaken = (new Date().getTime() - startTime); // 时间戳
}

所有这些变量都用于动画所涉及的数学运算。为了可读性,在函数中没有太多的代码行,我把它们全部分成了小的一行

这个手机触摸手势最后有趣的一部分

现在我对触摸事件,变量和函数的解释已经不存在了,现在是我关注如何创建动画的时候了。这正是菜单移动以及所有数学和算法背后的原因 动画开始

// 兼容性写法,手指抬起
function touchStart(startX, startY) {
     var menuOpen = document.querySelector(".menu.menu--visible");

     if (menuOpen !== null) {
        isOpen = true;
     } else {
        isOpen = false;
     }
    menu.classList.add("no-transition");
     appMenu.classList.add("no-transition");

     isMoving = true;
    menuWidth = document.querySelector(".app-menu").offsetWidth;
    lastX = startX;
    lastY = startY;

    if (isOpen) {
      moveX = 0;
    } else {
      moveX = -menuWidth;
    }
    dragDirection = "";
    menu.classList.add("menu--background-visible"); 
    // why is this being added? ‘.menu--background-visible .menu-background’ makes the overlay 
    // ‘active’, displaying it on the DOM for those sweet opacity changes.
}

每次触摸屏幕时,这些代码都会运行。此功能将用作重置为默认值,具体取决于你上次提起手指后菜单发生了什么

动画中间

function touchMove(evt, currentX, currentY, translateX, translateY) {
  if (!dragDirection) {
    if (Math.abs(translateX) >= Math.abs(translateY)) {
      dragDirection = "horizontal";
    } else {
      dragDirection = "vertical";
    }

    requestAnimationFrame(updateUi); 
    // this is what actually does the animation
  }
  // ...
}

你想知道的第一件事是手势的方向

在菜单中,垂直滚动真的不是什么可以关心的东西。意思是,在与手势相关的代码方面,行为本身应该是默认滚动。因此,确定当什么时候这是需要的

if (dragDirection === "vertical") {
    lastX = currentX;
    lastY = currentY;
} else {
    evt.preventDefault(); 
    // ...
}

没有 preventDefault,这是会发生什么事情:

这绝对不是你想要用你的手机触摸手势发生的事情,所以考虑一下:当你打开/关闭菜单时,你是否有兴趣阅读滚动隐藏的内容?如果你的拖拽方向是水平的,你就不能滚动

我们需要一些边界在这里! (设置限制)

if (moveX + (currentX - lastX) < 0 && moveX + (currentX - lastX) > -menuWidth) {
  moveX = moveX + (currentX - lastX);
  // ...
}

所以,记得我说我有限制吗?在这个例子中,菜单隐藏在屏幕的左边。所以,如果菜单是关闭的,变量 moveX开始为 -menuWidth- 我希望它被拖动到右边,直到完全显示

moveX + (currentX - lastX)

你可以称之为移动间隔。这就是告诉脚本菜单在窗口中的确切位置。我使用 moveX是因为我做了实际的动画。转到 updateUI函数 - ` requestAnimationFrame调用的函数 - 这就是你所拥有的

function updateUi() {
  if (isMoving) {
    var element = document.querySelector(".app-menu-container");

    element.style.transform = "translateX(" + moveX + "px)";
    element.style.webkitTransform = "translateX(" + moveX + "px)";

    requestAnimationFrame(updateUi);
  }
}

我希望动画无缝平滑。为此,脚本可以检测到并用于 translateX的时间间隔越小越好。目标不是看到使用 translateX引起的跳转

现在已经完成了,下一步就是计算叠加层的淡入效果

重叠计算

目标是:

  • 当moveX = -menuWidth时,不透明度= 0
  • 当movX = 0,不透明度= 0.5

然而,这些计算并不那么线性。问题始终是打破这些情况下通常使用的三路规则的零

overlay.classList.add("no-transition");

var percentageBeforeDif = (Math.abs(moveX) * 100) / menuWidth;
var percentage = 100 - percentageBeforeDif;

在这里,我确保 menuWidth对应于100%,当前位置(moveX)对应于百分比。在这个计算中我追求的百分比是

var newOpacity = (((maxOpacity) * percentage) / 100);

这个计算是需要的,因为不透明度只有在0到0.5之间(如在变量中定义的)之后才有效。如果0.5不透明度与100%相关,则百分比将是期望的不透明度

动画结束

function touchEnd(currentX, currentY, translateX, translateY, timeTaken) {
  isMoving = false;
  var velocity = 0.3;
  //... 
}

首先要记住的是,有人可以简单地点击,事件认为这是一个摸索和touchend。如果这是一个点击,菜单上没有任何事情发生

if (translateX === 0 && translateY === 0) {
  if (isOpen) {
    appMenu.classList.remove("no-transition");
    menu.classList.remove("no-transition");
  } else {
    menu.classList.remove("menu--background-visible");
    menu.classList.remove("no-transition");
  }
}

拖动结束后会发生什么?

  • 当菜单打开时,它可以关闭或保持打开状态 - 与动画一起 - 返回之前的位置
  • 如果它关闭了,那么它可以打开或者保持关闭状态,也可以在动画返回之前
if ((translateX < (-menuWidth) / 2) || (Math.abs(translateX) / timeTaken > velocity)) { 
  // if menu is open, this represents the close condition


if (translateX > menuWidth / 2 || (Math.abs(translateX) / timeTaken > velocity)) { 
  // if menu is closed, this represents the open condition

那么什么被认为足以打开菜单?五个像素移动?那么这个菜单可以根据距离打开或关闭。也就是说,如果将其拖过宽度的中间,并且拖动的速度大于定义的速度(也就是若手指拖动侧边栏超过该菜单栏本身宽度的一半位置的话,或者拖动的速度大于刚开始定义的速度,则该侧边栏就关闭或者打开的,若不是,则恢复初始前一个位置的)

就这样,你有一个工作的触摸式菜单!

总结

对本文进行总结一下,首先这个效果在我们平日的手机应用里,非常的常见,实现这一效果,主要利用的是移动端三大事件touchstart,touchmove,touchend,以及它们的触摸属性,也就是手指在屏幕DOM中的实际位置,这时,需要考虑手指是水平滑动还是垂直,甚至有时候还得考虑手倾斜的滑动,还要区分是一根手指滑动,还是多根手指滑动,侧边菜单栏动画的实现,以及要注意阻止默认事件,重叠计算等等一些细节

看似简单的效果,整个过程实现起来,还是不容易的,当然很多时候,在平时中,想当然的会用一些框架,移动端库来代替原生当中一些繁琐的写法的,原生js固然耐人耗脑,其实甭管咋实现,只要能实现就好,最后在重复一遍,若想获得本篇Demo源码,后台复制该标题回复[手势魅力-设置一个触摸菜单]就可以了的,本人对移动端也只知甚少,文若有误导的地方,请各路大佬多多指正

以下是本篇提点概要

  • HTML结构
  • 所有你需要了解的JavaScript触摸事件(touchstart,touchmove,touchend),以及两个触摸属性pageX,pageY
  • 需要知道关于requestAnimationFrame
  • 拖动,点击和滑动:额外的东西要考虑移动触摸手势(手势方向,水平,垂直,还有手指根数)
  • 你不知道你想知道的关于 - 是超级重要的部分
  • 全局变量和设置默认值(一些初始化值变量的设置)
  • 函数中的函数(手指按下,移动,抬起功能函数的封装调用)
  • 这个手机触摸手势最后有趣的一部分(创建动画)
  • 动画中间(手指移动,拖动菜单过程)
  • 我们需要一些边界在这里!(设置限制),也就是侧边栏菜单滑动的位置
  • 重叠计算(透明度变化,也就是用小数来计算,百分比值)
  • 动画结束(菜单栏打开和关闭状态,菜单栏的位置)

以下是本文陌生词汇(仅供参考)

1. Gestures Glamour: 手势魅力   2. dramatically:显着,极大的
   3. interaction:交互,相互作用    4.detect:检测
   5. scenario:脚本            6. Triggered:触发
   7. coordinate:坐标          8. perform:执行,演出,表演
   9. repaint:重绘            10. smooth:流畅的,滑动
   11. inactive:迟钝,非活动      12. battery:电池
   13. distinguish:区分         14. straightforward:直截了当
   15. cluttered:凌乱          16. digest:消化
   17. calculations:计算        18. readability:可读性
   19. involved:参与,用于        20. separated:分离
   21. algorithms:算法          22. identifying:识别,确定
   23. seamless:无缝           24. corresponds:对应
   25. percentage:百分比        26. velocity:速度

阅读英文原文打开底下链接

(https://medium.com/outsystems-experts/gestures-glamour-setting-up-a-touch-menu-6d9b94039997)

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-02-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 itclanCoder 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 序言
  • 前戏
  • 以下是本文陌生词汇(仅供参考)
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档