移动端页面如何优雅的适配各种屏幕,包括PC端

article/2025/9/18 18:55:22

本文为Varlet组件库源码主题阅读系列第八篇,读完本篇,可以了解到移动端页面如何适配各种尺寸的屏幕,包括pc端,另外如何将触摸事件转换成鼠标事件。

移动端适配

开发移动端页面,我们通常都会按照一个固定宽度的设计稿来做,但是实际上的手机屏幕尺寸五花八门,如果不进行适配的话会比较影响使用体验。

Varlet组件库的设计就是基于375px宽度的设计稿,然后使用postcss-px-to-viewport进行移动端适配,这个PostCSS插件会将px单位转换成vw单位,1vw等于1/100的视口宽度,所以使用vw作为单位就会随着视口的宽度进行变化达到适配不同机型的效果。

pxvw也很简单,假设某个元素的宽高为100px,设计稿宽度为375px,那么视口也就相当于是375px,那么1vw = 375 / 100 = 3.75px,那么100px / 3.75px = 26.66vw ,公式如下:

vw = px / (viewportSize / 100)

接下来我们从零创建一个Vite项目来看一下postcss-px-to-viewport插件的使用。

创建项目:

npm init vite@latest

根据选项创建一个Vue的项目,然后写一个非常简单的按钮:

接下来安装依赖和启动服务,效果如下:

假设我们的设计稿就是375px,那么我们切换到尺寸更大一点的机型看看:

直接上iPad,可以看到按钮尺寸没有变,但是因为屏幕变大了而显得按钮太小了,这显然是不够友好的,接下来我们就配置一下postcss-px-to-viewport插件。

这个插件本身是一个PostCSS的插件,所以首先要支持PostCss,在Vite项目中使用PostCSS很简单,只要项目中包含有效的PostCSS 配置,Vite就会自动使其应用于所有导入的CSS,所以我们要做的就是增加一个PostCSS 配置,参考postcss-px-to-viewport插件文档,先安装:

npm install postcss-px-to-viewport

然后创建postcss.config.js文件,写入如下内容:

