本篇为一移动端博文,个人觉得这篇外文还可以,就翻译了一下,最终实现的一个效果是:用手势创建一个本地菜单(点击一菜单按钮,实现设置一个触摸侧滑,滑动滑出效果,如下文中的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
函数告诉浏览器你要执行一个动画。它要求浏览器调用指定的函数,在下一次重绘之前更新动画。这有什么好处呢
拖动,点击和滑动:额外的东西要考虑移动触摸手势
这些事件需要能够检测和区分拖拽,点击和移动,并相应地做不同的事情。所以,当你玩手机触摸手势,想想:
在我的情况下,我只希望手势的方向是水平的,因为我希望滚动功能正常。我有限制,并且我希望它回到开始或结束。这取决于用户拖动了多少以及手指在屏幕上的速度
你不知道你想知道的关于 - 是超级重要的部分
我知道你想要了解移动触摸手势的有趣部分,但是我必须先介绍这一点,因为它会影响到你的代码。是的,现在是讨论变量的时候了。这好消息是,我也要解释为什么要设置它们的价值。这些功能将使代码看起来更清洁
全局变量和设置默认值
啊,是如此的好玩!看看所需要的变量数量;正是大多数人倾向于跳过的东西。(不要,你会后悔的)
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
引起的跳转
现在已经完成了,下一步就是计算叠加层的淡入效果
重叠计算
目标是:
然而,这些计算并不那么线性。问题始终是打破这些情况下通常使用的三路规则的零
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源码,后台复制该标题回复[手势魅力-设置一个触摸菜单]就可以了的,本人对移动端也只知甚少,文若有误导的地方,请各路大佬多多指正
以下是本篇提点概要
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)