H5移动端适配
移动端web页面的开发,由于手机屏幕尺寸、分辨率不同,或者需要考虑横竖屏问题,为了使得web页面在不同移动设备上具有相适应的展示效果,需要在开发过程中使用合理的适配方案来解决这个问题。
尺寸
想要实现移动端适配 就要认识下 显示设备的物理尺寸(屏幕对角线长度) 单位 “英寸”
一般用英寸描述屏幕的物理大小,如电脑显示器的17、22,手机显示器的4.8、5.7等使用的单位都是英寸。
英寸(inch,缩写为in)在荷兰语中的本意是大拇指,一英寸就是指甲底部普通人拇指的宽度。
英寸和厘米的换算:1英寸 = 2.54 厘米

分辨率
平常买手机都会关注一下手机分辨率, 一般来说,在一定条件下,智能手机的分辨率越来越高确实是好的。分辨率越高,也就代表着屏幕越清晰,视觉体验更好.
像素
说道像素我们第一时间会想到(Pixel) , 像素即一个小方块,它具有特定的位置和颜色。
图片、电子屏幕(手机、电脑)就是由无数个具有特定颜色和特定位置的小方块拼接而成。
像素可以作为图片或电子屏幕的最小组成单位。
在我们CSS布局中使用的px单位像素是一个相对单位, 我们称之为逻辑像素 .

手机 显示器 等物理设备上的 像素点 我们称之为 物理像素


所以对应分辨率 我们也会区分出屏幕分辨率(物理)和图像分辨率(逻辑)。
屏幕分辨率
屏幕分辨率指的是一个屏幕具体由多少个
物理像素点组成

HUAWEI P40 Pro 屏幕分辨率为 2640 x 1200 这表示手机分别在垂直和水平上所具有的像素点数。

iPhone XS Max 和 iPhone SE的分辨率分别为2688 x 1242和1136 x 640。这表示手机分别在垂直和水平上所具有的像素点数。
图像分辨率
我们通常说的
图片分辨率其实是指图片含有的像素数,比如一张图片的分辨率为800 x 400。这表示图片分别在垂直和水平上所具有的像素点数为800和400。
同一尺寸的图片,分辨率越高,图片越清晰。

PPI
PPI(Pixel Per Inch):每英寸包括的像素数。
PPI可以用于描述屏幕的清晰度以及一张图片的质量。
使用PPI描述图片时,PPI越高,图片质量越高,使用PPI描述屏幕时,PPI越高,屏幕越清晰。
在上面描述手机分辨率的图片中,我们可以看到:iPhone XS Max 和 iPhone SE的PPI分别为458和326,这足以证明前者的屏幕更清晰。
由于手机尺寸为手机对角线的长度,我们通常使用如下的方法计算PPI:

DPI
DPI(Dot Per Inch):即每英寸包括的点数。
这里的点是一个抽象的单位,它可以是屏幕像素点、图片像素点也可以是打印机的墨点。
平时你可能会看到使用DPI来描述图片和屏幕,这时的DPI应该和PPI是等价的,DPI最常用的是用于描述打印机,表示打印机每英寸可以打印的点数。
一张图片在屏幕上显示时,它的像素点数是规则排列的,每个像素点都有特定的位置和颜色。
当使用打印机进行打印时,打印机可能不会规则的将这些点打印出来,而是使用一个个打印点来呈现这张图像,这些打印点之间会有一定的空隙,这就是DPI所描述的:打印点的密度。

在上面的图像中我们可以清晰的看到,打印机是如何使用墨点来打印一张图像。
所以,打印机的DPI越高,打印图像的精细程度就越高,同时这也会消耗更多的墨点和时间。
https://screensiz.es/
设备独立像素 DIP
实际上,上面我们描述的像素都是物理像素,即设备上真实的物理单元。
下面我们来看看设备独立像素究竟是如何产生的:
智能手机发展非常之快,在几年之前,我们还用着分辨率非常低的手机A,分辨率是320x480,我们可以在上面浏览正常的文字、图片等等。
但是,随着科技的发展,低分辨率的手机已经不能满足我们的需求了。很快,更高分辨率的屏幕诞生了它的分辨率是640x940,我们成为手机B
理论上来讲,在A手机上相同大小的图片和文字,在B手机上会被缩放一倍,因为它的分辨率提高了一倍。这样,岂不是后面出现更高分辨率的手机,页面元素会变得越来越小吗?

然而,事实并不是这样的,我们现在使用的智能手机,不管分辨率多高,他们所展示的界面比例都是基本类似的。乔布斯在iPhone4的发布会上首次提出了Retina Display(视网膜屏幕)的概念,它正是解决了上面的问题,这也使它成为一款跨时代的手机。

在iPhone4使用的视网膜屏幕中,把2x2个像素当1个像素使用,这样让屏幕看起来更精致,但是元素的大小却不会改变。

如果B手机使用了视网膜屏幕的技术,那么显示结果应该是下面的情况,比如列表的宽度为300个像素,那么在一条水平线上,A手机会用300个物理像素去渲染它,而B手机实际上会用600个物理像素去渲染它。
我们必须用一种单位来同时告诉不同分辨率的手机,它们在界面上显示元素的大小是多少,这个单位就是设备独立像素(Device Independent Pixels)简称DIP或DP。上面我们说,列表的宽度为300个像素,实际上我们可以说:列表的宽度为300个设备独立像素。
打开chrome的开发者工具,我们可以模拟各个手机型号的显示情况,每种型号上面会显示一个尺寸,比如iPhone X显示的尺寸是375x812,实际iPhone X的分辨率会比这高很多,这里显示的就是设备独立像素。

设备像素比 DPR
设备像素比
device pixel ratio简称dpr,即物理像素和设备独立像素的比值。
在web中,浏览器为我们提供了window.devicePixelRatio来帮助我们获取dpr。
//iPhoneX
window.devicePixelRatio; //3
在css中,可以使用媒体查询min-device-pixel-ratio,区分dpr:
@media (-webkit-min-device-pixel-ratio: 2),(min-device-pixel-ratio: 2){
/*适配IPhone 两倍屏*/
}
当然,上面的规则也有例外,iPhone 6、7、8 Plus的实际物理像素是1080 x 1920,在开发者工具中我们可以看到:它的设备独立像素是414 x 736,设备像素比为3,设备独立像素和设备像素比的乘积并不等于1080 x 1920,而是等于1242 x 2208。
实际上,手机会自动把1242 x 2208个像素点塞进1080 * 1920个物理像素点来渲染,我们不用关心这个过程,而1242 x 2208被称为屏幕的设计像素。我们开发过程中也是以这个设计像素为准。
实际上,从苹果提出视网膜屏幕开始,才出现设备像素比这个概念,因为在这之前,移动设备都是直接使用物理像素来进行展示。

紧接着,Android同样使用了其他的技术方案来实现DPR大于1的屏幕,不过原理是类似的。由于Android屏幕尺寸非常多、分辨率高低跨度非常大,不像苹果只有它自己的几款固定设备、尺寸。所以,为了保证各种设备的显示效果,Android按照设备的像素密度将设备分成了几个区间:

当然,所有的Android设备不一定严格按照上面的分辨率,每个类型可能对应几种不同分辨率,所以,每个Android手机都能根据给定的区间范围,确定自己的DPR,从而拥有类似的显示。当然,仅仅是类似,由于各个设备的尺寸、分辨率上的差异,设备独立像素也不会完全相等,所以各种Android设备仍然不能做到在展示上完全相等。

移动端app开发下的单位(了解)
在iOS、Android开发中样式单位其实都使用的是设备独立像素。
iOS的尺寸单位为pt,Android的尺寸单位为dp
为了适配所有机型,我们在写样式时需要把物理像素转换为设备独立像素:例如:如果给定一个元素的高度为200px(这里的px指物理像素,非CSS像素),iphone6的设备像素比为2,我们给定的height应为200px/2=100dp。
WEB端开发下的单位
在写CSS时,我们用到最多的单位是px,即CSS像素,当页面缩放比例为100%时,一个CSS像素等于一个设备独立像素。
但是CSS像素是很容易被改变的,当用户对浏览器进行了放大,CSS像素会被放大,这时一个CSS像素会跨越更多的物理像素。
页面的缩放系数 = CSS像素 / 设备独立像素。
视口 viewport
viewport 解释为中文就是‘视口’的意思,也就是浏览器中用于显示网页的区域。在 PC 端,其大小也就是浏览器可视区域的大小,所以我们也不会太关注此概念;而在移动端,绝大多数情况下 viewport 都大于浏览器可视区,保证 PC 页面在移动浏览器上面的可视性。为提升可视性体验,针对移动端有了对 viewport 的深入研究。
在移动端有三种类型的 viewport: layoutviewport、visualviewport、idealviewport。具体解释如下
-
layoutviewport 布局视口: 大于实际屏幕, 元素的宽度继承于 layoutviewport,用于保证网站的外观特性与桌面浏览器一样。layoutviewport 到底多宽,每个浏览器不同。iPhone 的 safari 为 980px,通过 document.documentElement.clientWidth 获取。
-
visualviewport 视觉视口: 当前显示在屏幕上的页面,即浏览器可视区域的宽度。
-
idealviewport 理想视口: 为浏览器定义的可完美适配移动端的理想 viewport,固定不变,可以认为是设备视口宽度。比如 iphone 7 为 375px, iphone 7p 为 414px。
布局视口
布局视口(
layout viewport):当我们以百分比来指定一个元素的大小时,它的计算值是由这个元素的包含块计算而来的。当这个元素是最顶级的元素时,它就是基于布局视口来计算的。

所以,布局视口是网页布局的基准窗口,在PC浏览器上,布局视口就等于当前浏览器的窗口大小(不包括borders 、margins、滚动条)。
在移动端,布局视口被赋予一个默认值,大部分为980px,这保证PC的网页可以在手机浏览器上呈现,但是非常小,用户可以手动对网页进行放大。
我们可以通过调用document.documentElement.clientWidth / clientHeight来获取布局视口大小。
视觉视口
视觉视口(
visual viewport):用户通过屏幕真实看到的区域

