使页面在移动端各种大小的屏幕上都能够正常显示的一种移动端开放方案
一、概述
1.1 什么是移动端
-
主要是一些手持设备,最具有代表性就是手机和平板,比如智能手表,掌上游戏机等移动设备;
-
在前端开发中,移动端指的就是手机和ipad
-
在移动端上面运行项目,我们就称为移动端的项目
-
移动端主要关注点:各种类型移动设备(PC端关注点:各种浏览器)
移动端页面设计一般分为:
-
响应式布局
-
通过媒体查询技术实现不同屏幕都有良好的用户体验;一稿多端使用
-
-
移动端布局
-
pc端和移动端的是相互的独立,pc端一套页面,移动端一套页面
-
1.2 像素及分辨率简介
像素:即一个小方块,具有特定的位置和样式
分辨率(DPI):屏幕上显示的像素个数(每英寸包括的像素的数量,每英寸的点数 1英寸=2.54cm );分辨率越高,每英寸包含点数越大,画面越清晰;例如:分辨率160×128的意思是水平像素数为160个,垂直像素数128个;PPI(pixel per inch):每英寸的物理像素点
物理分辨率:是屏幕一出厂已经确定的像素,屏幕上有多少个发光点
逻辑分辨率:真正用于显示屏幕内容的像素(移动端开发只需要参考它即可);逻辑分辨率里面的一个像素,可以对应物理分辨率里面多个像素(发光点)
物理像素(physical pixel)/设备像素(dp:device pixel):实际开发中并不以物理像素为准
CSS像素/逻辑像素(logical pixel)/设备独立像素(dip:device independent pixel):实际开发中使用的像素;例如:width:100px;
设备像素比(dpr:device pixel ratio):dpr=设备像素/CSS像素(缩放比为1的前提下)
标清屏和高清屏:dpr=1--标清手机屏 dpr>1高清屏手机
缩放:改变的是CSS像素的大小(物理像素的个数)
1.3 视口(viewport)
视口就是手机屏幕用于显示的页面的区域 ,针对移动端
布局视口(默认):
按照百分比来设计网页,在不同的屏幕下,显示的大小由屏幕决定的,但是在移动端,本身屏幕就小,如果显示pc端页面,可能会造成页面的排版混乱,移动端浏览器为了正常显示pc端的页面,将移动端浏览器设置一个值980px。视口的大小一旦确定,100%值就确定了。
这就是布局视口(开发中需要更改)
常见的布局视口:
视觉视口:
- 用户通过屏幕真实看到的区域,即移动端屏幕的可视区域
- 视觉视口默认等于当前浏览器窗口的大小,对于移动端就是手机显示屏幕的大小
-
当用户对浏览器进行缩放时,不会改变布局视口的大小,只是将页面在视觉视口呈现效果改变
-
视觉视口一般就是浏览器的窗口的大小,自己定义的一个概念
.box{
width:100%
}
默认不会参考视觉视口的宽度,默认按照布局视口
理想视口:
-
页面在移动端设备完美显示的视口,按照手机的逻辑分辨率(视觉视口)进行页面布局
-
理想视口的大小=视觉视口,移动端网页就可以完美显示
-
需要手动设置:通过meta标签去开启理想视口
一般移动端布局会使用css3提供的单位来使用
获取视口宽度的写法:
<script>
// 获取视口宽度:
var viewWidth = document.documentElement.clientWidth || window.innerWidth || document.documentElement.getBoundingClientRect().width;
console.log(viewWidth);
</script>
1.4 单位
像素:固定单位,绝对长度单位
相对单位:
1. VW和VH(重点)
- vw(Viewport Width)和vh(Viewport Height)是前端开发中的一个动态单位,是一个相对于网页视口的单位
- 系统会将视口的高度和宽度分为100份,1vw就占用视口宽度的百分之一,1vh就占用视口高度的百分之一
- vw、vh与百分比不同的是,百分比永远都是以父元素作为参考,而vw和vh永远都是以视口作为参考
div {
width: 1vw;
height: 1vh;
background: #f40;
}
<div></div>
console.log(window.innerWidth, window.innerHeight);
const oDiv = document.querySelector("div");
console.log(getComputedStyle(oDiv).width);
console.log(getComputedStyle(oDiv).height);
2. em
-
参考当前盒子的font-size大小,1em=1个字体大小(16PX)
-
注意:如果当前盒子没有设置font-size,可以从父盒子去继承,因为font-size可以被自动继承;直到浏览器默认的字体大小为止。IE都支持
-
子元素的width和height参考自己的font-size大小,若自身没有设置font-size属性那么就会逐级往上找其父元素、祖先元素
3. rem(重点)
rem就是root em,和em一样是前端开发中的一个动态单位
只参考html标签的文字大小,如果html标签没有设置,使用浏览器默认的字体大小(默认是16px,即 1rem=16px)
rem和em的区别在于,rem是一个相对于根元素(html元素)字体大小的单位,除了根元素以外,其他祖先元素的字体大小不会影响rem的尺寸
* {
margin: 0;
padding: 0;
}
.father {
width: 200px;
height: 100px;
background: #f40;
}
<div class="father">
<div class="son"></div>
</div>
.son {
font-size: 12px;
width: 10rem;
height: 10rem;
background: skyblue;
}
4.百分比
- 子元素的宽度和高度参考的是父元素的宽度和高度
- 子元素的padding属性参考的是父元素的width
- 子元素的margin参考父元素的width
- 子元素的border不能以百分比进行设置
- 百分比是一个动态单位,永远都是以当前元素的父元素最为参考进行
* {
margin: 0;
padding: 0;
}
.father {
width: 200px;
height: 100px;
background: #f40;
}
<div class="father">
<div class="son"></div>
</div>
.son {
width: 50%;
height: 50%;
background: skyblue;
}
.son {
padding-top: 50%;
padding-bottom: 50%;
padding-left: 50%;
padding-right: 50%;
background: skyblue;
}
.son {
margin-top: 50%;
margin-bottom: 50%;
margin-left: 50%;
margin-right: 50%;
background: skyblue;
}
5. vmin和vmax
- vmin:vm和vh中较小的那个
- vmax:vm和vh中较大的那个
- 使用场景:保证移动开发过程中屏幕旋转之后尺寸不变
6. 单位抉择
-
em,rem是font-size的大小,一般用于文字适配,其中em适合局部适配,rem适合用于整个页面的文字适配
-
vw和vh是参考视口的宽高绝对的,一般用于设置大盒子的尺寸
-
vw和vh都是通过视口决定,通过min-width和min-height来限制大小。
-
移动端布局采用的方式rem进行布局。
1.5 移动端适配
a. 认识移动端布局 :
-
以手机自身的逻辑分辨率为页面的宽度,进行页面的布局,而我们把这种针对移动端的布局方式就称为移动端布局。
-
移动端页面实际参考的尺寸就是手机的逻辑分辨率(视口),比如,iPhone6、7、8 375
-
每个手机的物理分辨率是不同,导致了页面存在宽度不同,即页面宽度存在兼容,就需要进行页面适配
b. rem+媒体查询:
根据不同的屏幕,换算出每种字体的比例
-
盒子的尺寸是以rem为单位
-
需要利用媒体查询来设置当屏幕的大小不同时,html标签采用不同font-size
-
像具体的内容处理跟以前一样
-
以rem代替px,百分比可以保留
c. 开发步骤:
对拿到的psd原稿尺寸分析:
-
640px ——》320px
-
750px ——>375px 2倍图
-
1125px ——> 375px 3倍图
-
对原稿进行切片
-
通过PXcook软件对原稿进行页面的标注测量,确保尺寸的精确性。
-
引入JS脚本适配各种设备
-
统一脚本的逻辑分辨率和font-size尺寸
-
在开发过程中找个移动端的设备进行开发,开发使用px为单位,可以使用px to rem插件自动转换为rem单位
注意:所有的尺寸不允许像素的存在
二、适配原理
2.1 移动端rem布局原理解析
% em rem vw/vh
1rem = html的font-size
假设现在是在一个750px下: 1rem=750px
height: 100px / 750px rem
height: 0.13333333333333333333333333333333rem;
假设现在是在一个375px下: 1rem=375px
height: 50 / 375 = 0.13333333333333333333333333333333rem;
这里的 18.75 不一定是18.75,只要保证18.75是750的几分之几就可以
750px 1rem=750 / 18.75 = 40px,这里的40px应该是750px对应下的html里font-size的大小
height: 100 / 40 = 2.5rem;
2.2 利用JS 实现简单的动态适配
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<title>简单适配实现</title>
<style>
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
.header-container {
height: 2.5rem;
background-color: rgba(222, 24, 27, 0.9);
}
</style>
<script src="js/simple.js"></script>
</head>
<body>
<header class="header-container"></header>
</body>
</html>
simple.js:
(function() {
'use strict';
setRemUnit();
//监听页面,这里会自动调用setRemUnit()函数
window.addEventListener('resize', setRemUnit);
function setRemUnit() {
var docEl = document.documentElement;
var ratio = 18.75;
//获取视口的宽
//getBoundingClientRect()返回元素的大小及其相对于视口的位置
var viewWidth = docEl.getBoundingClientRect().width || window.innerWidth;
docEl.style.fontSize = viewWidth / ratio + 'px';
}
})();
2.3 利用JS实现通用的适配方案
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
.header-container {
width: 100%;
height: 2.5rem;
background-color: rgba(222, 24, 27, 0.9);
border-bottom: 1px solid #000;
}
flexible.js:
(function() {
'use strict';
// dpr(设备像素比)->scale = 1 / dpr
var docEl = document.documentElement,
viewportEl = document.querySelector('meta[name="viewport"]'),
dpr = window.devicePixelRatio || 1,
maxWidth = 540,
minWidth = 320;
dpr = dpr >= 3 ? 3 : (dpr >= 2 ? 2 : 1);
//给html元素设置属性
docEl.setAttribute('data-dpr', dpr);
docEl.setAttribute('max-width', maxWidth);
docEl.setAttribute('min-width', minWidth);
// 缩放比 写content
var scale = 1 / dpr,
content = 'width=device-width, initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no';
// 判断viewportEl是否存在
if (viewportEl) {
// 设置(更改)相关属性
viewportEl.setAttribute('content', content);
} else { //如果不存在,则创建meta标签并添加相关属性和属性值
viewportEl = document.createElement('meta');
viewportEl.setAttribute('name', 'viewport');
viewportEl.setAttribute('content', content);
document.head.appendChild(viewportEl);
}
setRemUnit();
window.addEventListener('resize', setRemUnit);
function setRemUnit() {
var ratio = 18.75;
var viewWidth = docEl.getBoundingClientRect().width || window.innerWidth;
// console.log(viewWidth);
if (maxWidth && (viewWidth / dpr > maxWidth)) {
viewWidth = maxWidth * dpr;
} else if (minWidth && (viewWidth / dpr < minWidth)) {
viewWidth = minWidth * dpr;
}
docEl.style.fontSize = viewWidth / ratio + 'px';
}
})();
<header class="header-container"></header>
2.4 综合小案例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>通用适配应用</title>
<link rel="stylesheet" href="css/base.css">
<link rel="stylesheet" href="css/icons.css">
<link rel="stylesheet" href="css/slider.css">
<link rel="stylesheet" href="css/index.css">
<script src="js/flexible.js"></script>
</head>
<body>
<header class="header-container">
<div class="navbar">
<div class="navbar-left">
<i class="iconfont icon-scan"></i>
</div>
<div class="navbar-center">
<div class="searchBox">
<div class="searchBox-prepend">
<i class="iconfont icon-search"></i>
</div>
<input type="text" class="searchBox-input" placeholder="开学季有礼,好货5折起">
<div class="searchBox-append">
<i class="iconfont icon-close"></i>
</div>
</div>
</div>
<div class="navbar-right">
<i class="iconfont icon-msg"></i>
</div>
</div>
</header>
<div class="main-container">
<!--幻灯片-->
<div class="slider">
<div class="slider-item-container">
<div class="slider-item">
<a href="###" class="slider-link">
<img src="img/slider/1.jpg" alt="slider" class="slider-img">
</a>
</div>
<div class="slider-item">
<a href="###" class="slider-link">
<img src="img/slider/2.jpg" alt="slider" class="slider-img">
</a>
</div>
<div class="slider-item">
<a href="###" class="slider-link">
<img src="img/slider/3.jpg" alt="slider" class="slider-img">
</a>
</div>
<div class="slider-item">
<a href="###" class="slider-link">
<img src="img/slider/4.jpg" alt="slider" class="slider-img">
</a>
</div>
<div class="slider-item">
<a href="###" class="slider-link">
<img src="img/slider/5.jpg" alt="slider" class="slider-img">
</a>
</div>
</div>
<!--小圆点-->
<!-- <div class="slider-indicator-container">
<span class="slider-indicator slider-indicator-active"></span>
<span class="slider-indicator"></span>
<span class="slider-indicator"></span>
<span class="slider-indicator"></span>
<span class="slider-indicator"></span>
</div> -->
</div>
<!--主导航-->
<nav class="nav-container">
<ul class="nav">
<li class="nav-item">
<a href="###" class="nav-link">
<img src="img/nav/1.png" alt="nav" class="nav-img">
<span class="nav-text">团购</span>
</a>
</li>
<li class="nav-item">
<a href="###" class="nav-link">
<img src="img/nav/2.png" alt="nav" class="nav-img">
<span class="nav-text">一元购</span>
</a>
</li>
<li class="nav-item">
<a href="###" class="nav-link">
<img src="img/nav/3.png" alt="nav" class="nav-img">
<span class="nav-text">优惠券</span>
</a>
</li>
<li class="nav-item">
<a href="###" class="nav-link">
<img src="img/nav/4.png" alt="nav" class="nav-img">
<span class="nav-text">教育</span>
</a>
</li>
<li class="nav-item">
<a href="###" class="nav-link">
<img src="img/nav/5.png" alt="nav" class="nav-img">
<span class="nav-text">旅行</span>
</a>
</li>
<li class="nav-item">
<a href="###" class="nav-link">
<img src="img/nav/6.png" alt="nav" class="nav-img">
<span class="nav-text">在线订餐</span>
</a>
</li>
<li class="nav-item">
<a href="###" class="nav-link">
<img src="img/nav/7.png" alt="nav" class="nav-img">
<span class="nav-text">庆典</span>
</a>
</li>
<li class="nav-item">
<a href="###" class="nav-link">
<img src="img/nav/8.png" alt="nav" class="nav-img">
<span class="nav-text">秒杀</span>
</a>
</li>
<li class="nav-item">
<a href="###" class="nav-link">
<img src="img/nav/9.png" alt="nav" class="nav-img">
<span class="nav-text">拍卖</span>
</a>
</li>
<li class="nav-item">
<a href="###" class="nav-link">
<img src="img/nav/10.png" alt="nav" class="nav-img">
<span class="nav-text">服务</span>
</a>
</li>
</ul>
</nav>
<!--商品-->
<div class="recommend-container">
<ul class="recommend">
<li class="recommend-item">
<a href="###" class="recommend-link">
<p class="recommend-pic">
<img src="img/recommend/1.jpg" alt="recommend" class="recommend-img">
</p>
<p class="recommend-name">欧派整体橱柜定制简约现代</p>
<p class="recommend-origPrice">
<del>¥2000.00</del>
</p>
<p class="recommend-info">
<span class="recommend-price">¥<strong class="recommend-price-num">1000</strong></span>
<span class="recommend-count">985件已售</span>
</p>
</a>
</li>
<li class="recommend-item">
<a href="###" class="recommend-link">
<p class="recommend-pic">
<img src="img/recommend/2.jpg" alt="recommend" class="recommend-img">
</p>
<p class="recommend-name">创维55吋4K超高清HDR</p>
<p class="recommend-origPrice">
<del>¥2999.00</del>
</p>
<p class="recommend-info">
<span class="recommend-price">¥<strong class="recommend-price-num">2299</strong></span>
<span class="recommend-count">63件已售</span>
</p>
</a>
</li>
<li class="recommend-item">
<a href="###" class="recommend-link">
<p class="recommend-pic">
<img src="img/recommend/3.jpg" alt="recommend" class="recommend-img">
</p>
<p class="recommend-name">【到手259元】苏泊尔 5L电压力锅</p>
<p class="recommend-origPrice">
<del>¥799.00</del>
</p>
<p class="recommend-info">
<span class="recommend-price">¥<strong class="recommend-price-num">299</strong></span>
<span class="recommend-count">1908件已售</span>
</p>
</a>
</li>
<li class="recommend-item">
<a href="###" class="recommend-link">
<p class="recommend-pic">
<img src="img/recommend/4.jpg" alt="recommend" class="recommend-img">
</p>
<p class="recommend-name">三只松鼠坚果礼包</p>
<p class="recommend-origPrice">
<del>¥125.00</del>
</p>
<p class="recommend-info">
<span class="recommend-price">¥<strong class="recommend-price-num">108</strong></span>
<span class="recommend-count">9532件已售</span>
</p>
</a>
</li>
<li class="recommend-item">
<a href="###" class="recommend-link">
<p class="recommend-pic">
<img src="img/recommend/5.jpg" alt="recommend" class="recommend-img">
</p>
<p class="recommend-name">蓝月亮洗衣液12斤</p>
<p class="recommend-origPrice">
<del>¥133.40</del>
</p>
<p class="recommend-info">
<span class="recommend-price">¥<strong class="recommend-price-num">89.9</strong></span>
<span class="recommend-count">5399件已售</span>
</p>
</a>
</li>
<li class="recommend-item">
<a href="###" class="recommend-link">
<p class="recommend-pic">
<img src="img/recommend/6.jpg" alt="recommend" class="recommend-img">
</p>
<p class="recommend-name">福临门葵花玉米油</p>
<p class="recommend-origPrice">
<del>¥109.90</del>
</p>
<p class="recommend-info">
<span class="recommend-price">¥<strong class="recommend-price-num">89.9</strong></span>
<span class="recommend-count">6294件已售</span>
</p>
</a>
</li>
<li class="recommend-item">
<a href="###" class="recommend-link">
<p class="recommend-pic">
<img src="img/recommend/7.jpg" alt="recommend" class="recommend-img">
</p>
<p class="recommend-name">TP-LINK 全千兆端口双频无线路由器</p>
<p class="recommend-origPrice">
<del>¥179.00</del>
</p>
<p class="recommend-info">
<span class="recommend-price">¥<strong class="recommend-price-num">169</strong></span>
<span class="recommend-count">4255件已售</span>
</p>
</a>
</li>
<li class="recommend-item">
<a href="###" class="recommend-link">
<p class="recommend-pic">
<img src="img/recommend/8.jpg" alt="recommend" class="recommend-img">
</p>
<p class="recommend-name">【前1800名再减50】家用高压洗车机</p>
<p class="recommend-origPrice">
<del>¥790.00</del>
</p>
<p class="recommend-info">
<span class="recommend-price">¥<strong class="recommend-price-num">268</strong></span>
<span class="recommend-count">1599件已售</span>
</p>
</a>
</li>
<li class="recommend-item">
<a href="###" class="recommend-link">
<p class="recommend-pic">
<img src="img/recommend/9.jpg" alt="recommend" class="recommend-img">
</p>
<p class="recommend-name">德国鲁茜rusch迷你婴儿辅食机 宝宝</p>
<p class="recommend-origPrice">
<del>¥898.00</del>
</p>
<p class="recommend-info">
<span class="recommend-price">¥<strong class="recommend-price-num">159</strong></span>
<span class="recommend-count">881件已售</span>
</p>
</a>
</li>
<li class="recommend-item">
<a href="###" class="recommend-link">
<p class="recommend-pic">
<img src="img/recommend/10.jpg" alt="recommend" class="recommend-img">
</p>
<p class="recommend-name">西域之尚红枣500g*5袋</p>
<p class="recommend-origPrice">
<del>¥89.00</del>
</p>
<p class="recommend-info">
<span class="recommend-price">¥<strong class="recommend-price-num">29.9</strong></span>
<span class="recommend-count">15049件已售</span>
</p>
</a>
</li>
</ul>
</div>
</div>
<div class="tabbar-container">
<ul class="tabbar">
<li class="tabbar-item tabbar-item-active">
<a href="###" class="tabbar-link">
<i class="iconfont icon-home"></i>
<span>首页</span>
</a>
</li>
<li class="tabbar-item">
<a href="###" class="tabbar-link">
<i class="iconfont icon-category"></i>
<span>分类页</span>
</a>
</li>
<li class="tabbar-item">
<a href="###" class="tabbar-link">
<i class="iconfont icon-cart"></i>
<span>购物车</span>
</a>
</li>
<li class="tabbar-item">
<a href="###" class="tabbar-link">
<i class="iconfont icon-personal"></i>
<span>个人中心</span>
</a>
</li>
</ul>
</div>
<script src="js/hammer.min.js"></script>
<script src="js/slider.js"></script>
<script>
var slider = new Slider(document.getElementById('slider'), {
initIndex: 0, // 初始显示第几张幻灯片,从0开始
speed: 300, // 幻灯片的切换速度
hasIndicator: true // 是否需要指示器indicator
});
var hammer = new Hammer(slider.getItemContainer());
// 是否在滑动
var isSwiping = false;
// 拖动时--手指拖动时
hammer.on('panmove', function(ev) {
// 求出移动是距离
var distance = ev.deltaX + slider.getDistanceByIndex(slider.getIndex());
// 让幻灯片移动
slider.move(distance);
});
// 拖动完成后--手指抬起后
hammer.on('panend', function(ev) {
if (isSwiping) return;
// deltaX-手指开始拖动到拖动完成手指抬起后所移动的x的距离
var distance = ev.deltaX + slider.getDistanceByIndex(slider.getIndex());
var index = getIndexByDistance(distance);
slider.to(index);
});
// 根据容器移动的距离获取索引
function getIndexByDistance(distance) {
if (distance > 0) {
return 0;
} else {
return Math.round(-distance / slider.getDistancePerSlide());
}
}
// 快速左滑动切换
hammer.on('swipeleft', function(ev) { // next
isSwiping = true;
slider.next(function() {
isSwiping = false;
});
});
// 快速右滑动切换
hammer.on('swiperight', function(ev) { // prev
isSwiping = true;
slider.prev(function() {
isSwiping = false;
});
});
</script>
</body>
</html>
关于具体项目代码,感兴趣的小伙伴可以私聊我噢~~
这是在ipad pro下:
这是在iPhone6/7/8下: