我们知道,当把我们开发的pc端页面放在移动端展示,会出现布局错误。所以我们要做移动端适配,来让页面更合适。
接下来我们就来学习一下viewport标签并且学习如何做适配。
viewport
meta-viewport 标签是苹果公司在 2007 年引进的,用于移动端布局视口的控制。
使用示例:
<meta name="viewport" content="width=device-width,initial-scale=1.0">
viewport 相关选项
- width 布局视口的宽度
- initial-scale 【系统】初始缩放比例
- maximum-scale 允许【用户】缩放的最大比例
- minimum-scale 允许【用户】缩放的最小比例
- user-scalable 是否允许用户缩放
- viewport-fit 设置为cover值可以解决刘海屏的留白问题
1. width
width值可以是设备宽度标识 device-width,也可以是具体值,但有些安卓手机是不支持具体值,IOS全系列都支持。
2. initial-scale
- initial-scale 为页面初始化时的显示比例。
- initial-scale = 屏幕宽度(设备独立像素) / 布局视口宽度。
- 只写initial-scale = 1.0 也可以实现完美视口,但为了良好的兼容性,width=device-width, initial-scale=1.0一般一起写。
3. maximum-scale
- 设置允许用户最大缩放比例,苹果浏览器 safari 不认识该属性
- maximum-scale = 屏幕宽度(设备独立像素) / 视觉视口宽度值
4. minimum-scale
- 设置允许用户最小缩放比例。
- minimum-scale = 屏幕宽度(设备独立像素) / 视觉视口宽度值
5. user-scalable
是否允许用户通过手指缩放页面。苹果浏览器 safari 不认识该属性
6.viewport-fit
设置为 cover 可以解决『刘海屏』的留白问题
所以在我们做移动端开发的时候,做好这样写<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
。
了解了上面介绍的只是,那么我们来看看日和做适配,来使每个手机合理的展示内容。
适配
为什么要做适配?
由于移动端设备的屏幕尺寸大小不一,会出现:同一个元素,在两个不同的手机上显示效果不一样(比例不同)。
要想让同一个元素在不同设备上,显示效果一样,就需要适配,无论采用何种适配方式,中心原则永远是:等比。
主流的适配方式有三种:
- viewport 适配
- rem 适配(主流方式,几乎完美适配)
- vw适配
viewport 适配
- 方法:拿到设计稿之后,设置布局视口宽度为设计稿宽度,然后直接按照设计稿给宽高进行布局即可。
- 优点:不用复杂的计算,直接使用图稿上标注的px值
- 缺点:
- 不能使用完整的meta标签,会导致在某些安卓手机上有兼容性问题。
- 不希望适配的东西,例如边框,也强制参与了适配
- 图片会失真
rem适配
首先来介绍一下em
,rem
。
em 和 rem 都是 css 中的长度单位。而且两个都是相对长度单位,不过两个有点区别
- em 相对的是父级元素的字体大小
- rem 相对的是根元素的字体大小
rem适配的原理:编写样式时统一使用rem为单位,在不同设备上动态调整根字体大小。
方案一
淘宝、百度的移动端页面用的此方案
- 设置完美视口
- 通过js设置根字体大小 = ( 当前设备横向独立像素值 * 100) / 设计稿宽度,设计稿对应的机型根字体设置为100px了。
- 编写样式时,直接以rem为单位,值为:设计值 / 100
- 增加 JS 代码进行实时适配
function adapter() { //获取布局视口宽度,因为开启了理想视口,布局视口=设备横向独立像素值 const dpWidth = document.documentElement.clientWidth //计算根字体大小 const rootFonstSize = (dpWidth * 100)/375 //设置根字体大小 document.documentElement.style.fontSize = rootFonstSize + 'px' } adapter() // 注意:onresize事件检测的是布局视口的变化。 window.onresize = adapter
优势:编写样式时直接挪动小数点即可。
方案二
搜狐、唯品会的移动端页面用的此方案
- 设置完美视口
- 通过js设置根字体大小 = 当前设备横向独立像素值 / 10
- 编写样式时,直接以rem为单位,值为:设计值 / (设计稿宽度 / 10)
- 增加 JS 代码进行实时适配
function adapter() { //获取布局视口宽度,因为开启了理想视口,布局视口=设备横向独立像素值 const dpWidth = document.documentElement.clientWidth //计算根字体大小 const rootFonstSize = dpWidth / 10 //设置根字体大小 document.documentElement.style.fontSize = rootFonstSize + 'px' } adapter() // 注意:onresize事件检测的是布局视口的变化。 window.onresize = adapter
通过rem适配时,动态设置完根字体大小后,只需要进行设计稿的px和rem的转换即可,不需要考虑其他的。
vw适配(百分比)
京东的移动端页面是通过vw和rem结合的方案 vw和vh是两个相对单位
- 1vw = 等于布局视口宽度的1%
- 1vh = 等于布局视口高度的1%
这里不需要通过js代码,直接通过元素的像素和设计稿的布局视口比值,然后vw作为单位即可完成适配。只需要考虑设计稿即可。
下面来通过less来完成在iphone6的设计稿下的345 * 150px的元素渲染适配。
@basic:375/100vw; *{ margin: 0; padding: 0; } #demo{ width: 345/@basic; height: 150/@basic; background-color: skyblue; margin: 0 auto; margin-top: 15/@basic; border: 1px solid black; }
下面来介绍一下京东的适配方案。
不过vw和vh有一定的兼容性问题:详见:这里
物理像素边框
高清屏幕下 1px 对应更多的物理像素,所以 1 像素边框看起来比较粗,使用媒查询来设置不同dpr下的边框:
@media screen and (-webkit-min-device-pixel-ratio:2){ #demo{ border: 0.5px solid black; } }
移动端事件
事件类型
移动端事件列表
- touchstart 元素上触摸开始时触发
- touchmove 元素上触摸移动时触发
- touchend 手指从元素上离开时触发
- touchcancel 触摸被打断时触发
这几个事件最早出现于IOS safari中,为了向开发人员转达一些特殊的信息。
应用场景
- touchstart 事件可用于元素触摸的交互,比如页面跳转,标签页切换
- touchmove 事件可用于页面的滑动特效,网页游戏,画板
- touchend 事件主要跟 touchmove 事件结合使用
- touchcancel 使用率不高
注意:
- touchmove 事件触发后,即使手指离开了元素,touchmove 事件也会持续触发
- 触发 touchmove 与 touchend 事件,一定要先触发 touchstart
- 事件的作用在于实现移动端的界面交互
点击穿透
touch 事件结束后会默认触发元素的 click 事件,如没有设置完美视口,则事件触发的时间间隔为 300ms 左右,如设置完美视口则时间间隔为 30ms 左右(备注:具体的时间也看设备的特性)。
如果 touch 事件隐藏了元素,则 click 动作将作用到新的元素上,触发新元素的 click 事件或页面跳转,此现象称为点击穿透
解决方法一
阻止默认行为
//阻止默认行为 node.addEventListener('touchstart', function(e){ e.preventDefault(); })
解决方法二
使背后元素不具备click特性,用touchXxxx代替click
banner_img.addEventListener('touchstart',()=>{ location.href = 'http://www.baidu.com' })
解决方案三
让背后的元素暂时失去click事件,300毫秒左右再复原。因为touch系列事件和click事件极限间隔大约300ms。
#anode{ pointer-events: none; }
btn.addEventListener('touchstart',(event)=>{ shade.style.display = 'none' setTimeout(()=>{ anode.style.pointerEvents = 'auto' },300) })
解决方案四
让隐藏的元素延迟300毫秒左右再隐藏
btn.addEventListener('touchstart',(event)=>{ setTimeout(()=>{ shade.style.display = 'none' },300) })