视觉视口默认等于当前浏览器的窗口大小(包括滚动条宽度)。
当用户对浏览器进行缩放时,不会改变布局视口的大小,所以页面布局是不变的,但是缩放会改变视觉视口的大小。
例如:用户将浏览器窗口放大了200%,这时浏览器窗口中的CSS像素会随着视觉视口的放大而放大,这时一个CSS像素会跨越更多的物理像素。
所以,布局视口会限制你的CSS布局而视觉视口决定用户具体能看到什么。
我们可以通过调用window.innerWidth / innerHeight来获取视觉视口大小。
理想视口
布局视口在移动端展示的效果并不是一个理想的效果,所以理想视口(
ideal viewport)就诞生了:网站页面在移动端展示的理想大小。

如上图,我们在描述设备独立像素时曾使用过这张图,在浏览器调试移动端时页面上给定的像素大小就是理想视口大小,它的单位正是设备独立像素。
上面在介绍CSS像素时曾经提到页面的缩放系数 = CSS像素 / 设备独立像素,实际上说页面的缩放系数 = 理想视口宽度 / 视觉视口宽度更为准确。
所以,当页面缩放比例为100%时,CSS像素 = 设备独立像素,理想视口 = 视觉视口。
我们可以通过调用screen.width / height来获取理想视口大小。
视口适配 Meta viewport
<meta>元素表示那些不能由其它HTML元相关元素之一表示的任何元数据信息,它可以告诉浏览器如何解析页面。
我们可以借助元素的viewport来帮助我们设置视口、缩放等,从而让移动端得到更好的展示效果。
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
Value 可能值 描述 width 正整数或device-width 以pixels(像素)为单位, 定义布局视口的宽度。 height 正整数或device-height 以pixels(像素)为单位, 定义布局视口的高度。 initial-scale 0.0 - 10.0 定义页面初始缩放比率。 minimum-scale 0.0 - 10.0 定义缩放的最小值;必须小于或等于maximum-scale的值。 maximum-scale 0.0 - 10.0 定义缩放的最大值;必须大于或等于minimum-scale的值。 user-scalable 一个布尔值(yes或者no) 如果设置为 no,用户将不能放大或缩小网页。默认值为 yes。
Value | 可能值 | 描述 |
|---|---|---|
width | 正整数或device-width | 以pixels(像素)为单位, 定义布局视口的宽度。 |
height | 正整数或device-height | 以pixels(像素)为单位, 定义布局视口的高度。 |
initial-scale | 0.0 - 10.0 | 定义页面初始缩放比率。 |
minimum-scale | 0.0 - 10.0 | 定义缩放的最小值;必须小于或等于maximum-scale的值。 |
maximum-scale | 0.0 - 10.0 | 定义缩放的最大值;必须大于或等于minimum-scale的值。 |
user-scalable | 一个布尔值(yes或者no) | 如果设置为 no,用户将不能放大或缩小网页。默认值为 yes。 |
device-width就等于理想视口的宽度,所以设置width=device-width就相当于让布局视口等于理想视口。
由于initial-scale = 理想视口宽度 / 视觉视口宽度,所以我们设置initial-scale=1;就相当于让视觉视口等于理想视口。
这时,1个CSS像素就等于1个设备独立像素,而且我们也是基于理想视口来进行布局的,所以呈现出来的页面布局在各种设备上都能大致相似。
//这三个值相等就是适配成功
console.log(window.screen.width)//设备理想视口 iphone 7 为 375px
console.log(document.documentElement.clientWidth)//布局视口 900
console.log(window.innerWidth)//手机整体宽度 1000
//加上以下,上面是三个值就能完全相等完成适配
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
缩放设置
上面提到width可以决定布局视口的宽度,实际上它并不是布局视口的唯一决定性因素,设置initial-scale也有肯能影响到布局视口,因为布局视口宽度取的是width和视觉视口宽度的最大值。
例如:若手机的理想视口宽度为400px,设置width=device-width,initial-scale=2,此时视觉视口宽度 = 理想视口宽度 / initial-scale即200px,布局视口取两者最大值即device-width 400px。
若设置width=device-width,initial-scale=0.5,此时视觉视口宽度 = 理想视口宽度 / initial-scale即800px,布局视口取两者最大值即800px。
获取窗口大小API
-
window.innerHeight:获取浏览器视觉视口高度(包括垂直滚动条)。 -
window.outerHeight:获取浏览器窗口外部的高度。表示整个浏览器窗口的高度,包括侧边栏、窗口镶边和调正窗口大小的边框。 -
window.screen.Height:获取获屏幕取理想视口高度,这个数值是固定的,`设备的分辨率/设备像素比 -
window.screen.availHeight:浏览器窗口可用的高度。 -
document.documentElement.clientHeight:获取浏览器布局视口高度,包括内边距,但不包括垂直滚动条、边框和外边距。 -
document.documentElement.offsetHeight:包括内边距、滚动条、边框和外边距。 -
document.documentElement.scrollHeight:在不使用滚动条的情况下适合视口中的所有内容所需的最小宽度。测量方式与clientHeight相同:它包含元素的内边距,但不包括边框,外边距或垂直滚动条。
媒体查询 @media
使用 @media 查询,你可以针对不同的媒体类型定义不同的样式。
@media mediatype and|not|only (media feature) {
CSS-Code;
}
<link rel="stylesheet" media="mediatype and|not|only (media feature)" href="mystylesheet.css">
媒体类型
| 值 | 描述 |
|---|---|
| all | 用于所有设备 |
| aural | 已废弃。用于语音和声音合成器 |
| braille | 已废弃。 应用于盲文触摸式反馈设备 |
| embossed | 已废弃。 用于打印的盲人印刷设备 |
| handheld | 已废弃。 用于掌上设备或更小的装置,如PDA和小型电话 |
| 用于打印机和打印预览 | |
| projection | 已废弃。 用于投影设备 |
| screen | 用于电脑屏幕,平板电脑,智能手机等。 |
| speech | 应用于屏幕阅读器等发声设备 |
| tty | 已废弃。 用于固定的字符网格,如电报、终端设备和对字符有限制的便携设备 |
| tv | 已废弃。 用于电视和网络电视 |
媒体功能
| 值 | 描述 |
|---|---|
| aspect-ratio | 定义输出设备中的页面可见区域宽度与高度的比率 |
| color | 定义输出设备每一组彩色原件的个数。如果不是彩色设备,则值等于0 |
| color-index | 定义在输出设备的彩色查询表中的条目数。如果没有使用彩色查询表,则值等于0 |
| device-aspect-ratio | 定义输出设备的屏幕可见宽度与高度的比率。 |
| device-height | 定义输出设备的屏幕可见高度。 |
| device-width | 定义输出设备的屏幕可见宽度。 |
| grid | 用来查询输出设备是否使用栅格或点阵。 |
| height | 定义输出设备中的页面可见区域高度。 |
| max-aspect-ratio | 定义输出设备的屏幕可见宽度与高度的最大比率。 |
| max-color | 定义输出设备每一组彩色原件的最大个数。 |
| max-color-index | 定义在输出设备的彩色查询表中的最大条目数。 |
| max-device-aspect-ratio | 定义输出设备的屏幕可见宽度与高度的最大比率。 |
| max-device-height | 定义输出设备的屏幕可见的最大高度。 |
| max-device-width | 定义输出设备的屏幕最大可见宽度。 |
| max-height | 定义输出设备中的页面最大可见区域高度。 |
| max-monochrome | 定义在一个单色框架缓冲区中每像素包含的最大单色原件个数。 |
| max-resolution | 定义设备的最大分辨率。 |
| max-width | 定义输出设备中的页面最大可见区域宽度。 |
| min-aspect-ratio | 定义输出设备中的页面可见区域宽度与高度的最小比率。 |
| min-color | 定义输出设备每一组彩色原件的最小个数。 |
| min-color-index | 定义在输出设备的彩色查询表中的最小条目数。 |
| min-device-aspect-ratio | 定义输出设备的屏幕可见宽度与高度的最小比率。 |
| min-device-width | 定义输出设备的屏幕最小可见宽度。 |
| min-device-height | 定义输出设备的屏幕的最小可见高度。 |
| min-height | 定义输出设备中的页面最小可见区域高度。 |
| min-monochrome | 定义在一个单色框架缓冲区中每像素包含的最小单色原件个数 |
| min-resolution | 定义设备的最小分辨率。 |
| min-width | 定义输出设备中的页面最小可见区域宽度。 |
| monochrome | 定义在一个单色框架缓冲区中每像素包含的单色原件个数。如果不是单色设备,则值等于0 |
| orientation | 定义输出设备中的页面可见区域高度是否大于或等于宽度。 |
| resolution | 定义设备的分辨率。如:96dpi, 300dpi, 118dpcm |
| scan | 定义电视类设备的扫描工序。 |
| width | 定义输出设备中的页面可见区域宽度。 |

为什么要用适配
一般移动端的标准默认设计尺寸:750px
当视口适配时,即加上以下代码
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
iphoe 6/7/8设备的独立像素是375×667,则iphoe 6/7/8的宽度就为375px,设计图肯定是按照全像素设计,不会进行像素压缩,设计图都是在逻辑像素上绘制,但是在实际设备上,实际是750的像素会压缩到375之内来显示,因为iphoe 6/7/8的倍比DIP是2

如上图日历宽度是284px,如果我们css直接设置284px,然后用iphoe 或其他手机机型查看日历尺寸,会发现日历宽度实际要小很多。应该设置为284/2 px
这就出现了一个问题: px是相对逻辑单位,无法跟随设备的 宽度尺寸而变化,不能直接写固定的 px逻辑像素单位,写死了无法在不同手机里面都有相同的展示效果,不同尺寸宽度的手机内同一个元素的布局占比是一致的。
rem
css3 新增相对单位
rem和em单位一样,都是一个相对单位,不同的是em是相对于父元素的font-size进行计算,rem是相对于根元素html的font-size进行计算的,这样一来rem就完美的绕开了复杂的层级关系,实现了类似em单位的功能。默认情况下,浏览器给的字体大小是16px,按照转化关系16px = 1rem。
vw vh
css3新增视口单位
- vw:1vw等于视口宽度的1%
- vh:1vh等于视口高度的1%
- vmin:选取vw和vh中最小的那个
- vmax:选取vw和vh中最大的那个

例如,在桌面端浏览器视口尺寸为650px,那么1vw = 650 * 1% = 6.5px(这是理论推算得出,如果浏览器不支持0.5px,那么实际渲染结果可能是7px)。
如果设计稿为 750px,那么 1vw = 7.5px,100vw = 750px。其实设计稿按照设么都没多大关系,最终转化过来的都是相对单位,上面讲的 rem 也是对它的模拟。这里的比例关系也推荐不要自己换算,使用 pxtoviewport 的库就可以帮我们转换。当然每种方案都会有其弊端,这里就不展开讨论。
移动端调试方法
打开cmd,输入ipconfig
http://127.0.0.1:5503/event.html,这个是电脑本地打开的页面,手机要求连上同一个wifi,用ip地址替换127.0.0.1,在手机浏览器中输入http://192.168.3.14:5503/event.html即可。
flexible rem方案
flexible方案是阿里早期开源的一个移动端适配解决方案,引用flexible后,我们在页面上统一使用rem来布局。
https://github.com/amfe/lib-flexible
https://main.m.taobao.com/index.html
它的核心代码非常简单:
// set 1rem = viewWidth / 10
function setRemUnit () {
var rem = document.documentElement.clientWidth / 10;
document.documentElement.style.fontSize = rem + 'px';
}
setRemUnit();
//document.documentElement.clientWidth
rem 是相对于html节点的font-size来做计算的。
我们通过设置document.documentElement.style.fontSize就可以统一整个页面的布局标准。
上面的代码中,将html节点的font-size设置为页面clientWidth(布局视口)的1/10,即1rem就等于页面布局视口的1/10,这就意味着我们后面使用的rem都是按照页面比例来计算的。
这时,我们只需要将UI出的图转换为rem即可。
以iPhone6为例:布局视口为375px,则1rem = 37.5px,这时UI给定一个元素的宽为75px(设备独立像素),我们只需要将它设置为75 / 37.5 = 2rem。
6.06rem = 750px设计稿上 的 454.5px,即6.06×37.5×2 = 454.5px
当然,每个布局都要计算非常繁琐,我们可以借助PostCSS的px2rem插件来帮助我们完成这个过程。
下面的代码可以保证在页面大小变化时,布局可以自适应,当触发了window的resize和pageShow事件之后自动调整html的fontSize大小。
// reset rem unit on page resize
window.addEventListener('resize', setRemUnit);
window.addEventListener('pageshow', function (e) {
if (e.persisted) {//e.persisted判断当前页面是否从浏览器缓存(如后退/前进)中恢复加载。
setRemUnit() //true表示页面是从浏览器的 缓存(如往返缓存,即 BFCache) 中恢复的
} //false表示页面是 全新加载 的(例如直接访问或刷新页面)。
})
function setRemUnit () {
var rem = docEl.clientWidth / 10
docEl.style.fontSize = rem + 'px'
}
优点: 相对于px固定布局和百分比布局 能够非常好的解决 不同尺寸设备下的尺寸 间隙 字体 的相对大小问题
缺点: 1. 计算麻烦;2. 需要js辅助
缺点可以用sass,Stylus,写函数自动计算来弥补缺点。
vw_fontsize = 75
vw_design = 750
rem(px)
px / vw_fontsize * 1rem
div {
margin:0;
width: rem(455);
height:82px;
background-color:□red;
font-size:rem(80);
}
由于viewport单位得到众多浏览器的兼容,上面这种方案现在已经被官方弃用
vh、vw方案
vh、vw方案即将视觉视口宽度 window.innerWidth和视觉视口高度 window.innerHeight 等分为 100 份。
上面的flexible方案就是模仿这种方案,因为早些时候vw还没有得到很好的兼容。
vw(Viewport's width):1vw等于视觉视口的1%vh(Viewport's height):1vh为视觉视口高度的1%vmin:vw和vh中的较小值vmax: 选取vw和vh中的较大值

如果视觉视口为375px,那么1vw = 3.75px,这时UI给定一个元素的宽为75px(设备独立像素),我们只需要将它设置为75 / 3.75 = 20vw。
这里的比例关系我们也不用自己换算,我们可以使用PostCSS的 postcss-px-to-viewport 插件帮我们完成这个过程。写代码时,我们只需要根据UI给的设计图写px单位即可。
// iPhone 6 尺寸作为设计稿基准
$vw_base: 375px;
@function vw($px) {
@return ($px / $vw_base) * 100vw;
}
.mod_nav {
background-color: #fff;
&_list {
display: flex;
padding: vw(15) vw(10) vw(10); // 内间距
&_item {
flex: 1;
text-align: center;
font-size: vw(10); // 字体大小
&_logo {
display: block;
margin: 0 auto;
width: vw(40); // 宽度
height: vw(40); //高度
img {
display: block;
margin: 0 auto;
max-width: 100%;
}
}
&_name {
margin-top: vm(2);
}
}
}
}
当然,没有一种方案是十全十美的,vw同样有一定的缺陷:
px转换成vw不一定能完全整除,因此有一定的像素差。- 比如当容器使用
vw,margin采用px时,很容易造成整体宽度超过100vw,从而影响布局效果。当然我们也是可以避免的,例如使用padding代替margin,结合calc()函数使用等等…
结合方案
结合 rem viewport视口 vw 进行优化适配布局
两个要点:
- 给根元素大小设置随着视口变化而变化的vw单位,这样就可以实现动态改变其大小。
- 限制根元素字体大小的最大最小值,配合body加上最大宽度和最小宽度
这样我们就能够实现对布局宽度的最大最小限制。因此,根据以上条件,我们可以得出代码实现如下:
// rem 单位换算: 定位75px 只是方便运算, 750-75px、 640-64px、 1080-108px, 如此类推
$vw_fontsize: 75; // iPhone6 尺寸的根元素大于基准值
@function rem($px) {
@return ($px / $vm_fontsize / 2) * 1rem;
}
// 根元素大小使用vw单位
html {
font-size: ($vm_fontsize / ($vm_design / 2)) * 100vw;
// 同时,通过Media Queries 限制根元素最大值,最小值
@media screen and (max-width: 320px) {
font-size: 64px;
}
@media screen and (min-width:540px) {
font-size: 108px;
}
}
// body 也增加最大最小宽度限制,避免默认100%宽度的block元素跟随body而过大过小
body {
max-width: 540px;
min-width: 320px;
}
移动端字体缩放问题
pc端 字体最小限制为 12px
移动端 字体最小限制为 8px
移动端适配后 字体容易存在的问题:
-
如果使用rem单位设置字体, 那可能会出现通过rem计算,最终呈现到页面上是
23.335px这样的奇葩的字体大小,可能还会因此出现锯齿、模糊不清等问题; -
对于大屏希望一行显示的文字多一些,小屏显示的少一些,而不是一视同仁的全部显示同样多的文字。这样在大屏下显得文字特别大
解决方案:
-
移动端适配中常规模块内容 字体大小尽量推荐使用固定 px单位
iOS与Android平台的适配方式背后隐藏的设计哲学是这样的:阅读文字时,可读性较好的文字字号行距等绝对尺寸数值组合与文字所在媒介的绝对尺寸关系不大。(可以这样简单理解:A4大小的报纸和A3大小甚至更大的报纸,舒适的阅
读字号绝对尺寸是一样的,因为他们都需要拿在手里阅读,在手机也是上同理);在看图片视频时,图片、视频的比例应该是固定的,不应该出现拉伸变形的情况。而rem用在字号时,使字号在不同屏幕上的绝对尺寸不一致,违背了设计哲学
-
使用dpr适配
设备的dpr不一样时候我们可以通过js设置标签自定义属性来控制给css传递dpr,然后我们针对不同的dpr设置具体的字体就可以了。
比如,我们针对页面的标题的字体大小就可以如下设置:
.title { font-size: 12px; } [data-dpr="2"] .title { font-size: 24px; } [data-dpr="3"] .title { font-size: 36px; }
解决移动端1px 边框困境的方案
为了适配各种屏幕,我们写代码时一般使用设备独立像素来对页面进行布局。
而在设备像素比大于1的屏幕上,我们写的1px实际上是被多个物理像素渲染,这就会出现1px在有些屏幕上看起来很粗的现象。
.5px 边框
新版 ios支持 .5px 单位的边框 但是 兼容性差,目前之余IOS8+才支持,在IOS7及其以下、安卓系统都是显示0px。
@media only screen and (-webkit-min-device-pixel-ratio:2){
.border{
border-bottom: .5px solid #222;
}
}
border-image
基于media查询判断不同的设备像素比给定不同的border-image:
用边框线的图片进行剪裁,不建议,如果要换边框线的图片会很不方便
.border_1px{
border-bottom: 1px solid #000;
}
@media only screen and (-webkit-min-device-pixel-ratio:2){
.border_1px{
border-bottom: none;
border-width: 0 0 1px 0;
border-image: url(https://blog.csdn.net/img/1pxline.png) 0 0 2 0 stretch;
}
}
background-image
和border-image类似,准备一张符合条件的边框背景图,模拟在背景上。
.border_1px{
border-bottom: 1px solid #000;
}
@media only screen and (-webkit-min-device-pixel-ratio:2){
.border_1px{
background: url(https://blog.csdn.net/img/1pxline.png) repeat-x left bottom;
background-size: 100% 1px;
}
}
上面两种都需要单独准备图片,而且圆角不是很好处理,但是可以应对大部分场景。
伪类 + transform
基于media查询判断不同的设备像素比对线条进行缩放:
.border_1px:before{
content: '';
position: absolute;
top: 0;
height: 1px;
width: 100%;
background-color: #000;
transform-origin: 50% 0%;
}
@media only screen and (-webkit-min-device-pixel-ratio:2){
.border_1px:before{
transform: scaleY(0.5);
}
}
@media only screen and (-webkit-min-device-pixel-ratio:3){
.border_1px:before{
transform: scaleY(0.33);
}
}
这种方式可以满足各种场景,如果需要满足圆角,只需要给伪类也加上border-radius即可。
如下,下面那条线就是使用该方法做的线,上面那条线明显发虚,是因为他是两个物理像素合在一起的,是分散的,这个在手机端的时候眼睛近距离观察是很容易看出区别的

<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">
<title>vw + rem</title>
<style>
p {
width: 100%;
font-size: rem(28);
text-align: center;
border-bottom: 1px solid #222;
}
.bottom-line {
position: relative;
}
.bottom-line:after {
position: absolute;
left: 0;
bottom: -10px;
content: '';
width: 100%;
height: 1px;
background-color: #222;
transform: scaleY(0.5);
}
</style>
</head>
<body>
<p class="bottom-line">我是一段文本</p>
</body>
</html>
设置viewport
通过设置缩放,让CSS像素等于真正的物理像素。
例如:当设备像素比为3时,我们将页面缩放1/3倍,这时1px等于一个真正的屏幕像素。
const scale = 1 / window.devicePixelRatio;
const viewport = document.querySelector('meta[name="viewport"]');
if (!viewport) {
viewport = document.createElement('meta');
viewport.setAttribute('name', 'viewport');
window.document.head.appendChild(viewport);
}
viewport.setAttribute('content', 'width=device-width,user-scalable=no,initial-scale=' + scale + ',maximum-scale=' + scale + ',minimum-scale=' + scale);
实际上,上面这种方案是早先flexible采用的方案。
当然,这样做是要付出代价的,这意味着你页面上所有的布局都要按照物理像素来写。这显然是不现实的,这时,我们可以借助flexible或vw、vh来帮助我们进行适配。
box-shadow
css阴影的方式处
div{
box-shadow:0 -1px 2px #ccc;
}
保持图片的宽高比
保持高宽比的图,应改用padding-top实现
.mod_banner {
position: relative;
padding-top: percentage(100/700); // 使用padding-top
height: 0;
overflow: hidden;
img {
width: 100%;
height: auto;
position: absolute;
left: 0;
top: 0;
}
图片模糊问题
产生原因
我们平时使用的图片大多数都属于位图(png、jpg...),位图由一个个像素点构成的,每个像素都具有特定的位置和颜色值:

理论上,位图的每个像素对应在屏幕上使用一个物理像素来渲染,才能达到最佳的显示效果。
而在dpr > 1的屏幕上,位图的一个像素可能由多个物理像素来渲染,然而这些物理像素点并不能被准确的分配上对应位图像素的颜色,只能取近似值,所以相同的图片在dpr > 1的屏幕上就会模糊:
解决方案
为了保证图片质量,我们应该尽可能让一个屏幕像素来渲染一个图片像素,所以,针对不同DPR的屏幕,我们需要展示不同分辨率的图片,这样用户才能有更好的浏览体验。
如:在dpr=2的屏幕上展示两倍图(@2x),在dpr=3的屏幕上展示三倍图(@3x)。

会有人问为什么不直接用2倍图或3倍图,直接用高清图,主要是考虑到性能和负载量的问题,在千万级访问量的项目上,图片都是放在web服务器上,如果一个用户访问16个图标,如果他是1倍屏,他一次浏览就多浪费了32×15 KB ,如果更多图片就会有更多的流量浪费,因为一般项目是会付带宽费用的,有一个人访问就会付一个人的带宽费用,这也是为什么要CSS优化,如0.5的时候写.5,0不要加单位,16进行能缩写就缩写,这些规则都是为了节约流量,这些在大项目中积少成多可以省很多成本。大型企业对流量浪费的把控是很严格的,会有很多的自动化测试模型是卡你项目的流量优化。
所以一倍屏用户用一倍图,二倍屏用户用二倍图,不浪费更多的流量,让每个人都有更好的体验效果。
media查询
使用media查询判断不同的设备像素比来显示不同精度的图片:
.avatar{
background-image: url(conardLi_1x.png);
}
@media only screen and (-webkit-min-device-pixel-ratio:2){
.avatar{
background-image: url(conardLi_2x.png);
}
}
@media only screen and (-webkit-min-device-pixel-ratio:3){
.avatar{
background-image: url(conardLi_3x.png);
}
}
只适用于背景图
image-set
使用image-set:
.avatar {
background-image: -webkit-image-set( "conardLi_1x.png" 1x, "conardLi_2x.png" 2x );
}
只适用于背景图
srcset
使用img标签的srcset属性,浏览器会自动根据像素密度匹配最佳显示图片:
<img src="conardLi_1x.png"
srcset=" conardLi_2x.png 2x, conardLi_3x.png 3x">
JavaScript拼接图片url
使用window.devicePixelRatio获取设备像素比,遍历所有图片,替换图片地址为对应的像素:
const dpr = window.devicePixelRatio;
const images = document.querySelectorAll('img');
images.forEach((img)=>{
img.src.replace(".", `@${dpr}x.`);
})
HD 高清化 viewport适配
高清化适配要解决的问题:
该方案是解决1像素线的问题,和在不同dpr手机显示效果一样,还要用最省事的方法,不用伪元素去做边框线,就可以用该高清划适配方案。
该方法的原理是动态的修改meta标签的viewport,动态的用js去修改,保证一像素线就是一个物联像素的大小,在dpr大的设备中,页面比例缩小后会使得页面元素也跟着一起缩小,这时候我们再根据缩放比例,调整html的font-size来放大元素。
<!-- 3倍屏手机就改成:-->
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=0.3333333333333333,maximum-scale=0.3333333333333333,minimum-scale=0.3333333333333333">
<!-- 2倍屏手机就改成:-->
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=0.5,maximum-scale=0.5,minimum-scale=0.5">
<!-- 3倍屏手机就改成:-->
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1">
高清化原理和步骤:
1. 实现高清化 1px 线
(1)原理
通过修改viewport 的 initial-scale 缩放比例实现1px 线
2倍屏下 scale 修改为 0.5 能够实现 1px 极细线 ip4-ip6
3倍屏下 scale 修改为 0.3 能够实现 1px 极细线 ip6pls - ip11
(2)JS实现
用js 获取 window.devicePixelRatio获取设备的dpr
设置缩放比例 1/dpr
1倍屏 dpr = 1
2倍屏 dpr = 2 缩小到 .5
3倍屏 dpr = 3 缩小到 .3333
2. 设置了 initial-scale 缩放比例后 所有页面内容都会被缩小
例如:
div width为200 设置initial-scale 由1改为.5之后 div的显示宽度为100
p文本也会变小 之前字体大小为16px 缩小后为 8px
注:虽然之前有说字体大小最小设置是16px ,因为缩放是视觉效果,所以缩小后为8px也是可以成立的。
为了解决这个问题 我们需要 修改元素的css尺寸为原来的2倍 或者 3倍
例如 原先 width: rem(400); 现在就应该设置为 width: rem(800)
字体原先是 rem(28) 就应该设置为 rem(56)
但我们不可能一个个去手动修改页面中rem,应该用js动态修改
因为rem单位的参照系是 html的fontsize 我们只需要让font-size改
为1倍屏幕下的2倍 或者3倍就可以
doc.documentElement.style.fontSize = `${_baseFontSize / 2 * dpr}px`;
HD 高清化 viewport适配方案封装代码
HDview.js:
'use strict';
/**
* @param {Boolean} [normal = false] - 默认开启页面压缩以使页面高清;
* @param {Number} [baseFontSize = 100] - 基础fontSize, 默认100px;
* @param {Number} [fontscale = 1] - 有的业务希望能放大一定比例的字体;
*/
(function (win) {
win.flex = (baseFontSize, fontscale) => {//
const _baseFontSize = baseFontSize || 100;
const _fontscale = fontscale || 1;
const doc = win.document;
const ua = navigator.userAgent;
const matches = ua.match(/Android[\S\s]+AppleWebkit\/(\d{3})/i);
const UCversion = ua.match(/U3\/((\d+|\.){5,})/i);
const isUCHd = UCversion && parseInt(UCversion[1].split('.').join(''), 10) >= 80;
const isIos = navigator.appVersion.match(/(iphone|ipad|ipod)/gi);
//获取设备DPR 赋值给变量 dpr
let dpr = win.devicePixelRatio || 1;
if (!isIos && !(matches && matches[1] > 534) && !isUCHd) {
// 如果非iOS, 非Android4.3以上, 非UC内核, 就不执行高清, dpr设为1;
dpr = 1;
}
//根据dpr 反比设置scale dpr越高 缩放比例 越小 2dpr => scale(.5) 3dpr=>scale(.33)
const scale = 1 / dpr;
//获取meta viewport标签
let metaEl = doc.querySelector('meta[name="viewport"]');
if (!metaEl) {
//如果没有 创建一个
metaEl = doc.createElement('meta');
metaEl.setAttribute('name', 'viewport');
doc.head.appendChild(metaEl);
}
// 设置meta viewport 属性 拼接缩放比例到 content值中
metaEl.setAttribute('content', `width=device-width,user-scalable=no,initial-scale=${scale},maximum-scale=${scale},minimum-scale=${scale}`);
//根据缩小倍率 反向 等比放大 html的font-size 保证实际效果一样
doc.documentElement.style.fontSize = `${_baseFontSize / 2 * dpr}px`;
document.documentElement.setAttribute('data-dpr', dpr)
};
})(window);
调用:
<!-- 在引入后调用一下flex就可以了 -->
<script>
flex(75);//默认情况下html的font-size = 75 px,
</script>
适配iPhoneX
iPhoneX的出现将手机的颜值带上了一个新的高度,它取消了物理按键,改成了底部的小黑条,但是这样的改动给开发者适配移动端又增加了难度。
安全区域`
在iPhoneX发布后,许多厂商相继推出了具有边缘屏幕的手机。
这些手机和普通手机在外观上无外乎做了三个改动:圆角(corners)、刘海(sensor housing)和小黑条(Home Indicator)。为了适配这些手机,安全区域这个概念变诞生了:安全区域就是一个不受上面三个效果的可视窗口范围。
为了保证页面的显示效果,我们必须把页面限制在安全范围内,但是不影响整体效果。
viewport-fit
viewport-fit是专门为了适配iPhoneX而诞生的一个属性,它用于限制网页如何在安全区域内进行展示。

