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

2018-02-11 14:08:10来源:https://juejin.im/post/5a7b3b55f265da4e983efb96作者:稀土掘金人点击

分享

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


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





前戏

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


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


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


有时候,这可能是一个吃力不讨好的工作。那种让你用一只手盯着屏幕,另一只手放在你的额头上,另一只手放在鼠标上滚动的时间


有 - 我敢说呢? - 如丝般流畅的手势触摸手势和动画可能是一个挑战,并随着时间的推移变得越来越突出。但这是另一天的战斗。或另一篇文章。或两者


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





所以,在我转向实际的代码之前,在那里有一些我想要经历的事情,所以请耐心等待


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><!-- react-text: 75 -->Welcome to itclanCoder!<!-- /react-text --><br></h1>
<h3><!-- react-text: 78 -->手势魅力:设置一个触摸侧滑菜单<!-- /react-text --><br><!-- react-text: 80 -->阅读原文即可 <!-- /react-text --></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 结束 -->
所有你需要了解的JavaScript触摸事件

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


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

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


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

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


浏览器将尝试匹配显示刷新,以允许流畅的动画
非活动选项卡中的动画将停止(在CPU上花费的更少)
它不会耗尽你的电池寿命
拖动,点击和滑动:额外的东西要考虑移​​动触摸手势

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


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

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


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

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


全局变量和设置默认值

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


var trackableElement;
var menu = document.querySelector(".menu");
var appMenu = document.querySelector(".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;
// 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
// (in the space between the actual menu and the page behind it)
}

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


函数中的函数

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


function onTouchStart(evt) {
startTime = new Date().getTime();
startX = evt.touches[0].pageX;
startY = evt.touches[0].pageY;
touchingElement = true;
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);
}
function onTouchEnd(evt) {
if (!touchingElement)
return;
touchingElement = false;
const translateX = currentX - startX; // distance moved in the x axis
const translateY = currentY - startY; // 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源码,itclanCoder后台复制该标题回复[手势魅力-设置一个触摸菜单] 就可以下载了


以下是本篇提点概要


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

原文地止: 手势魅力-设置一个触摸菜单 点击该链接可读英文原文





最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台