module.exports = {plugins: {"postcss-px-to-viewport": {// 需要转换的单位unitToConvert: "px",// 设计稿的视口宽度viewportWidth: 375,// 单位转换后保留的精度unitPrecision: 4,},},
};

再次启动服务看看效果:

报错了,虽然不知道为什么会把这个配置文件也当成ES Module解析,但是解决方法很简单,把后缀名改成.cjs即可,再次重启:

可以看到按钮变大了,单位也由我们书写的px变成了vw

桌面端适配

这个适配指的不是尺寸,因为前面已经使用vw解决了尺寸的适配问题,这里主要是指事件,具体来说是我们在移动端使用的交互事件一般是touch事件,但是桌面端肯定不支持,所以为了让我们的移动端组件库不至于在桌面端完全无法使用,需要将touch事件转成mouse事件。

Varlet使用的是@varlet/touch-emulator这个包来实现的,使用也很简单,安装:

npm i @varlet/touch-emulator

导入:

import '@varlet/touch-emulator'

接下来修改一下我们上面的示例,给按钮增加一个touchstart事件:

然后分别在模拟器和非模拟器环境下单击一下按钮:

显然,非模拟器环境下单击是没有效果的,接下来配置一下 @varlet/touch-emulator,再次查看非模拟器环境下的点击效果:

可以看到成功触发了。

接下来就来窥探一下 @varlet/touch-emulator都做了些什么。

// 判断是否是浏览器环境
const inBrowser = typeof window !== 'undefined'
// 判断该环境是否支持touch事件
const supportTouch = inBrowser && 'ontouchstart' in window
// ...

首先进行了一下环境判断,如果不满足这两个条件就不需要做任何处理。

// ...
if (inBrowser && !supportTouch) {createTouchEmulator()
}
// ...

满足条件则调用createTouchEmulator方法:

// ...
function createTouchEmulator() {window.addEventListener('mousedown', (event) => onMouse(event, 'touchstart'), true)window.addEventListener('mousemove', (event) => onMouse(event, 'touchmove'), true)window.addEventListener('mouseup', (event) => onMouse(event, 'touchend'), true)
}
// ...

监听了三个鼠标事件,分别对应三个touch事件,注意addEventListener方法第三个参数都传了true,这个参数默认是false,表示在事件冒泡的阶段调用事件处理函数,传true就表示在事件捕获的阶段调用事件处理函数,举个栗子,比如我们给页面上的一个div也绑定了mousedown事件,然后当我们鼠标在这个div上按下,如果是冒泡阶段,那么div的事件函数会先被调用,如果是捕获阶段,那么window的事件函数会先被调用,所以这里传true笔者猜测是因为如果是冒泡阶段触发的话,某个元素的可能会阻止冒泡,那么就不会触发window上绑定的这几个事件了。

这几个处理方法内都调用了onMouse方法:

// ...
let initiated = false
let eventTarget
function onMouse(mouseEvent, touchType) {// 事件类型、事件目标const { type, target } = mouseEvent// mousedown = true(mousedown事件)//             false(mouseup事件)//             保持(mousemove事件)initiated = isMousedown(type) ? true : isMouseup(type) ? false : initiated// 如果是鼠标移动事件且鼠标没有按下则返回if (isMousemove(type) && !initiated) return// 判断是否要更新事件目标if (isUpdateTarget(type)) eventTarget = target// 手动构造对应的touch事件并触发triggerTouch(touchType, mouseEvent)// 如果鼠标松开了则清除保存的事件目标if (isMouseup(type)) eventTarget = null
}const isMousedown = (eventType) => eventType === 'mousedown'
const isMousemove = (eventType) => eventType === 'mousemove'
const isMouseup = (eventType) => eventType === 'mouseup'
// ...

这个方法首先根据鼠标事件的类型设置了initiated变量,记录鼠标的按下状态,如果是鼠标移动事件且鼠标没有按下,那么个方法会直接返回,因为touch事件都需要先按下才会触发,然后调用了isUpdateTarget方法判断是否要更新事件目标:

const isUpdateTarget = (eventType) =>isMousedown(eventType) || !eventTarget || (eventTarget && !eventTarget.dispatchEvent)

鼠标按下显然对应的是touchstart,触发的第一个touch事件,事件目标肯定也是新的,所以需要更新,理论上不同手指的事件目标是可能不一样的,但是由于桌面端鼠标事件只能有一个,所以直接用一个变量保存即可。

eventTarget不存在当然也需要更新,但是笔者觉得这种情况应该不会出现,因为touchstart或者说是mousedown事件肯定是最先被触发的,eventTarget应该已经有值了。

第三个条件笔者也没有理解,按理说只要是DOM元素应该都会有dispatchEvent方法。

接下来调用了triggerTouch方法:

// ...
function triggerTouch(touchType, mouseEvent) {const { altKey, ctrlKey, metaKey, shiftKey } = mouseEvent;// bubbles:该事件是否冒泡// cancelable:该事件能否被取消const touchEvent = new Event(touchType, { bubbles: true, cancelable: true });// 设置几个键的按下标志touchEvent.altKey = altKey;touchEvent.ctrlKey = ctrlKey;touchEvent.metaKey = metaKey;touchEvent.shiftKey = shiftKey;// 设置三种类型的触摸点对象数据touchEvent.touches = getActiveTouches(mouseEvent);touchEvent.targetTouches = getActiveTouches(mouseEvent);touchEvent.changedTouches = createTouchList(mouseEvent);// 派发事件eventTarget.dispatchEvent(touchEvent);
}
// ...

先手动创建一个对应类型的touchEvent对象,设置该事件支持冒泡,然后设置了相关按键的按下状态,笔者也是才知道TouchEvent事件是需要这几个属性的:

然后设置触摸点数据,一共有三种类型:

  • touches:当前屏幕上所有触摸点的列表
  • targetTouches:当前对象上所有触摸点的列表
  • changedTouches:涉及当前(引发)事件的触摸点的列表

移动端触摸点是可能存在多个的,比如我同时好几个手指一起触摸,可以通过这三个列表进行区分,同样举个栗子,比如我给一个div绑定了三个touch事件,第一次我一个手指触摸到div上,此时这三个列表的值是一样的,就是第一个手指的触摸点,然后我第二个手指也开始触摸,但是不是触摸到div上,而是其他元素上,那么此时touches列表会包含两个手指的触摸点,targetTouches列表只会包含第一个手指的触摸点,changedTouches列表则为第二个手指的触摸点。手指全部松开后,这三个列表都将为空。

但是在桌面端,鼠标触摸点显然只有一个,所以这三个列表其实都是相同的。

touchestargetTouches都调用了getActiveTouches方法获取:

// ...
function getActiveTouches(mouseEvent) {const { type } = mouseEvent;if (isMouseup(type)) return createTouchList();return updateTouchList(mouseEvent);
}
// ...

松开事件touchList是空的,所以返回一个空列表即可,调用的是createTouchList方法:

// ...
function createTouchList() {const touchList = [];touchList.item = function (index) {return this[index] || null;};return touchList;
}
// ...

原生的TouchList对象存在一个item方法,返回列表中以指定值作为索引的 Touch 对象,所以使用数组来代表TouchList需要自行提供一个同名方法。

其他事件类型则会调用updateTouchList方法:

// ...
function updateTouchList(mouseEvent) {const touchList = createTouchList();touchList.push(new Touch(eventTarget, 1, mouseEvent));return touchList;
}
// ...

同样先创建了一个touchList,然后创建了一个Touch实例添加进去,这个Touch类定义如下,模拟的是原生的Touch对象:

// ...
function Touch(target, identifier, mouseEvent) {const { clientX, clientY, screenX, screenY, pageX, pageY } = mouseEvent;this.identifier = identifier;this.target = target;this.clientX = clientX;this.clientY = clientY;this.screenX = screenX;this.screenY = screenY;this.pageX = pageX;this.pageY = pageY;
}
// ...

changedTouches直接调用的是createTouchList方法,显然无论何时返回的都是空的列表,这个似乎是有点问题的,因为前面说了,只有一个触摸点的话这三个列表的值应该都是一样的。

最后在事件目标上进行了事件的派发。

总结一下,整体所做的事情就是监听鼠标的三个事件,然后手动创建对应的touch事件对象,最后在事件目标元素上进行派发即可。


http://chatgpt.dhexx.cn/article/aNlexnOH.shtml

相关文章

FTP客户端和服务器的设计与实现

1 毕业设计目的和意义 2 1.1 毕业设计目的 3 1.1.1 目的一:面向系统的软件开发 3 1.1.2 目的二:面向网络应用的软件开发 3 2.毕业设计意义 3 2 毕业设计设计 4 2.1 概述 4 2.2 毕业设计原理 4 2.2.1 使用FTP协议下载文件的流程 4 2.2.2 相关类库说明 6 2…

移动端适配方案总结

目录 一、背景介绍1.1 为什么要进行移动端适配1.2 移动端适配方案 二、rem方案2.1 什么是rem2.2 怎么根据屏幕尺寸设置根元素html的font-size2.3 postcss-pxtorem 三、viewport方案3.1 什么是viewport方案3.2 postcss-px-to-viewport 四、总结(如果只想看实现步骤可…

移动端适配的几种方式

百分比适配方式 这种方法&#xff0c;只是宽度能适配&#xff0c;高度不能适配&#xff0c;只能设置某个高度固定死 需求&#xff1a;是四个div高度为100px&#xff0c;宽度等宽横向排列 <!DOCTYPE html> <html lang"en"> <head><meta chars…

.移动端适配的解决方案

何为移动端适配 移动端适配就是值在不同的移动端 可以去讲我们的内容适应不同屏幕尺寸大小 我们之前写单位用的是px这个单位 但是这是一个写死的单位 rem 所以我们用一个可变的单位 rem &#xff08;是指用html字体大小作为单位 比如说我们设置html字体大小为16px 那么 …

移动web适配

当屏幕宽度发生变化时&#xff0c;页面元素的尺寸&#xff08;宽度和高度&#xff09;也会随之变化&#xff0c;为更好的达到适配效果&#xff0c;用户体验更好&#xff0c;百分比布局 和 Flex布局 是有缺陷的&#xff0c;不能完成最终的适配。想要解决检测屏幕大小的问题&…

FTP-Web端如何直接访问FTP资源

ftp客户端工具&#xff1a;iis7服务器管理工具 IIs7服务器管理工具可以批量管理ftp站点&#xff0c;同时具备定时上传下载的功能。 作为服务器集成管理器&#xff0c;它最优秀的功能就是批量管理windows与linux系统服务器、vps。能极大的提高站长及服务器运维人员工作效率。同…

移动端适配的理解和各种方案解析(详解)

-&#x1f482; 个人网站:【紫陌】【笔记分享网】&#x1f485; 想寻找共同学习交流、共同成长的伙伴&#xff0c;请点击【前端学习交流群】 前言&#xff1a;最近在弄移动端项目&#xff0c;记录一下移动端的应用方案。对各个方案的解决理解。 目录 1.什么是移动端适配 2.理解…

手机上安装FTP客户端软件(AndFTP),实现通过手机访问计算机FTP服务器

服务器连接工具&#xff1a; IIS7服务器管理工具是一款windows全系下用于连接并操控基于windows和linux系统的VPS、VNC、FTP等远程服务器、云服务器的管理工具。 界面简单明了&#xff0c;操作易上手&#xff0c;功能强大&#xff0c;支持批量导入服务器&#xff0c;并批量打开…

pc端与移动端适配 解决方案

一般网站实现pc端与移动端适配的需求&#xff0c;方案有两个&#xff1a; 1、一套页面&#xff0c;从设计时就考虑到跨设备适配&#xff0c;响应式的一步到位&#xff1b; 2、开发两套页面&#xff0c;根据设备尺寸加载加载不同的资源&#xff0c;目前已经不常见了&#xff1…

【14.HTML-移动端适配】

移动端适配 1 布局视口和视觉视口1.1 设置移动端布局视口宽度 2 移动端适配方案2.1 rem单位动态html的font-size&#xff1b;2.2 vw单位2.3 rem和vw对比2.4 flex的弹性布局 1 布局视口和视觉视口 1.1 设置移动端布局视口宽度 避免布局视口宽度默认980px带了的缩放问题,并且禁止…

移动端常见适配方案

基础 网上已经有非常多的基础知识总结&#xff0c;不再赘诉&#xff0c;详情可以见 《关于移动端适配&#xff0c;你必须要知道的》 《不要再问我移动适配的问题了》 其中容易搞混的概念是视口 <meta name"viewport" content"widthdevice-width,user-sc…

FTP服务应用(手机端与电脑端无线传输)

FTP服务应用&#xff08;手机端与电脑端无线传输&#xff09; 准备工具&#xff1a;Android手机.KSWEB软件。 1.利用Android手机.打开移动网络共享。 2.电脑连上WiFi热点。 3.Android手机安装KSWEB软件&#xff0c;并打开FTP服务器。 4.在KSWEB软件左划到FTP模块&#xff0c;将…

移动端适配方案有哪几种?

虽然我们课程明确的区分各种移动端适配方案&#xff0c;但依然有很多同学搞不清楚移动端等比适配和响应式&#xff0c;这里对移动端主流适配方案给大家做一个分析。 移动端适配是指同一个页面可以在不同的移动端设备上都有合理的布局。主流的实现方案有两种&#xff1a; 响应…

solr简介和使用

一、搜索功能的流行方案 由于搜索引擎功能在门户社区中对提高用户体验有着重在门户社区中涉及大量需要搜索引擎的功能需求,目前在实现搜索引擎的方案上有集中方案可供选择: 1、基于Lucene自己进行封装实现站内搜索。工作量及扩展性都较大,不采用。 2、调用Google、Baidu的…

solr 安装和使用

Solr是基于ApacheLucene构建的流行、快速、开源的企业搜索平台 Solr具有高度可靠性、可扩展性和容错性&#xff0c;提供分布式索引、复制和负载平衡查询、自动故障切换和恢复、集中配置等功能。Solr为许多世界上最大的互联网站点提供搜索和导航功能 环境准备 linux centos7 ja…

什么是Solr,它能为我们解决什么问题,怎么用?

一. 什么是Solr? 其实我们大多数人都使用过Solr,也许你不会相信我说的这句话,但是事实却是如此啊 ! 每当你想买自己喜欢的东东时,你可能会打开某宝或者某东,像这样一搜,就能搜到很多东西,你知道你看到的这些数据都来自哪儿吗?百度一下你就知道!这些数据来自哪儿吗?等你了解…

solr实现原理

solr那是我1年前使用到的一个搜索引擎&#xff0c;由于当初对于配置了相应了&#xff0c;但是今天突然面试问到了&#xff0c;哎&#xff0c;太久了&#xff0c;真的忘记了&#xff0c;今天特地写一篇博客记下来 solr是一个独立的企业级搜索应用服务器&#xff0c;它对外t提供…

solr的使用详解

一、Solr简介 由于搜索引擎功能在门户社区中对提高用户体验有着重在门户社区中涉及大量需要搜索引擎的功能需求&#xff0c;目前在实现搜索引擎的方案上有几种方案可供选择&#xff1a; 基于Lucene自己进行封装实现站内搜索。工作量及扩展性都较大&#xff0c;不采用。 调用Go…

solr基础理解和功能分析

一、solr概述 Solr是一个开源搜索平台&#xff0c;用于构建搜索应用程序。 它建立在Lucene(全文搜索引擎)之上。Solr是一个可扩展的&#xff0c;可部署&#xff0c;搜索/存储引擎&#xff0c;优化搜索大量以文本为中心的数据。 二、solr管理界面功能 1.Logging 展示Solr的日…

Solr基本概念

Solr是一种开放源码的、基于Lucene的搜索服务器。它易于安装和配置&#xff0c;而且附带了一个基于HTTP 的管理界面。 官网&#xff1a; http://lucene.apache.org/solr/ Solr全文检索基本原理&#xff1a; http://www.importnew.com/12707.html 相关概念&#xff1a; Core: …