contain: 可视窗口完全包含网页内容
cover:网页内容完全覆盖可视窗口
默认情况下或者设置为auto和contain效果相同。
env、constant

我们需要将顶部和底部合理的摆放在安全区域内,iOS11新增了两个CSS函数env、constant,用于设定安全区域与边界的距离。
函数内部可以是四个常量:
safe-area-inset-left:安全区域距离左边边界距离safe-area-inset-right:安全区域距离右边边界距离safe-area-inset-top:安全区域距离顶部边界距离safe-area-inset-bottom:安全区域距离底部边界距离
注意:我们必须指定viweport-fit后才能使用这两个函数:
<meta name="viewport" content="viewport-fit=cover">
constant在iOS < 11.2的版本中生效,env在iOS >= 11.2的版本中生效,这意味着我们往往要同时设置他们,将页面限制在安全区域内:
body {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
当使用底部固定导航栏时,我们要为他们设置padding值:
{
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
细节适配
移动端浏览器厂商复杂 IOS版本 安卓内核版本参差不齐 还有微信浏览器 QQ浏览器这种魔改内核版本 我们需要对一些个性化进行适配处理
meta权限
设置禁止缓存:直接从远程服务器拉取当前的资源内容,而不是从缓存里调取。
<meta http-equiv="Pragma" content="no-cache" >
有些手机,尤其是ios系统,它会把一些数字识别成电话号码,或识别为邮箱,一点击它就可以直接拨打电话。我们并不喜欢这个东西,所以要把它禁止掉
禁止将页面中的一连串数字识别为电话号码/邮箱、并设置为手机可以拨打的一个连接。 telephone默认值为 yes
<meta name="format-detection" content="telephone=no, email=no" >
.iphone设备中的safari私有meta标签它表示:允许全屏模式浏览;用于删除默认的苹果工具栏和菜单栏
<meta name="apple-mobile-web-app-capable" content="yes" >
content设置状态栏颜色,iphone的私有标签,它指定的iphone中safari顶端的状态条的样式;
只接受白色和黑色两种值,black/white
<meta name="apple-mobile-web-app-status-bar-style" content="black">
其他内核浏览器模式设置与权限配置
<!-- 启用360浏览器的极速模式(webkit),强制以极速模式浏览浏览,极速模式兼容性好 -->
<meta name="renderer" content="webkit">
<!-- 避免IE使用兼容模式 这个是我们做页面的常驻标签,用户用IE高版本访问的话,我们会强制开启edge内核,因为edge和chrome类似,兼容性比较好-->
<meta http-equiv="X-UA-Compatible" content="IE=edge"><!-- 常驻标签 -->
<!-- 针对手持设备优化,主要是针对一些老的不识别viewport的浏览器,比如黑莓,现在一般不用了 -->
<meta name="HandheldFriendly" content="true">
<!-- 微软的老式浏览器 ,现在一般不用了-->
<meta name="MobileOptimized" content="320">
<!-- uc强制竖屏 -->
<meta name="screen-orientation" content="portrait">
<!-- QQ强制竖屏 -->
<meta name="x5-orientation" content="portrait">
<!-- UC强制全屏 -->
<meta name="full-screen" content="yes">
<!-- QQ强制全屏 -->
<meta name="x5-fullscreen" content="true">
<!-- UC应用模式 -->
<meta name="browsermode" content="application">
<!-- QQ应用模式 -->
<meta name="x5-page-mode" content="app">
<!-- windows phone 点击无高光 windows phone都死了,现在基本也没用了-->
<meta name="msapplication-tap-highlight" content="no">
兼容优化
以下问题都是手机真机可能会出现的问题,电脑模拟器上是看不出来的
滑动卡顿
-webkit-overflow-scrolling: touch;
overflow-scrolling: touch;
touche://使用具有回弹效果的滚动, 当手指从触摸屏上移开,内容会继续保持一段时间的滚动效果。继续滚动的速度和持续的时间和滚动手势的强烈程度成正比。同时也会创建一个新的堆栈上下文。 同样可以解决overflow-x:auto在iOS的兼容问题
auto://使用普通滚动, 当手指从触摸屏上移开,滚动会立即停止。
IOS头部/底部的fixed定位导致输入框收回闪屏问题
main{
position:fixed;
top:50px;
bottom:50px;
overflow:scroll;
}
//只需要在中间部分外层div添加css样式
iOS下取消input在输入的时候英文首字母的默认大写
<input type="text" autocapitalize="none">
iOS系统中,中文输入法输入英文时,字母之间可能会出现一个六分之一空格的问题
this.value = this.value.replace(/\u2006/g, '');
某些情况下非可点击元素如(label,span)监听click事件,ios下不会触发
cursor: pointer;
//针对此种情况只需对不触发click事件的元素添加一行css代码即可
移动端元素:active伪类不生效
<body ontouchstart>
<!-- 给body添加 ontouchstart 属性 ,移动端页面要习惯加这个-->
</body>
html在移动端IOS点击闪屏问题
html,body{
-webkit-tap-highlight-color: rgba(0, 0, 0, 0); //或者 transparent
}
打电话,发短信,写邮件
打电话:
<a href="tel:10086">拨打10086</a>
发短信:
<a href="sms:10086">发短信给10086</a> //winphone系统无效
发邮件:
//注:在添加这些功能时,第一个功能以"?"开头,后面的以"&"开头
//普通邮件
<a href="mailto:2971411050@qq.com">点击我发邮件</a>
//收件地址后添加?cc=开头,可添加抄送地址(Android存在兼容问题)
<a href="mailto:2971411050@qq.com?cc=2971411050@qq.com">点击我发邮件</a>
阻止旋转屏幕时自动调整字体大小
* {
-webkit-text-size-adjust: none;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
text-size-adjust: 100%;
}
//iOS浏览器横屏时会重置字体大小,设置text-size-adjust为none可以解决iOS上的问题,但桌面版Safari的字体缩放功能会失效,因此最佳方案是将text-size-adjust为100%
input的placeholder会出现文本位置偏上的情况
input[type="text"] {
line-height:normal;
}
//input 的placeholder会出现文本位置偏上的情况:PC端设置line-height等于height能够对齐,而移动端仍然是偏上,解决方案时是设置css
移动端样式美化
//去掉webkit的滚动条
::-webkit-scrollbar{ display: none; } //去掉滚动条,兼容性也不好,小米手机,火狐浏览器还是有滚动条,chrome,华为手机滚动条去掉了
//--以下兼容性特别差,一般没有意义用上--//
::-webkit-scrollba //滚动条整体部分
::-webkit-scrollbar-thumb //滚动条内的小方块
::-webkit-scrollbar-track //滚动条轨道
::-webkit-scrollbar-button //滚动条轨道两端按钮
::-webkit-scrollbar-track-piece //滚动条中间部分,内置轨道
::-webkit-scrollbar-corner //边角,两个滚动条交汇处
::-webkit-resizer //两个滚动条的交汇处上用于通过拖动调整元素大小的小控件
//--------------------------------//
// 禁止长按链接与图片弹出菜单
a,img { -webkit-touch-callout: none }
// 禁止ios和android用户选中文字
html,body {
-webkit-user-select:none;
user-select: none;
}
// 改变输入框placeholder的颜色值
::-webkit-input-placeholder { /* WebKit browsers */
color: #999;
}
:-moz-placeholder { /* Mozilla Firefox 4 to 18 */
color: #999;
}
::-moz-placeholder { /* Mozilla Firefox 19+ */
color: #999;
}
:-ms-input-placeholder { /* Internet Explorer 10+ */
color: #999;
}
input:focus::-webkit-input-placeholder{
color:#999;
}
// android上去掉语音输入按钮
input::-webkit-input-speech-button {
display: none
}
//去除input默认样式的方法
input {
/*可同时屏蔽输入框怪异的内阴影,解决iOS下无法修改按钮样式,测试还发现,加了此属性后,iOS下默认还是有圆角的,不过可以用border-radius属性修改*/
-webkit-appearance: none;
border: 0;
}
终端访问判断
var browser= {
versions:function(){
var u = navigator.userAgent, app = navigator.appVersion;
return {
trident: u.indexOf('Trident') > -1, //IE内核
presto: u.indexOf('Presto') > -1, //opera内核
webKit: u.indexOf('AppleWebKit') > -1, //苹果、谷歌内核
gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') == -1,//火狐内核
mobile: !!u.match(/AppleWebKit.*Mobile.*/), //是否为移动终端
ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), //ios终端
android: u.indexOf('Android') > -1 || u.indexOf('Adr') > -1, //android终端
iPhone: u.indexOf('iPhone') > -1 , //是否为iPhone或者QQHD浏览器
iPad: u.indexOf('iPad') > -1, //是否iPad
webApp: u.indexOf('Safari') == -1, //是否web应该程序,没有头部与底部
weixin: u.indexOf('MicroMessenger') > -1, //是否微信 (2015-01-22新增)
qq: u.match(/\sQQ/i) == " qq" //是否QQ
};
}(),
console.log(browser.versions)
language:(navigator.browserLanguage || navigator.language).toLowerCase()
};
移动端事件
touch事件组
touch事件组最早出现于IOS safari中,为了向开发人员转达一些特殊的信息,所以新增了这些事件,随着Android中的webkit的加入,慢慢地这样的专有事件成了事实的标准,从而导致W3C开始指定Touch Event规范的内容。
touchstart、touchmove、touchend、touchcancel是移动端专属事件,电脑端是没办法触发的,相反的,mousedown、mousemove、mouseup是电脑端事件,在移动端是没办法触发的。mouse事件是针对指针的,在移动端是没有指针的,只有触点。
注:建议移动端的所有事件都用addEventListener监听,用on+事件在一些机子上会有bug。
- touchstart 手指触摸屏幕时触发,即使已经有手指在屏幕上也会触发。
- touchmove 手指在屏幕滑动时触发。
- touchend 手指从屏幕时移开时触发。
elem.addEventListener("touchstart",function(e){
console.log('touch触屏触摸');
});
elem.addEventListener("touchmove",function(e){
console.log('touch触屏移动');
});
elem.addEventListener("touchend",function(e){
console.log('touch触屏抬起');
});
elem.addEventListener("touchcancel",function(e){
console.log('系统取消触摸事件的时候触发');
});
监听触摸后触摸事件会实现一个event对象,这个对象里面包括3个触摸对象列表。
touches 屏幕上所有手指列表
targetTouches 在当前DOM标签上手指的列表
changedTouches 涉及当前事件的手指的列表
touches 屏幕上所有手指列表 :

targetTouches 在当前DOM标签上手指的列表

changedTouches 涉及当前事件的手指的列表:

event属性
clientX 触摸目标在浏览器中的x坐标
clientY 触摸目标在浏览器中的y坐标
identifier 标识触摸的唯一ID。 从0开始给手指编号 ...
pageX 触摸目标在当前DOM中的x坐标
pageY 触摸目标在当前DOM中的y坐标
screenX 触摸目标在屏幕中的x坐标
screenY 触摸目标在屏幕中的y坐标
target 触摸的DOM节点目标。
click延迟
在早期的手机上,浏览器为了能够实现放大和缩放功能,采用双击的方式来达到这样的交互效果,为了实现这样的效果,浏览器需要判断用户在第一次触碰屏幕之后,是否在300ms之内再次点击,有则表明用户希望缩放和放大,所以click事件会推迟到300ms之后运行。
解决方案:
使用FastClick库
<script src="https://cdn.bootcdn.net/ajax/libs/fastclick/0.1.0/fastclick.js"></script>
// 使用了jquery的时候
$(function() {
FastClick.attach(document.body);
});
// 没使用jquery的时候
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function() {
FastClick.attach(document.body);
}, false);
}
//DOMContentLoaded dom内容加载完毕触发 load事件会在所有资源(图片 脚本等)加载完成后触发
//DOMContentLoaded 的触发不需要等待图片等其他资源加载完成。
禁用缩放或设置理想视口
目前基本页面适配都是使用viewport标签适配 也都会锁定缩放和禁止用户缩放,当在viewport设置不允许缩放或设置成理想视口时,那么300ms延迟的机制也就不存在了
<meta name="viewport" content="user-scalable=no">
<meta name="viewport" content="width=device-width">
zepto tap事件
Zepto是一个轻量级的针对现代高级浏览器的JavaScript库, 它与jquery有着类似的api。 如果你会用jquery,那么你也会用zepto。
相当于移动端的jquery,要比jquery要小很多,jq把所有的东西都放在一个文件里面,而zepto分模块,根据需求引入对应的js。
地址:
模块化:
为了满足不同用户的不同需求,而且为了保证文件足够小,很多功能成了选配的独立模块。如下没有打钩的,比如你要用zepto 的touch功能,那么你就引入zepto-touch.js。

