当前位置: 首页 > Web前端 > HTML5

手机页面如何优雅适配各种屏幕,包括PC

时间:2023-04-05 19:11:18 HTML5

?,包括PC端,以及如何将触摸事件转化为鼠标事件。适配移动端开发移动端页面,我们通常会遵循固定宽度的设计稿,但手机实际屏幕尺寸是千变万化的,不适配会影响用户体验。Varlet组件库的设计是基于宽度为375px的设计稿,然后使用postcss-px-to-viewport进行移动端适配。这个PostCSS插件会将px单位转换成vw单位,1vw等于视口宽度的1/100。所以,以vw为单位,会随着viewport的宽度变化,达到适配不同机型的效果。将px转换为vw也非常简单。假设一个元素的宽高都是100px,设计稿的宽是375px,那么视口就相当于375px,那么1vw=375/100=3.75px,那么100px/3.75px=26.66vw,公式如下:vw=px/(viewportSize/100)下面我们从头开始创建一个Vite项目,看看如何使用postcss-px-to-viewport插件。创建项目:npminitvite@latest根据options创建一个vue项目,然后写一个很简单的按钮:接下来安装依赖,启动服务,效果如下:假设我们的设计稿是375px,那么我们切换尺寸变大看一些机型:直接在iPad上,可以看到按键尺寸没有变化,但是因为屏幕变大了,按键太小了,显然不够友好。接下来,我们将配置postcss-px-to-viewport插件。这个插件本身是一个PostCSS插件,所以首先要支持PostCSS。在Vite项目中使用PostCSS非常简单。只要项目包含一个有效的PostCSS配置,Vite就会自动将它应用到所有导入的CSS,所以我们要做的最重要的是添加一个PostCSS配置,参考postcss-px-to-viewport插件文档,首先安装:npminstallpostcss-px-to-viewport然后创建一个postcss.config.js文件,写入如下内容:module.exports={plugins:{"postcss-px-to-viewport":{//待转换单位unitToConvert:"px",//设计稿的视口宽度viewportWidth:375,//单位转换后保留的精度unitPrecision:4,},},};再次启动服务看效果:报错,虽然不知道为什么这个配置文件也被解析为ESModule,但是解决方法很简单,把后缀名改成.cjs,重启即可again:yes看到按钮变大了,单位也从我们写的px变成了vw。桌面适配这个适配不是指尺寸,因为已经用vw来解决尺寸适配问题,这里主要是指事件,具体来说,我们在移动端使用的交互事件一般都是触摸事件,但是桌面端肯定是不支持的,所以为了不让我们的移动端组件库在桌面端完全无法使用,我们需要将触摸事件转化为鼠标事件。Varlet是使用@varlet/touch-emulator包实现的,使用起来非常简单。安装:npmi@varlet/touch-emulator导入:import'@varlet/touch-emulator'接下来修改我们上面的例子,给按钮添加touchstart事件:然后分别在模拟器和非模拟器环境点击按钮:很明显,在非模拟器环境下点击是没有效果的,然后配置@varlet/touch-emulator,再次查看非模拟器环境下的点击效果:可以看到触发成功。让我们来看看@varlet/touch-emulator做了什么。//判断是否是浏览器环境constinBrowser=typeofwindow!=='undefined'//判断环境是否支持触摸事件constsupportTouch=inBrowser&&'ontouchstart'inwindow//...先来一点环境判断,如果不满足这两个条件,则不需要处理。//...if(inBrowser&&!supportTouch){createTouchEmulator()}//...如果满足条件,将调用createTouchEmulator方法://...functioncreateTouchEmulator(){window.addEventListener('mousedown',(event)=>onMouse(event,'touchstart'),true)window.addEventListener('mousemove',(event)=>onMouse(event,'touchmove'),true)window.addEventListener('mouseup',(event)=>onMouse(event,'touchend'),true)}//...监听了三个鼠标事件,分别对应三个触摸事件。注意addEventListener方法的第三个参数传入的是true,默认为false,表示在事件冒泡阶段调用事件处理函数。传true表示在事件捕获阶段调用事件处理函数。比如我们给页面上的一个div绑定一个mousedown事件,然后当我们的鼠标在这个Press这个div上的时候,如果是在冒泡阶段,那么会先调用div的事件函数,如果是在捕获阶段,那么会先调用window的事件函数,所以这里传true的原因是笔者猜测如果是在冒泡阶段就触发了,如果是的话可能是某个元素阻止冒泡,那么这些绑定到窗口的事件就不会被触发。在这些处理方法中调用了onMouse方法://...letinitiated=falseleteventTargetfunctiononMouse(mouseEvent,touchType){//事件类型,事件目标const{type,target}=mouseEvent//mousedown=true(mousedownevent)//false(mouseupevent)//hold(mousemoveevent)initiated=isMousedown(type)?真:isMouseup(类型)?false:initiated//如果是鼠标移动事件且鼠标没有按下则返回if(isMousemove(type)&&!initiated)return//判断是否更新事件目标if(isUpdateTarget(type))eventTarget=target//手动构造对应的触摸事件并触发triggerTouch(touchType,mouseEvent)//如果鼠标被释放则清除保存的事件目标if(isMouseup(type))eventTarget=null}constisMousedown=(eventType)=>eventType==='mousedown'constisMousemove=(eventType)=>eventType==='mousemove'constisMouseup=(eventType)=>eventType==='mouseup'//...这个方法首先根据鼠标事件的类型,记录鼠标按下的状态。如果是鼠标移动事件,鼠标没有按下,那么a方法会直接返回,因为触摸事件需要按下才能触发,然后调用isUpdateTarget方法判断是否更新事件目标:constisUpdateTarget=(eventType)=>isMousedown(事件类型)||!事件目标||(eventTarget&&!eventTarget.dispatchEvent)鼠标按下显然对应touchstart,触发的第一个触摸事件,事件目标也一定是新的,所以需要更新,理论上不同的手指事件目标可能不同,但是既然有桌面上只能是一次鼠标事件,直接用变量保存即可。如果eventTarget不存在,当然需要更新,但是我觉得这种情况不应该发生,因为touchstart或者mousedown事件肯定是最先被触发的,eventTarget应该已经有值了。第三种情况,作者不理解。按理说只要是DOM元素就应该有dispatchEvent方法。接下来,调用triggerTouch方法://...functiontriggerTouch(touchType,mouseEvent){const{altKey,ctrlKey,metaKey,shiftKey}=mouseEvent;//bubbles:事件是否冒泡//cancelable:事件是否可以取消consttouchEvent=newEvent(touchType,{bubbles:true,cancelable:true});//设置几个按键的按下标志touchEvent.altKey=altKey;touchEvent.ctrlKey=ctrlKey;touchEvent.metaKey=metaKey;touchEvent.shiftKey=shiftKey;//设置三种类型的触摸点对象数据touchEvent.touches=getActiveTouches(mouseEvent);touchEvent.targetTouches=getActiveTouches(鼠标事件);touchEvent.changedTouches=createTouchList(mouseEvent);//dispatcheventeventTarget.dispatchEvent(touchEvent);}//...首先手动创建一个对应类型的touchEvent对象,设置事件支持冒泡,然后设置相关按钮的按下状态。我只知道TouchEvent事件需要这些属性:然后设置触摸点数据,一共有三种:touches:当前屏幕所有触摸点的列表targetTouches:当前对象所有触摸点的列表changedTouches:当前(触发)事件涉及的触摸点列表,可能不止一个。比如我同时触摸几个手指,我可以通过这三个列表来区分它们。比如我给一个div绑定了三个触摸事件,我第一次用一根手指触摸div上,这时候三个列表的值是一样的,就是第一个手指的触摸点,然后我的第二根手指也开始触摸,但不是触摸div,而是其他元素,则touches列表将包含两个手指的触摸点,targetTouches列表将仅包含第一根手指的触摸点,而changedTouches列表将包含第二根手指的触摸点。手指全部松开后,三个列表就会全部清空。但是在桌面端,显然只有一个鼠标点,所以这三个列表其实是一样的。touches和targetTouches都调用getActiveTouches方法获取://...functiongetActiveTouches(mouseEvent){const{type}=mouseEvent;如果(isMouseup(type))返回createTouchList();返回updateTouchList(mouseEvent);}//。..释放事件touchList是空的,所以只返回一个空列表并调用createTouchList方法://...functioncreateTouchList(){consttouchList=[];touchList.item=function(index){返回这个[index]||无效的;};returntouchList;}//...原来的TouchList对象有一个item方法,返回指定值的Touch对象作为列表中的索引,所以需要提供一个数组来表示同名的TouchList方法.其他事件类型将调用updateTouchList方法://...functionupdateTouchList(mouseEvent){consttouchList=createTouchList();touchList.push(newTouch(eventTarget,1,mouseEvent));返回触摸列表;}//..同样先创建一个touchList,然后创建一个Touch实例添加进去。这个Touch类定义如下,模拟原生的Touch对象://...functionTouch(target,identifier,mouseEvent){const{clientX,clientY,screenX,screenY,pageX,pageY}=mouseEvent;this.identifier=标识符;this.target=目标;this.clientX=clientX;this.clientY=clientY;this.screenX=screenX;.pageX=pageX;this.pageY=pageY;}//...changedTouches直接调用了createTouchList方法。显然,无论什么时候返回一个空列表,这似乎有点问题,因为如前所述,仅对于一个触摸点,这三个列表的值应该相同。最后,事件的派发是在事件目标上进行的。总结一下,整个就是监听鼠标的三个事件,然后手动创建对应的触摸事件对象,最后派发到事件目标元素上。