day-095-ninety-five-20230620-移动端事件处理-响应式
移动端事件处理
移动端的事件处理
- 移动端事件处理
- PC端主要以:
鼠标事件
、键盘事件
、资源加载事件
、动画事件
等事件为主。- 其中
click
在PC端
是点击事件
!
- 其中
- 移动端主要以:
手指事件
(单手指
和多手指
)、资源加载事件
、动画事件
等为主。- 其中,
click
在移动端
是单击事件
。
- 其中,
- PC端主要以:
移动端事件问题
- 移动端事件的各个问题:
-
问题1:
click事件
在移动端
存在300ms的延迟
。- 原因:
click事件
在移动端
是单击事件
:- 在第一次点击后,需要观察
300ms
,看是否触发了第二次点击;- 如果
没有触发第二次点击
,则为单击操作
,触发click事件
。 - 如果
触发了第二次点击
,则为双击操作
,click事件
是不触发的!
- 如果
- 在第一次点击后,需要观察
- 解决方案:
-
用
touch事件模型
-即单手指事件模型
,来代替click事件
。-
touchstart
:手指开始触摸。 -
touchmove
:手指移动。 -
touchend
:手指离开。<!DOCTYPE html> <html><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>基于touch实现点击操作</title><style>.box {position: absolute;top: 20px;left: 40px;box-sizing: border-box;width: 100px;height: 100px;border: 1px solid #000;}</style></head><body><div class="box"></div><!-- IMPORT JS --><script>// 基于touch事件模型模拟出“点击”的效果const box = document.querySelector(".box");// 简易的处理方法:只要手指离开盒子,则认为触发了点击操作「这样是不准确的,如果手指之前发了移动,则本操作不再是点击,而是滑动」box.ontouchend = function () {this.style.background = "pink";};</script></body> </html>
-
touchcancel
:因意外情况如手机没电,导致touch事件取消
了。 -
但是这种方式,如果需要自己去实现,太繁琐了!
<!DOCTYPE html> <html><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>基于touch实现点击操作</title><style>.box {position: absolute;top: 20px;left: 40px;box-sizing: border-box;width: 100px;height: 100px;border: 1px solid #000;}</style></head><body><div class="box"></div><!-- IMPORT JS --><script>// 基于touch事件模型模拟出“点击”的效果const box = document.querySelector(".box");box.ontouchstart = function (ev) {/*手指按下的时候:记录手指起始的坐标位置ev:TouchEventtouches/targetTouches/changedTouches:都记录了手指位置的相关信息「伪数组」我们平时都用changedTouches,因为其可以在 touchend 事件中,记录出手指离开屏幕时的坐标*/let finger = ev.changedTouches[0];this.startX = finger.pageX;this.startY = finger.pageY;this.isMove = false;};box.ontouchmove = function (ev) {/*手指移动的时候:获取最新的手指坐标,减去起始坐标,计算出偏移的距离在给定的误差值(一般都是10px)范围内,计算是否发生移动*/let finger = ev.changedTouches[0];let changeX = finger.pageX - this.startX,changeY = finger.pageY - this.startY;if (Math.abs(changeX) > 10 || Math.abs(changeY) > 10)this.isMove = true;this.changeX = changeX;this.changeY = changeY;};box.ontouchend = function (ev) {/*手指离开屏幕的时候:判断是移动还是点击操作如果是移动操作,还可以基于偏移的距离算出移动的方向*/let { isMove, changeX, changeY } = this;if (!isMove) {console.log("当前是点击操作");this.style.background = "pink";return;}if (Math.abs(changeX) >= Math.abs(changeY)) {// 是左右滑动if (changeX >= 0) {console.log("向右滑动");} else {console.log("向左滑动");}return;}// 是上下滑动if (changeY >= 0) {console.log("向下滑动");} else {console.log("向上滑动");}};/*移动端的常规操作,基本上都是基于 touchstart/touchmove/touchend 模拟出来的+ 模拟点击+ 模拟滑动「知道滑动方向」+ 模拟单击/双击「300ms」+ 模拟长按「750ms」+ ...对于一些需要多根手指进行的操作,可以基于 gesturestart/gesturechange/gestureend 模拟出来+ 缩放+ 旋转+ ...*/</script></body> </html>
-
-
基于一些现有的封装好的事件库来解决。
-
fastclick.js 只能解决
click事件
的300ms延迟
问题。- 适用于:操作简单的移动端产品上,在此产品上,只有点击行为,此时我们继续使用
click事件
,只不过基于fastclick插件
,把其300ms延迟
处理掉即可! - 原理:基于
事件委托
,对页面中的click行为
做统一的处理
,核心还是基于touch事件模型
解决的! - 代码:
-
JS高级进阶/day0619_QQMusic/js/fastclick.js
-
JS高级进阶/day0619_QQMusic/touchDemo.html
<!DOCTYPE html> <html><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>基于touch实现点击操作</title><style>.box {position: absolute;top: 20px;left: 40px;box-sizing: border-box;width: 100px;height: 100px;border: 1px solid #000;}</style></head><body><div class="box"></div><script src="js/fastclick.js"></script><script>// 这样的处理在移动端会有300ms延迟问题// 此时我们基于 fastclick 插件处理一下即可FastClick.attach(document.body);const box = document.querySelector(".box");box.onclick = function () {console.log(`fastclick`);this.style.background = "pink";};</script></body> </html>
-
- 适用于:操作简单的移动端产品上,在此产品上,只有点击行为,此时我们继续使用
-
Zepto.js 被称为
移动端的jQuery库
,语法和jQuery
非常类似,但是能够更好的支持移动端。- 相比较于
jQuery
来讲:- 不考虑
IE8
及IE8以下版本
的兼容
。 - 只实现了
jQuery
中最常用
、最核心
的方法
。- 导致
Zepto
比jQuery库
小很多。
- 导致
- 支持
css3动画
。 - 封装了一套
完善的移动端事件处理方案
。
- 不考虑
- 支持的移动端事件操作。
tap
点击。singleTap
单击。doubleTap
双击。longTap
长按。swipe
、swipeLeft
/swipeRight
/swipeDown
/swipeUp
滑动。pinchIn
/pinchOut
缩放。- …
- 但是
Zepto
和jQuery
一样,其大部分代码是用来操作DOM
的,已经不适用于当下的Vue开发
/React开发
了。 - 代码:
-
JS高级进阶/day0619_QQMusic/js/zepto.min.js
-
JS高级进阶/day0619_QQMusic/touchDemo.html
<!DOCTYPE html> <html><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>基于touch实现点击操作</title><style>.box {position: absolute;top: 20px;left: 40px;box-sizing: border-box;width: 100px;height: 100px;border: 1px solid #000;}</style></head><body><div class="box"></div><script src="js/zepto.min.js"></script><script>$(".box").tap(function () {console.log(`zepto-tap`);$(this).css({background: "pink",});});</script></body> </html>
-
- 相比较于
-
hammer.js 移动端专属的事件库
- hammer.js
- 代码:
-
JS高级进阶/day0619_QQMusic/js/hammer.min.js
-
JS高级进阶/day0619_QQMusic/touchDemo.html
<!DOCTYPE html> <html><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>基于touch实现点击操作</title><style>.box {position: absolute;top: 20px;left: 40px;box-sizing: border-box;width: 100px;height: 100px;border: 1px solid #000;}</style></head><body><div class="box"></div><script src="js/hammer.min.js"></script><script>const box = document.querySelector(".box");const instHammer = new Hammer(box);instHammer.on("tap", function () {console.log(`hammer-tap`);box.style.background = "pink";});</script></body> </html>
-
-
-
- 原因:
-
问题2:事件穿透问题
- 事件传透的是指:触发某个目标元素的触摸事件(touch事件)时,会同时触发
该目标元素相同位置中其他元素
的鼠标点击click事件
。- 触发步骤:
- 触摸第一层,让第一层隐藏。
- 露出第二层,而第二层是基于click事件处理的!
- 触发步骤:
- 事件触发的先后顺序是:
- 解决方案:click和touch事件不要混合在一起使用!!
- 事件传透的是指:触发某个目标元素的触摸事件(touch事件)时,会同时触发
-
问题3:keydown/up/press等事件在移动端用不了,统一基于input事件代替即可!
- 文本框.οninput=function(){}
- 只要文本框中有内容的输入,则input事件就会触发!
-
移动端模拟点击操作
没做处理-直接用click代替
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><title>基于touch实现点击操作</title><style>.box {position: absolute;top: 20px;left: 40px;box-sizing: border-box;width: 100px;height: 100px;border: 1px solid #ccc;}</style></head><body><div class="box"></div></body>
</html><script>const box = document.querySelector(".box");// 这样的处理在移动端会有300ms延迟问题。box.onclick = function () {this.style.background = "pink";};
</script>
简易的处理方法-直接用touchend代替
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><title>基于touch实现点击操作</title><style>.box {position: absolute;top: 20px;left: 40px;box-sizing: border-box;width: 100px;height: 100px;border: 1px solid #ccc;}</style></head><body><div class="box"></div></body>
</html><script>// 基于touch事件模型模拟出点击的效果。const box = document.querySelector(".box");// 简易的处理方法:只要手指离开盒子,则认为触发了点击操作。// 这样是不准确的,如果手指之前发生了移动,则本操作不再是点击,而是滑动。box.ontouchend = function () {console.log(`touchend-->`);this.style.background = "pink";};
</script>
用touch事件模型来代替click事件
- 思路:
- 手指按下的时候:记录手指起始的坐标位置。
- ev:TouchEvent
- touches/targetTouches/changedTouches:都记录了手指位置的相关信息,结果是一个伪数组。
- 我们平时都用changedTouches,因为其可以在touchend事件中,记录出手指离开屏幕时的坐标。
- touches/targetTouches/changedTouches:都记录了手指位置的相关信息,结果是一个伪数组。
- ev:TouchEvent
- 手指移动的时候:获取最新的手指坐标,减去起始坐标,计算出偏移的距离。
- 在给定的误差值(一般都是10px)范围内,计算是否发生移动。
- 手指离开屏幕的时候:判断是移动还是点击操作。
- 如果是移动操作,还可以基于偏移的距离算出移动的方向。
- 移动端的事件
- 移动端的常规操作,基本上都是基于touchstart/touchmove/touchend模拟出来的。
- 模拟点击。
- 模拟滑动-知道滑动方向。
- 模拟单击/双击-300ms。
- 模拟长按-750ms。
- …
- 对于一些需要多根手指进行的操作,可以基于gesturestart/gesturechange/gestureend模拟出来。
- 缩放。
- 旋转。
- …
- 移动端的常规操作,基本上都是基于touchstart/touchmove/touchend模拟出来的。
- 手指按下的时候:记录手指起始的坐标位置。
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><title>基于touch实现点击操作</title><style>.box {position: absolute;top: 20px;left: 40px;box-sizing: border-box;width: 100px;height: 100px;border: 1px solid #ccc;}</style></head><body><div class="box"></div></body>
</html><script>// 基于touch事件模型模拟出点击的效果。const box = document.querySelector(".box");box.ontouchstart = function (ev) {// 手指按下的时候:记录手指起始的坐标位置。// ev:TouchEvent// - touches/targetTouches/changedTouches:都记录了手指位置的相关信息,结果是一个伪数组。// - 我们平时都用changedTouches,因为其可以在touchend事件中,记录出手指离开屏幕时的坐标。// console.log(`ev-->`, ev);let finger = ev.changedTouches[0];// console.log(`finger-->`, finger);this.startX = finger.pageX;this.startY = finger.pageY;this.isMove = false;};box.ontouchmove = function (ev) {// 手指移动的时候:获取最新的手指坐标,减去起始坐标,计算出偏移的距离。// 在给定的误差值(一般都是10px)范围内,计算是否发生移动。let finger = ev.changedTouches[0];// console.log(`finger-->`, finger);let changeX = finger.pageX - this.startX;let changeY = finger.pageY - this.startY;if (Math.abs(changeX) > 10 || Math.abs(changeY) > 10) {this.isMove = true;}this.changeX = changeX;this.changeY = changeY;};box.ontouchend = function (ev) {// 手指离开屏幕的时候:判断是移动还是点击操作。// 如果是移动操作,还可以基于偏移的距离算出移动的方向。let { isMove, changeX, changeY } = this;if (!isMove) {console.log(`当前是点击操作`);this.style.background = "pink";return;}if (Math.abs(changeX) >= Math.abs(changeY)) {// 是左右滑动。if (changeX >= 0) {console.log(`向右滑动`);} else {console.log(`向左滑动`);}return}// 是上下滑动。if (changeY >= 0) {console.log(`向下滑动`);} else {console.log(`向上滑动`);}};// - 移动端的常规操作,基本上都是基于touchstart/touchmove/touchend模拟出来的。// - 模拟点击。// - 模拟滑动-知道滑动方向。// - 模拟单击/双击-300ms。// - 模拟长按-750ms。// - ...// - 对于一些需要多根手指进行的操作,可以基于gesturestart/gesturechange/gestureend模拟出来。// - 缩放。// - 旋转。// - ...
</script>
基于fastclick插件处理
基于zepto进行处理
基于hammer进行处理
响应式
响应式布局开发技巧
- 需要做响应式:
- PC端全屏项目(一般都是管理系统)
- 技术方案:外层容器的宽高采用百分比布局-如vw与vh,一些具体的元素基本都是固定布局。偶尔基于@media进行微调。
- PC端和移动端共用一套项目(一般是结构和样式较为简单的企业官网/宣传页等)
-
技术方案:流式布局(外层容器的宽度按照百分比方式处理),基于@media进行结构和样式的调整。
@media all and (max-width:960px) {.header{//...}... }
- 基于@media调整样式越细,展示效果会越好!
- 核心就是大量写样式。
-
- 移动端项目(适配不同型号的手机设备,有的还需要适配pad端)
- 传统方案:基于@media进行样式调整,尽可能适配更多的设备,这是官方提供的方案。
- 这也是官方唯一认可方案:@media。
- 发展:
- 固定布局:只写最小的尺寸,如320px,中间居中。
- …
- 2023年目前:rem等比缩放。
- 新方案:rem等比缩放。
- 不论那一种方案,目前做排列布局,基本上都是基于flex来处理!
- 传统方案:基于@media进行样式调整,尽可能适配更多的设备,这是官方提供的方案。
- PC端全屏项目(一般都是管理系统)
rem响应式布局方案
-
移动端响应式布局第一步:设置meta标签的viewport。
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
- viewport:设置视口(或html页面)的规则。
- width=device-width 让HTML渲染的宽度和设备宽度保持一致。
- initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0 不让HTML页面进行缩放。
- maximum-scale=1.0, minimum-scale=1.0 是为了兼容安卓低端机。
- user-scalable=no 禁止手动缩放。
- 如果不设置meta标签的viewport会出现什么问题?
- 不论手机设备多宽,HTML页面都是以980px的宽度进行渲染。这样就会出现,手机屏幕渲染不全的情况。
- 不完全渲染:就会出现横竖向的滚动条。
- 完全渲染:就要把页面整体缩小,所有内容都会变得很小。
- 不论手机设备多宽,HTML页面都是以980px的宽度进行渲染。这样就会出现,手机屏幕渲染不全的情况。
- viewport:设置视口(或html页面)的规则。
-
rem响应式布局方案
- 什么是rem?
-
px物理像素,固定单位。
-
em相对单位,相对于父元素的字体大小。
-
一般用于段落首行缩进两个汉字之类的效果。
.box{font-size:14px;//此时在该元素的子元素中1em=14px;p{text-indent:2em;//首行缩进两个字符。} }
-
-
rem(root em) 相对单位,相对于根元素(也就是html)的字体大小。
html{font-size:20px;//1rem=20px。浏览器能识别的最小字体是12px。 } .box1{width:100px; } .box2{width:5rem; } //目前box1和box2是一样大的。 //但是如果以后,把html的字体改为30px了,那么box1依然是100px,但是box2则自动变为150px了! //也就是:只要修改根元素的字体大小,那么所有以rem为单位的样式,都会自动跟着等比缩放。
- 也就是:只要修改根元素的字体大小,那么所有以rem为单位的样式,都会自动跟着等比缩放。
-
- 基于rem实现响应式布局开发的步骤:
-
第一步:按照特定的尺寸(一般是设计稿的尺寸),设置rem和px的初始转换比例,然后把测量出来的像素值,全部按照这个比例,改为rem值,赋值给元素的样式!
-
设计稿的尺寸:一般都是750px的。
- 但UI组件库的设计稿一般都是375px的。
-
rem和px的初始转换比例:设置的值一定是方便计算的!
html{font-size:16px;//1rem=100px ; 750px }
量出来一个盒子大小是300px*260px。font-size为28px。
.box{width:3rem;height:2.6rem;font-size:0.28rem; }
-
-
第二步:获取当前设备的尺寸,计算出相比较于设计稿而言,缩放的比例。然后按照这个比例,去修改html的字体大小(也就是rem和px的换算比例)。
- 公式:当前设备的宽度/当前的换算比例=设计稿宽度(750px)/初始换算比例(100)
- 当前的换算比例=(当前设备的宽度/设计稿宽度(750px))*初始换算比例(100)
- 基于公式计算出最新的换算比例后,修改html的字体大小,那么之前所有以rem为单位的样式,都会按照最新的换算比例,实现等比缩放。
- 公式:当前设备的宽度/当前的换算比例=设计稿宽度(750px)/初始换算比例(100)
-
第三步:我们一般都会限制一个最大的缩放范围(比如:540),设备宽度即便超过这个范围,换算比例也不会再继续放大了,整个页面内容最宽540,左右两边预留空白即可。
- 为什么设计师给我们的设计稿,都比实际的手机尺寸大一倍?
- iphone6/7/8 -> 375px
- iphone6/7/8 plus / iphoneXR -> 414px
- 750px的设计稿是参照375px设备来设计的,之所以大一倍,是因为:DPR屏幕像素密度比Device Pixel Ratio
- 物理像素
- 分辨率
- 在DPR=2.0的设备上,我们准备的原始图片大小,要比最后设置的尺寸大一倍。
- 屏幕就是按照大一倍的方式渲染的。
- 在DPR=3.0的上,原始图片大小,要比设置的尺寸大两倍。
- …
- DPR对图片是最有影响的,对于文字等影响不大!!而且经过实测,DPR=3.0相比于DPR=2.0,变化也不是很大!
- 所以设计师给我们比真正尺寸大一倍的设计稿,其目的:让我们切出比设计尺寸大一倍的图片!如果设计稿中没有图片,按照375px的设计稿也是没有问题的。UI组件库一般都是这样的!
- 理论上,官方告诉我们图片的处理方案是这样的:
- 准备三张图
logo.png
logo@2x.png
logo@3x.png
。 - 我们需要根据当前设备的DPR,来决定使用那张图。
- 准备三张图
- 只不过前端这样处理太麻烦了,需要写js代码或@media动态控制加载的图片,而NativeApp开发有现成的处理方案。
- 都是按照三张图处理。
- 所以在WebApp开发中,我们只会准备一张二倍图,不论DPR是多少,加载的都是这个二倍图。
- 如果某些二倍图片在DPR为3的设备上,看越来模糊,就单独找设计师要一张三倍图。
- 目前实测,一般用二倍图就好了!
- 为什么设计师给我们的设计稿,都比实际的手机尺寸大一倍?
-
- 什么是rem?
移动端处理css
- 在移动端编写CSS3样式,为了兼容低版本的浏览器,我们需要写两套
- -webkit-transition:
- transition:
- 期望可以自动加前缀
-
webpack -> postcss
- 需要使用webpack。
-
prefixfree.min.js
- 可以通过引入这个js来就可以了。
<script src="./js/prefixfree.min.js" async></script>
-
…
-