Touch events
地址: https://github.com/madrobby/zepto/blob/master/src/touch.js#files
“touch”模块添加以下事件,可以使用 on 和 off。
tap—元素tap的时候触发。swipe,swipeLeft,swipeRight,swipeUp,swipeDown— 当元素被划过时触发。(可选择给定的方向)longTap— 当一个元素被按住超过750ms触发。touchstart 开始750ms内没有触发touchmove touchend scrollsingleTapanddoubleTap— 这一对事件可以用来检测元素上的单击和双击。(如果你不需要检测单击、双击,使用tap代替)。
这些事件也是所有Zepto对象集合上的快捷方法。
<script src="js/zepto.min.js"></script>
<script src="js/zepto-touch.js"></script>
<script>
const target = document.querySelector('#target');
target.addEventListener('click', () => {
console.time();
console.log('click');
console.timeEnd();
})
target.addEventListener('touchstart', () => {
console.time();
console.log('touchstart');
console.timeEnd();
})
$(target).on('tap', function () {
console.time();
console.log('tap');
console.timeEnd();
})
<script>
百度touchjs
基本事件:
touchstart //手指刚接触屏幕时触发
touchmove //手指在屏幕上移动时触发
touchend //手指从屏幕上移开时触发
touchcancel //触摸过程被系统取消时触发(少用)
事件绑定
touch.on( element, types, callback );
功能描述:事件绑定方法,根据参数区分事件绑定和事件代理。
参数描述:
| 参数 | 类型 | 描述 |
|---|---|---|
| element | element或string | 元素对象、选择器 |
| types | string | 事件的类型(多为手势事件),可接受多个事件以空格分开;支持原生事件的透传 |
| callback | function | 事件处理函数, 移除函数与绑定函数必须为同一引用; |
手势事件
| 分类 | 参数 | 描述 |
|---|---|---|
| 缩放 | pinchstart | 缩放手势起点 |
| pinchend | 缩放手势终点 | |
| pinch | 缩放手势 | |
| pinchin | 收缩 | |
| pinchout | 放大 | |
| 旋转 | rotateleft | 向左旋转 |
| rotateright | 向右旋转 | |
| rotate | 旋转 | |
| 滑动 | swipestart | 滑动手势起点 |
| swiping | 滑动中 | |
| swipeend | 滑动手势终点 | |
| swipeleft | 向左滑动 | |
| swiperight | 向右滑动 | |
| swipeup | 向上滑动 | |
| swipedown | 向下滑动 | |
| swipe | 滑动 | |
| 拖动开始 | dragstart | 拖动屏幕 |
| 拖动 | drag | 拖动手势 |
| 拖动结束 | dragend | 拖动屏幕 |
| 拖动 | drag | 拖动手势 |
| 长按 | hold | 长按屏幕 |
| 敲击 | tap | 单击屏幕 |
| 双击屏幕 | doubletap |
事件处理函数回调event对象属性
| 属性 | 描述 |
|---|---|
| originEvent | 触发某事件的原生对象 |
| type | 事件的名称 |
| rotation | 旋转角度 |
| scale | 缩放比例 |
| direction | 操作的方向属性 |
| fingersCount | 操作的手势数量 |
| position | 相关位置信息, 不同的操作产生不同的位置信息 |
| distance | swipe类两点之间的位移 |
| distanceX, x | 手势事件x方向的位移值, 向左移动时为负数 |
| distanceY, y | 手势事件y方向的位移值, 向上移动时为负数 |
| angle | rotate事件触发时旋转的角度 |
| duration | touchstart 与 touchend之间的时间戳 |
| factor | swipe事件加速度因子 |
| startRotate | 启动单指旋转方法,在某个元素的touchstart触发时调用 |
iScroll
iScroll是一个高性能,资源占用少,无依赖,多平台的javascript滚动插件。
它可以在桌面,移动设备和智能电视平台上工作。它一直在大力优化性能和文件大小以便在新旧设备上提供最顺畅的体验。
iScroll不仅仅是 滚动。它可以处理任何需要与用户进行移动交互的元素。在你的项目中包含仅仅4kb大小的iScroll,你的项目便拥有了滚动,缩放,平移,无限滚动,视差滚动,旋转功能。给它一个扫帚它甚至能帮你打扫办公室。
地址:
https://github.com/cubiq/iscroll
http://caibaojian.com/iscroll-5/
文件地址:
https://github.com/cubiq/iscroll/blob/master/build/iscroll.js
<div id="wrapper">
<ul>
<li>...</li>
<li>...</li>
...
</ul>
</div>
<script type="text/javascript">
var myScroll = new IScroll('#wrapper');
</script>
移动端初始样式
normalize.css
/*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css */
/* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in iOS.
*/
html {
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers.
*/
body {
margin: 0;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/* Grouping content
========================================================================== */
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
pre {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/* Text-level semantics
========================================================================== */
/**
* Remove the gray background on active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* 1. Remove the bottom border in Chrome 57-
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Remove the border on images inside links in IE 10.
*/
img {
border-style: none;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers.
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input { /* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select { /* 1 */
text-transform: none;
}
/**
* Correct the inability to style clickable types in iOS and Safari.
*/
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
vertical-align: baseline;
}
/**
* Remove the default vertical scrollbar in IE 10+.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10.
* 2. Remove the padding in IE 10.
*/
[type="checkbox"],
[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Remove the inner padding in Chrome and Safari on macOS.
*/
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in Edge, IE 10+, and Firefox.
*/
details {
display: block;
}
/*
* Add the correct display in all browsers.
*/
summary {
display: list-item;
}
/* Misc
========================================================================== */
/**
* Add the correct display in IE 10+.
*/
template {
display: none;
}
/**
* Add the correct display in IE 10.
*/
[hidden] {
display: none;
}
reset-mobile.css
* {
-webkit-tap-highlight-color: transparent;
outline: 0;
}
body,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
p,
blockquote,
dl,
dt,
dd,
ul,
ol,
li,
pre,
form,
fieldset,
legend,
button,
input,
textarea,
th,
td {
margin: 0;
padding: 0;
vertical-align: baseline;
}
img {
border: 0 none;
vertical-align: top;
}
i,
em {
font-style: normal;
}
ol,
ul {
list-style: none;
}
input,
select,
button,
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: 100%;
font-family: inherit;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
a,
a:visited {
text-decoration: none;
color: #333;
}
body {
margin: 0 auto;
background: #e8e8ed;
font-size: 14px;
font-family: -apple-system, Helvetica, sans-serif;
line-height: 1.5;
color: #666;
-webkit-text-size-adjust: 100% !important;
/*-webkit-user-select: none;
user-select: none;*/
}
input[type="text"] {
line-height:normal;
}
案例
产品签约

一般移动端页面我们会用一个div包裹整个页面,一般命名container,,然后在里面再进行布局,里面的布局尽量用语义化标签,除非用框架之类。
一般包裹的container有个基础的样式,以下样式是让页面完全定着,后期防止有滑动的情况
.wj-container {
position: absolute;
top:0;
left:0;
}
箭头一般用i标签或span标签,不能用伪元素,因为要点击返回。
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="css/reset-mobile.css">
<link rel="stylesheet" href="css/case02.css">
<title>产品签约</title>
</head>
<body>
<div class="wj-container">
<header class="wj-title">
<span class="wj-title_back-arrow"><</span>
<h2 class="wj-title_name">产品签约</h2>
</header>
<article class="wj-list">
<div class="wj-list_title">
<span>产品名称</span>
<span>产品费率</span>
</div>
<div class="wj-list_content">
<div class="wj-item">
<div class="wj-item_left">
<img class="wj-item_icon" src="images/weiXin.png" alt="">
<span class="wj-item__title">微信渠道</span>
</div>
<span class="wj-item_rate">3.33%</span>
</div>
<div class="wj-item">
<div class="wj-item_left">
<img class="wj-item_icon" src="images/zhiFuBao.png" alt="">
<span class="wj-item_title">支付宝</span>
</div>
<span class="wj-item_rate">7.33%</span>
</div>
<div class="wj-item">
<div class="wj-item_left">
<img class="wj-item_icon" src="images/qq.png" alt="">
<span class="wj-item_title">QQ支付</span>
</div>
<span class="wj-item_rate">8.33%</span>
</div>
</div>
</article>
</div>
<div>
<p class="hit hit-color "></p>
</div>
</body>
</html>
vw_design = 750 //设计图宽度
vw(px)
px / vw_design * 100vw;
.wj-container {
position: absolute;
top:0;
left:0;
}
.wj-title {
display:flex;
justify-content: center;
text-align: center;
width: 100%;
height: vw(90);
background-color #fff
}
.wj-title_back-arrow {
position: absolute;
left: vw(28);
line-height: vw(86);
font-size: 16px;
font-weight: 900;
font-family: '微软雅黑';
}
.wj-title_name {
width: 100vw;
line-height: vw(86);
text-align: center;
font-size: vw(36);
color: #222;
font-weight:400;
}
.wj-list {
display:flex;
flex-direction: column;
justify-content: flex-start;
}
.wj-list_title {
box-sizing: border-box;
display:flex;
justify-content: space-around;
width 100%;
height: vw(87);
line-height: @height;//@height是一种简洁语法,会以刚才height的值作为line-height的值
font-size: vw(32);
color: #a0a0a0;
background-color #fafafa;
border:1px solid #eee;
}
.wj-list_content {
display:flex;
flex-direction: column;
justify-content: flex-start;
width: 100%;
background-color #fff;
}
.wj-item {
position relative;
display: flex;
justify-content: flex-start;
padding: vw(34) vw(30);
}
.wj-item:not(:last-child):after {
content: ''
position: absolute;
bottom:0;
width: vw(750 - 60);
height: 1px;
background-color: #e9e9e9;
}
.wj-item_left {
width: vw(350);
padding-left: vw(62)
padding-right:vw(70)
font-size: vw(32);
}
.wj-item_icon {
width: vw(42);
height: vw(42);
margin-right: vw(18);
}
.wj0item_title {
color: #333;
}
.wj-item_rate {
font-size: vw(36);
color: #e52e25;
}
图片占位
一般布局不会直接给img设置vw,而是会给他用一个容器包裹起来,设置vw,然后img 100%占有。考虑到图片有时候会需要加载处理,为了保证页面不会塌陷。使用padding-top占位,然后图片加载出来用定位。
需求:中间的图片没有加载出来的时候,页面内容不会塌陷

<!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">
<link rel="stylesheet" href="css/reset-mobile.css">
<link rel="stylesheet" href="css/case00-rem.css">
<title>vw 适配</title>
</head>
<body>
<div>
我是里面的文本
</div>
<section>
<img src="images/1.jpg" alt="">
</section>
<main>
</main>
</body>
</html>
vw_fontsize = 75
vw_design = 750
vw(px)
(px / vw_design) * 100vw
div
width: vw(430);
height: vw(280);
font-size: vw(28); //14px
border: 1px solid #222
section
position relative
margin: 0 auto;
width: vw(494);
background-color #222;
height: 0;
padding-top: vw(429/789 * 494)
// imgH / imgW * wrapW
img
position absolute
top 0
left 0
width 100%
height: auto
main
height: 40vh;
background-color #368
移动端下拉刷新

<!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, minimal-ui">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<link rel="stylesheet" href="css/reset-mobile.css">
<link rel="stylesheet" href="css/h5ui.min.css">
<title>pull down 下拉刷新</title>
<style>
.pd-page {
position: absolute;
width: 100%;
height: 100vh;
top: 0;
left: 0;
}
.pd-pocket {
width: 100%;
overflow: hidden;
position: absolute;
top: 0;
visibility: hidden;
padding-top: 30px;
}
</style>
</head>
<body ontouchstart>
<div class="pd-page">
<div class="pd-pocket">
<div class="h5ui-loading processing">
<i class="loading-processing_icon"></i>
<span>正在加载</span>
</div>
</div>
<div class="pd-content">
<ul>
</ul>
</div>
</div>
<script type="text/template" id="listTemp">
<ul>
{{each list}}
<li>
<a class="h5ui-list" href="javascript:void(0);">
<div class="h5ui-list_bd">{{$index}} {{$value}}</div>
<div class="h5ui-list_ft">
<i class="h5ui-list_ft_arrow"></i>
</div>
</a>
</li>
{{/each}}
</ul>
</script>
<script src="js/template-web.js"></script>
<script src="js/zepto.min.js"></script>
<script>
const $content = $('.pd-content');
let list = new Array(30);
list = list.fill(0).map(item => `随机内容为:${~~(Math.random() * 40)}`);
$content.html(template('listTemp', { list }));
/*
下拉刷新
手指在content区域进行向下滑动拖拽(touchstart touchmove touchend)的时候
让content 随着随着手指滑动的距离向下偏移,同时让pocket取消visibility:hidden
抬起手指 touchend的时候 图标显示 loading 旋转
刷新加载结束之后 pocket 区域设置为visibility:hidden 同时让content区域上偏移为0
*/
function PullDown({ pocketEle, contentEle, callback }) {
this.$pocket = pocketEle || $('.pd-pocket');
this.$content = contentEle || $('.pd-content');
this.startPosY = 0;
this.startPosX = 0;
this.isPulling = false; //是否下拉中
this.isLoading = false; //是否加载中
this.callback = callback || function () { return false; }
this.maxDown = 120;
this.eventInt();
}
PullDown.prototype.eventInt = function () {
$(this.$content).on('touchstart touchmove touchend', this.eventAgent.bind(this));
}
PullDown.prototype.eventAgent = function (ev) {
let evType = ev.type;
if (evType in this.__proto__) {//if (this.__proto__[evType]) ,不这么判断,如果evType函数的结果是false,undifined,就是判断失误
this[evType].bind(this)(ev);
}
}
PullDown.prototype.touchstart = function (ev) {
//如果正在加载中 停止行为
if (this.isLoading === true) {
this.isPulling = false;
return false;
}
//如果正在拖拽中 停止重复拖拽行为
if (this.isPulling === true) {
return false;
}
this.startPosY = ev.targetTouches[0].pageY;
this.startPosX = ev.targetTouches[0].pageX;
this.isPulling = true;
}
PullDown.prototype.touchmove = function (ev) {
//手指移动量
let diffY = ev.targetTouches[0].pageY - this.startPosY;
let diffX = ev.targetTouches[0].pageX - this.startPosX;
//如果已经有一次下拉行为没有结束的话 终止拖拽
if (this.isPulling === false) {
return false;
}
//如果正在加载中 禁止拖拽
if (this.isLoading === true) {
return false;
}
//当垂直滑动距离 大于水平滑动距离的一半的时候 认为是上下运动
if ((Math.abs(diffY) - Math.abs(diffX)) < Math.abs(diffX) / 2) {
return false;
}
//判断当前scrollTop滚动高度是否是0
if (document.documentElement.scrollTop !== 0) {
return false;
}
//当前Y点 - 初始Y点 如果>0的话 用户在下拉 反之 用户在上拉
if (diffY < 0) {
return false;
}
//当拉到最大限度的时候 触发刷新功能
if (diffY > this.maxDown) {
this.loading();
return false;
}
$(this.$content).css({
//用transform改变内容的位置,因为用margin和padding是实际位置的改变,transform改变的不是实际位置是渲染位置,性能更好
//transform在水平滑动或竖直滑动的时候,最好是把translateZ(0)永远固定为0,早期在一些真机上有一些性能上的缺陷,会有小范围的抖动
//所以要动态修改translateX/Y的时候,一定要永远保证translateZ为0,固定式写法
transform: `translate(0,${diffY}px) translateZ(0)`
})
}
PullDown.prototype.touchend = function (ev) {
//如果正在加载中 终止行为
if (this.isLoading === true) {
return false;
}
if (this.isPulling === false) {
return false;
}
this.isPulling = false; //释放拖拽中开关
//松开手指 自动回弹
this.backTop();
}
PullDown.prototype.backTop = function () {
$(this.$content).css({
transition: '.3s',
transform: `translate(0,0) translateZ(0)`
})
}
PullDown.prototype.loading = function () {
$('body').css({
overflow: 'hidden'
});
this.isLoading = true; //正在加载中
this.callback(this.loadEnd.bind(this));
$(this.$pocket).css({
visibility: "visible"
})
}
PullDown.prototype.loadEnd = function (msg) {
$('body').css({
overflow: 'auto'
});
this.isLoading = false; //二次回调=>加载结束
this.isPulling = false;
//加载完成 回弹
this.backTop();
}
let pullDown = new PullDown({
callback(cb) {
console.log('加载中');
//模拟ajax异步
setTimeout(function () {
list = list.fill(0).map(item => `随机内容为:${~~(Math.random() * 40)}`);
$content.html(template('listTemp', { list }));
//数据加载完成 二次回调告知pullDown模块加载完成
cb && cb('加载完成1');
}, 1500);
}
});
</script>
</body>
</html>
京东图片案例


html
<!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">
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/reset-mobile.css">
<link rel="stylesheet" href="css/index.css">
<link rel="stylesheet" href="swiper/swiper-bundle.min.css">
<title>京东商城--手机版</title>
</head>
<body>
<div class="jd-page">
<header class="jd-head">
<div class="jd-search">
<a href="#">
<h1 class="jd-search_logo">京东商城</h1>
</a>
<div class="jd-search_input">
<i class="jd-icon_search"></i>
<input type="text" placeholder="搜索">
</div>
<a class="jd-search_login" href="javascript:;">登录</a>
</div>
<div class="jd-banner">
<div class="swiper-container">
<div class="swiper-wrapper">
<div class="swiper-slide">
<img src="images/jd/l1.jpg" alt="">
</div>
<div class="swiper-slide">
<img src="images/jd/l2.jpg" alt="">
</div>
<div class="swiper-slide">
<img src="images/jd/l3.jpg" alt="">
</div>
</div>
<!-- 如果需要分页器 -->
<div class="swiper-pagination"></div>
</div>
</div>
<nav class="jd-nav">
<a class="jd-nav_item" href="javascript:;"><img class="jd-nav_icon" src="images/jd/nav1.png" alt=""><span
class="jd-nav_title">四字导航</span></a>
<a class="jd-nav_item" href="javascript:;"><img class="jd-nav_icon" src="images/jd/nav2.png" alt=""><span
class="jd-nav_title">四字导航</span></a>
<a class="jd-nav_item" href="javascript:;"><img class="jd-nav_icon" src="images/jd/nav3.png" alt=""><span
class="jd-nav_title">四字导航</span></a>
<a class="jd-nav_item" href="javascript:;"><img class="jd-nav_icon" src="images/jd/nav4.png" alt=""><span
class="jd-nav_title">四字导航</span></a>
<a class="jd-nav_item" href="javascript:;"><img class="jd-nav_icon" src="images/jd/nav5.png" alt=""><span
class="jd-nav_title">四字导航</span></a>
<a class="jd-nav_item" href="javascript:;"><img class="jd-nav_icon" src="images/jd/nav6.png" alt=""><span
class="jd-nav_title">四字导航</span></a>
<a class="jd-nav_item" href="javascript:;"><img class="jd-nav_icon" src="images/jd/nav7.png" alt=""><span
class="jd-nav_title">四字导航</span></a>
<a class="jd-nav_item" href="javascript:;"><img class="jd-nav_icon" src="images/jd/nav0.png" alt=""><span
class="jd-nav_title">四字导航</span></a>
</nav>
</header>
<main class="jd-main">
<section class="jd-product">
<div class="jd-title-more">
<div class="jd-title-more_left">
<i class="jd-title-more_icon"></i>
<span class="jd-title-more_name">掌上秒杀</span>
<div class="jd-time-block">
<span class="jd-time-block_item">0</span>
<span class="jd-time-block_item ">0</span>
<span class="jd-time-block_colon">:</span>
<span class="jd-time-block_item">0</span>
<span class="jd-time-block_item">0</span>
<span class="jd-time-block_colon">:</span>
<span class="jd-time-block_item">0</span>
<span class="jd-time-block_item">0</span>
</div>
</div>
<span class="jd-title-more_jump">更多 ></span>
</div>
<div class="jd-scroll-list">
<a href="javascript:;">
<div class="jd-scroll-list_item">
<img class="jd-scroll-list_pic" src="images/jd/detail01.jpg" alt="">
<p class="jd-scroll-list_price">¥10</p>
<del class="jd-scroll-list_del-price">¥100</del>
</div>
</a>
<a href="javascript:;">
<div class="jd-scroll-list_item">
<img class="jd-scroll-list_pic" src="images/jd/detail01.jpg" alt="">
<p class="jd-scroll-list_price">¥10</p>
<del class="jd-scroll-list_del-price">¥100</del>
</div>
</a>
<a href="javascript:;">
<div class="jd-scroll-list_item">
<img class="jd-scroll-list_pic" src="images/jd/detail01.jpg" alt="">
<p class="jd-scroll-list_price">¥10</p>
<del class="jd-scroll-list_del-price">¥100</del>
</div>
</a>
</div>
</section>
<section class="jd-product">
<h3 class="jd-product_title">京东自营</h3>
<div class="jd-product_content">
<div class="jd-product_item border-right">
<a href="javascript:;">
<img src="images/jd/cp1.jpg" alt="">
</a>
</div>
<div class="jd-product_item">
<div class="jd-product_item--top">
<a href="javascript:;">
<img src="images/jd/cp2.jpg" alt="">
</a>
</div>
<div class="jd-product_item--bottom">
<a href="javascript:;">
<img src="images/jd/cp3.jpg" alt="">
</a>
</div>
</div>
</div>
</section>
<section class="jd-product">
<h3 class="jd-product_title">京东自营</h3>
<div class="jd-product_content">
<div class="jd-product_item border-right">
<div class="jd-product_item--top">
<a href="javascript:;">
<img src="images/jd/cp2.jpg" alt="">
</a>
</div>
<div class="jd-product_item--bottom">
<a href="javascript:;">
<img src="images/jd/cp3.jpg" alt="">
</a>
</div>
</div>
<div class="jd-product_item ">
<a href="javascript:;">
<img src="images/jd/cp1.jpg" alt="">
</a>
</div>
</div>
</section>
</main>
</div>
<script src="js/zepto.min.js"></script>
<script src="swiper/swiper-bundle.min.js"></script>
<script>
new Swiper('.swiper-container', {
loop: true, // 循环模式选项
// 如果需要分页器
pagination: {
el: '.swiper-pagination',
}
})
</script>
</body>
</html>
css
/* 变量 */
/* tool */
/* gloab */
a {
text-decoration: none;
color: #222;
}
h1 {
font-size: 0;
}
img {
display: block;
outline: 0;
border: 0;
}
.jd-page {
overflow: hidden;
position: absolute;
top: 0;
left: 0;
width: 100%;
}
[class*="-icon"],
[class*="_logo"] {
background: url("../images/jd/sprites.png") no-repeat;
background-size: 200px 200px;
}
/* swiper hack */
.jd-page .swiper-pagination-bullet {
border-color: #fff;
background-color: #fff;
opacity: 1;
}
.jd-page .swiper-pagination-bullet-active {
background-color: #ffa500;
}
.jd-icon_search {
position: absolute;
left: 8px;
width: 5.333333333333334vw;
height: 5.333333333333334vw;
background-position: -60px -109px;
}
/* header */
.jd-head {
width: 100%;
display: flex;
flex-direction: column;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: center;
}
.jd-search {
z-index: 999;
box-sizing: border-box;
position: fixed;
width: 100%;
padding: 1.333333333333333vw 1.333333333333333vw;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
align-items: center;
background-color: rgba(122,0,0,0.3);
}
.jd-search_logo {
width: 16vw;
height: 9.6vw;
background-position: 0 -103px;
}
.jd-search_input {
position: relative;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: center;
}
.jd-search_input > input {
width: 69.33333333333334vw;
height: 8vw;
line-height: 8vw;
border: 0;
outline: 0;
border-radius: 8.266666666666666vw;
}
.jd-search_input ::-webkit-input-placeholder {
text-indent: 2em;
color: ccc;
}
.jd-search_input ::-moz-placeholder {
text-indent: 2em;
color: ccc;
}
.jd-search_input :-ms-input-placeholder {
text-indent: 2em;
color: ccc;
}
.jd-search_input ::-ms-input-placeholder {
text-indent: 2em;
color: ccc;
}
.jd-search_input ::placeholder {
text-indent: 2em;
color: ccc;
}
.jd-search_login {
color: #fff;
}
/* banner */
.jd-banner {
width: 100%;
}
.jd-banner img {
width: 100%;
}
/* nav */
.jd-nav {
box-sizing: border-box;
width: 100%;
height: 40vw;
padding: 2.666666666666667vw 0;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-around;
align-items: space-between;
align-content: space-between;
background-color: #fff;
box-shadow: 0 -1px 2px #ccc;
}
.jd-nav_item {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: center;
width: 24.8vw;
height: 17.599999999999998vw;
text-align: center;
}
.jd-nav_icon {
width: 10.666666666666668vw;
height: 10.666666666666668vw;
}
.jd-nav_title {
padding-top: 4px;
font-size: 14px;
}
/* main */
.jd-main {
position: relative;
box-sizing: border-box;
padding: 0 10px;
}
.jd-product {
box-sizing: border-box;
width: 100%;
margin-top: 10px;
background-color: #fff;
box-shadow: 0 0 1px #ccc;
}
.jd-title-more {
box-sizing: border-box;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
align-items: center;
width: 100%;
height: 9.066666666666666vw;
padding: 0 10px;
}
.jd-title-more_left {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: center;
}
.jd-title-more_icon {
width: 4.266666666666667vw;
height: 5.333333333333334vw;
background: url("../images/jd/seckill-icon.png") no-repeat center;
vertical-align: middle;
background-size: 4.266666666666667vw 5.333333333333334vw;
}
.jd-title-more_name {
font-size: 14px;
padding: 0 2.666666666666667vw;
color: #d8505c;
}
.jd-time-block {
width: 32vw;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-around;
align-items: center;
font-size: 14px;
}
.jd-time-block_item {
padding: 1px 4px;
background-color: #000;
color: #fff;
}
.jd-time-block_colon {
max-width: 4px;
font-size: 8px;
white-space: 0px;
letter-spacing: 0;
}
.jd-title-more_jump {
font-size: 14px;
color: #222;
}
.jd-scroll-list {
padding: 10px 0;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-around;
align-items: center;
}
.jd-scroll-list_item {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
justify-content: space-between;
align-items: center;
padding: 0 5.333333333333334vw;
font-size: 14px;
}
.jd-scroll-list_pic {
width: 100%;
}
.jd-scroll-list_price {
color: #d8505c;
}
.jd-scroll-list_del-price {
line-height: 14px;
}
.jd-product_title {
position: relative;
box-sizing: border-box;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: center;
padding: 4px 8px;
width: 100%;
font-weight: 400;
font-size: 16px;
}
.jd-product_title:before {
content: '';
width: 4px;
height: 16px;
margin-right: 8px;
background-color: #d8505c;
}
.jd-product_title:after {
position: absolute;
bottom: 0;
left: 0;
content: '';
width: 100%;
height: 1px;
transform: scaleY(0.5);
background-color: #ccc;
}
.jd-product_content {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: flex-start;
}
.jd-product_item {
position: relative;
width: 50%;
flex: 1;
display: flex;
flex-direction: column;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: center;
}
.jd-product_content:after {
position: absolute;
content: '';
width: 1px;
height: 100%;
background-color: #ccc;
}
.jd-product_item img {
width: 100%;
}
.jd-product_item--top {
border-bottom: 1px solid #ccc;
}
.jd-product_item.border-right {
border-right: 1px solid #ccc;
}
注:UI要和业务分开,业务(用户的操作,浏览方向、轮播图、特效等)要和核心逻辑(日期方法)分开
数据和业务分离、业务和逻辑分离。


2191

被折叠的 条评论
为什么被折叠?



