一、基础知识
1、屏幕
移动设备与PC设备最大的差异在于屏幕,这主要体现在屏幕尺寸和屏幕分辨率两个方面。
通常我们所指的屏幕尺寸,实际上指的是屏幕对角线的长度(一般用英寸来度量)。
而分辨率则一般用像素来度量 px,表示屏幕水平和垂直方向的像素数,例如 1920*1080 指的是屏幕垂直方向和水平方向分别有1920和1080个像素点而构成。
2、长度单位
在Web开发中可以使用px(像素)、em、pt(点)、in(英寸)、cm(厘米)做为长度单位,我们最常用px(像素)做为长度单位。
我们可以将上述的几种长度单位划分成相对长度单位和绝对长度单位。
例如:iPhone3G/S和iPhone4/S的屏幕尺寸都为 3.5 英寸(in)但是屏幕分辨率却分别为 480x320px、960x480px,由此我们可以得出英寸是一个绝对长度单位,而像素是一个相对长度单位(像素并没有固定的长度)。
3、像素密度
DPI(Dots Per Inch)是印刷行业中用来表示打印机每英寸可以喷的墨汁点数,计算机显示设备从打印机中借鉴了DPI的概念,由于计算机显示设备中的最小单位不是墨汁点而是像素,所以用PPI(Pixels Per Inch)值来表示屏幕每英寸的像素数量,我们将PPI、DPI都称为像素密度,但PPI应用更广泛,DPI在Android设备比较常见。
利用屏幕分辨率计算 PPI :
4、设备独立像素
随着技术发展,设备不断更新,出现了不同PPI的屏幕共存的状态(如iPhone3G/S为163PPI,iPhone4/S为326PPI),像素不再是统一的度量单位,这会造成同样尺寸的图像在不同PPI设备上的显示大小不一样。
如下图,假设你设计了一个163x163的蓝色方块,在PPI为163的屏幕上,那这个方块看起来正好就是1x1寸大小,在PPI为326的屏幕上,这个方块看起来就只有0.5x0.5寸大小了。
但是做为用户是不会关心这些细节的,他们只是希望在不同PPI的设备上看到的图像内容差不多大小,所以这时我们需要一个新的单位,这个新的单位能够保证图像内容在不同的PPI设备看上去大小应该差不多,这就是独立像素,在IOS设备上叫PT(Point),Android设备上叫DIP(Device independent Pixel)或DP。
举例说明就是iPhone 3G(PPI为163)1pt = 1px,iPhone 4(PPI为326)1pt = 2px。
通过上面例子我们不难发现 pt 同px是有一个对应(比例)关系的,这个对应(比例)关系是操作系统确定并处理,目的是确保不同PPI屏幕所能显示的图像大小是一致的,通过 window.devicePixelRatio 可以获得该比例值。
所以,我们如何处理在不同 pt/px 比例上使得显示相同大小的图片呢?
很简单,在美工设计图片的时候,多设计几种尺寸的图片。
5、像素
5.1、物理像素
物理像素指的是屏幕渲染图像的最小单位,属于屏幕的物理属性,不可人为进行改变,其值大小决定了屏幕渲染图像的品质,我们以上所讨论的都指的是物理像素。
获取屏幕的物理像素尺寸:
window.screen.width;
window.screen.height;
5.2、CSS像素
CSS像素,与设备无关像素,指的是通过CSS进行网页布局时用到的单位,其默认值(PC端)是和物理像素保持一致的(1个单位的CSS像素等于1个单位的物理像素),但是我们可通缩放来改变CSS像素的大小。
我们需要理解的是物理像素和CSS像素的一个关系,1个物理像素并不总是等于一个CSS像素,通过缩放,一个CSS像素可能大于1个物理像素,也可能小于1个物理像素。
二、调试
1、模拟调试
现代主流浏览器均支持移动开发模拟调试,通常按F12可以调起,其使用也比较简单,可以帮我们方便快捷定位问题。
2、真机调试
模拟调试可以满足大部分的开发调试任务,但是由于移动设备种类繁多,环境也十分复杂,模拟调试容易出现差错,所以真机调试变的非常必要。
有两种方法可以实现真机调试:
1、将做好的网页上传至服务器或者本地搭建服务器,然后移动设备通过网络来访问。(重点)
2、借助第三方的调试工具,如weinre、debuggap、ghostlab(推荐) 等。
真机调试必须保证移动设备同服务器间的网络是相通的。
三、视口
视口(viewport)是用来约束网站中最顶级块元素<html>的,即它决定了<html>的大小。
1、PC 设备
在PC设备上viewport的大小取决于浏览器窗口的大小,以CSS像素做为度量单位。
通过以往CSS的知识,我们都能理解<html>的大小是会影响到我们的网页布局的,而viewport又决定了<html>的大小,所以viewport间接的决定并影响了我们网页的布局。
/* 获取viewport的大小 */
document.documentElement.clientWidth;
document.documentElement.clientHeight;
在PC端,我们通过调整浏览器窗口可以改变 viewport 的大小,为了保证网页布局不发生错乱,需要给元素设定较大固定宽度。
2、移动设备
移动设备屏幕普遍都是比较小的,但是大部分的网站又都是为PC设备来设计的,要想让移动设备也可以正常显示网页,移动设备不得不做一些处理,通过上面的例子我们可以知道只要viewport足够大,就能保证原本为PC设备设计的网页也能在移动设备上正常显示,移动设备厂商也的确是这样来处理的。
在移动设备上viewport不再受限于浏览器的窗口,而是允许开发人员自由设置viewport的大小,通常浏览 器会设置一个默认大小的 viewport,为了能够正常显示那些专为PC设计的网页,一般这个值的大小会大于屏幕的尺寸。
如下图为常见默认viewport大小(仅供参考):
从图中统计我们得知不同的移动厂商分别设置了一个默认的viewport的值,这个值保证大部分网页可以正常在移动设备下浏览。
但是由于我们手机的屏幕很小,而 viewport 的值却很大,所以页面所有的内容就会缩小以适应屏幕,所以用手机看起来,这些字体和图片就会特别小,这就像手机设置里面有个电脑版显示一样。
要解释上面的原因,需要进一步对移动设备的 viewport 进行分析,移动设备上有2个viewport(为了方便讲解人为定义的),分别是 layout viewport 和 ideal viewport。
1、layout viewport(布局视口)指的是我们可以进行网页布局区域的大小,同样是以CSS像素做为计量单位,可以通过下面方式获取
/* 获取layout viewport */
document.documentElement.clientWidth;
document.documentElement.clientHeight;
通过前面介绍我们知道,如果要保证为PC设计的网页在移动设备上布局不发生错乱,移动设备会默认设置一个较大的viewport(如IOS为980px),这个viewport实际指的是layout viewport。
2、ideal viewport(理想视口)设备屏幕区域,(以设备独立像素PT、DP做为单位)以CSS像素做为计量单位,其大小是不可能被改变,通过下面方式可以获取。
/* 获取ideal viewport有两种情形 */
/* 新设备 */
window.screen.width;
window.screen.height;
/* 老设备 */
window.screen.width / window.devicePixelRatio;
window.screen.height / window.devicePixelRatio;
理解两个viewport后我们来解释为什么网页会被缩放或出现水平滚动条:
其原因在于移动设备浏览器会默认设置一个layout viewport,并且这个值会大于ideal viewport,那么我们也知道ideal viewport就是屏幕区域,layout viewport是我们布局网页的区域,那么最终layout viewport是要显示在ideal viewport里的,而layout viewport大于ideal viewport时,于是就出现滚动条了,那么为什么有的移动设备网页内容被缩放了呢?移动设备厂商认为将网页完整显示给用户才最合理,而不该出现滚动条,所以就将layout viewport进行了缩放,使其恰好完整显示在ideal viewport(屏幕)里,其缩放比例为ideal viewport / layout viewport。
3、移动浏览器
移动端开发主要是针对IOS和Android两个操作系统平台的,除此之外还有Windows Phone。
移动端主要可以分成三大类,系统自带浏览器、应用内置浏览器、第三方浏览器。
3.1、系统浏览器
指跟随移动设备操作系统一起安装的浏览器,一般不能卸载。比如 iPhone 的 safari 浏览器。
3.2、应用内置浏览器
通常在移动设备上都会安装一些APP例如 QQ、微信、微博、淘宝等,这些APP里往往会内置一个浏览器,我们称这个浏览器为应用内置浏览器(也叫WebView),这个内置的浏览器一般功能比较简单,并且客户端开发人员可以更改这个浏览器的某些设置。
3.3、第三方浏览器
指安装在手机的浏览器如FireFox、Chrome、360等等。
在IOS 和 Android 操作系统上自带浏览器、应用内置浏览器都是基于Webkit内核的。
四、屏幕适配
经过分析我们得到,移动页面最理想的状态是,避免滚动条且不被默认缩放处理,我们可以通过设置 <meta name="viewport" content="">来进行控制,并改变浏览器默认的layout viewport的宽度。
1、viewport 详解
viewport 是由苹果公司为了解决移动设备浏览器渲染页面而提出的解决方案,后来被其它移动设备厂商采纳,其使用参数如下:
通过设置属性 content="" 实现,中间以逗号分隔。
示例:
<meta name="viewport" content="width=device-width, initital-scale=1.0, user-scalable=no">
width:设置 layout viewport 宽度,其取值可为数值或者device-width。
height:设置layout viewport 高度,其取值可为数值或者device-height
initital-scale:设置页面的初始缩放值,为一个数字,可以带小数。
maximum-scale:允许用户的最大缩放值,为一个数字,可以带小数。
minimum-scale:允许用户的最小缩放值,为一个数字,可以带小数。
user-scalable:是否允许用户进行缩放,值为"no"(不能缩放)或"yes"(可以缩放)。
注:device-width 和 device-height 就是 ideal viewport 的宽和高。
2、控制缩放
设置 <meta name="viewport" content="initial-scale=1">,这时我们发现网页没有被浏览器设置缩放。
设置 <meta name="viewport" content="width=device-width">,这时我们发现网页也没有被浏览器设设置缩放。
当我们设置 width=device-width,也达到了 initial-scale=1 的效果,得知其实 initial-scale = ideal viewport / layout viewport。
两种方式都可以控制缩放,开发中一般同时设置 width=device-width 和 initial-scale=1.0(为了解决一些兼容问题)参见 移动前端开发之viewport深入理解 (www.cnblogs.com/2050/p/3877…
<meta name="viewport" content="width=device-width, initial-scale=1.0">
3、适配方案
关于 em 和 rem
em 是相对长度单位(参照父元素),其参照当前元素字号大小,如果当前元素未设置字号则会继承其祖先元素字号大小。
例如: .box {font-size: 16px;} 则 1em = 16px
.box {font-size: 32px;} 则 1em = 32px,0.5em = 16px
rem 相对长度单位(参照 html 元素),其参照根元素(html)字号大小。
例如 :html {font-size: 16px;} 则 1rem = 16px
html {font-size: 32px;} 则 1rem = 32px,0.5rem = 16px.
vw:viewport width,视口宽度 (1vw = 1%视口宽度)
vh:viewport height 视口高度 (1vh = 1%视口高度)
4、移动端终极适配方案
网易淘宝适配方案:www.manster.me/?p=311
三、案例:JD移动端网页
相关源码已放置 Github:github.com/Daotin/Web/…
html 源码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" href="./css/base.css">
<link rel="stylesheet" href="./css/index.css">
<script src="./js/index.js"></script>
</head>
<body>
<div class="jd">
<!-- 搜索栏开始 -->
<div class="search">
<a href="javascript:;" class="search-logo"></a>
<form action="" class="search-text">
<input type="text" placeholder="请输入商品名称">
</form>
<a href="javascript:;" class="search-login">登录</a>
</div>
<!-- 搜索栏结束 -->
<!-- 轮播图开始 -->
<div class="slideshow">
<ul class="slideshow-img clearfix">
<li>
<a href="javascript:;">
<img src="./uploads/l1.jpg" alt="">
</a>
</li>
<li>
<a href="javascript:;">
<img src="./uploads/l2.jpg" alt="">
</a>
</li>
<li>
<a href="javascript:;">
<img src="./uploads/l3.jpg" alt="">
</a>
</li>
<li>
<a href="javascript:;">
<img src="./uploads/l4.jpg" alt="">
</a>
</li>
<li>
<a href="javascript:;">
<img src="./uploads/l5.jpg" alt="">
</a>
</li>
<li>
<a href="javascript:;">
<img src="./uploads/l6.jpg" alt="">
</a>
</li>
<li>
<a href="javascript:;">
<img src="./uploads/l7.jpg" alt="">
</a>
</li>
<li>
<a href="javascript:;">
<img src="./uploads/l8.jpg" alt="">
</a>
</li>
</ul>
<ul class="slideshow-dot">
<li class="select"></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</div>
<!-- 轮播图结束 -->
<!-- 导航栏开始 -->
<div class="nav">
<ul class="nav-ul clearfix">
<li>
<a href="javascript:;">
<img src="./uploads/nav0.png">
</a>
<p>商品分类</p>
</li>
<li>
<a href="javascript:;">
<img src="./uploads/nav1.png">
</a>
<p>商品分类</p>
</li>
<li>
<a href="javascript:;">
<img src="./uploads/nav2.png">
</a>
<p>商品分类</p>
</li>
<li>
<a href="javascript:;">
<img src="./uploads/nav3.png">
</a>
<p>商品分类</p>
</li>
<li>
<a href="javascript:;">
<img src="./uploads/nav4.png">
</a>
<p>商品分类</p>
</li>
<li>
<a href="javascript:;">
<img src="./uploads/nav5.png">
</a>
<p>商品分类</p>
</li>
<li>
<a href="javascript:;">
<img src="./uploads/nav6.png">
</a>
<p>商品分类</p>
</li>
<li>
<a href="javascript:;">
<img src="./uploads/nav7.png">
</a>
<p>商品分类</p>
</li>
</ul>
</div>
<!-- 导航栏结束 -->
<!-- 主体内容开始 -->
<div class="content">
<div class="content-box clearfix content-box-sk">
<div class="content-title">
<span class="content-title-left-clock"></span>
<span class="content-title-left-text fl">掌上秒杀</span>
<div class="content-title-left-time fl">
<span>0</span>
<span>0</span>
<span>:</span>
<span>0</span>
<span>0</span>
<span>:</span>
<span>0</span>
<span>0</span>
</div>
<span class="content-title-right fr">更多秒杀...</span>
</div>
<lu class="content-ul clearfix">
<li>
<a href="javascript:;">
<img src="./uploads/detail01.jpg" alt="" class="br">
</a>
<p>¥10.00</p>
<p class="content-ul-delete">¥20.00</p>
</li>
<li>
<a href="javascript:;">
<img src="./uploads/detail01.jpg" alt="" class="br">
</a>
<p>¥10.00</p>
<p class="content-ul-delete">¥20.00</p>
</li>
<li>
<a href="javascript:;">
<img src="./uploads/detail01.jpg" alt="">
</a>
<p>¥10.00</p>
<p class="content-ul-delete">¥20.00</p>
</li>
</lu>
</div>
<div class="content-box clearfix">
<div class="content-title">
<h3>京东超市</h3>
</div>
<lu class="content-ul">
<li class="fl">
<a href="javascript:;">
<img src="./uploads/cp1.jpg" alt="">
</a>
</li>
<li class="fl bl bb">
<a href="javascript:;">
<img src="./uploads/cp2.jpg" alt="">
</a>
</li>
<li class="fl bl">
<a href="javascript:;">
<img src="./uploads/cp3.jpg" alt="">
</a>
</li>
</lu>
</div>
<div class="content-box clearfix">
<div class="content-title">
<h3>京东超市</h3>
</div>
<lu class="content-ul">
<li class="fr">
<a href="javascript:;">
<img src="./uploads/cp4.jpg" alt="">
</a>
</li>
<li class="fl bl bb">
<a href="javascript:;">
<img src="./uploads/cp5.jpg" alt="">
</a>
</li>
<li class="fl bl">
<a href="javascript:;">
<img src="./uploads/cp6.jpg" alt="">
</a>
</li>
</lu>
</div>
</div>
<!-- 主体内容结束 -->
</div>
</body>
</html>
CSS 源码:
CSS 初始化代码:
@charset "UTF-8";
/*css 初始化 */
html, body, ul, li, ol, dl, dd, dt, p, span, h1, h2, h3, h4, h5, h6, form, fieldset, legend, img, input, div {
margin: 0;
padding: 0;
/* 盒模型 */
box-sizing: border-box;
/* 去掉移动端特有的点击高亮效果: transparent 透明色*/
-webkit-tap-highlight-color: transparent;
}
body {
width: 100%;
font: 12px/150% "Microsoft YaHei", sans-serif;
color: #666;
background: #fff
}
fieldset, img, input, button { /*fieldset组合表单中的相关元素*/
border: none;
padding: 0;
margin: 0;
outline-style: none; /*去除蓝色边框*/
}
ul, ol, li {
list-style: none; /*清除列表风格*/
}
a,
a:hover {
text-decoration: none;
}
a:active {
color: red;
}
select, input {
vertical-align: middle; /*图片文字垂直居中*/
}
select, input, textarea {
font-size: 12px;
margin: 0;
}
textarea {
resize: none; /*不能改变多行文本框的大小*/
}
img {
border: 0;
vertical-align: middle; /* 去掉图片低测默认的3像素空白缝隙*/
}
table {
border-collapse: collapse; /*合并外边线*/
}
.clearfix::before,
.clearfix::after {
content: "";
display: block;
height: 0;
line-height: 0;
clear: both;
visibility: hidden;
}
.clearfix {
*zoom: 1; /*IE/7/6*/
}
h1, h2, h3, h4, h5, h6 {
text-decoration: none;
font-weight: normal;
font-size: 100%;
}
s, i, em {
font-style: normal;
text-decoration: none;
}
/*公共类*/
.w {
/*版心 提取 */
width: 1225px;
margin: 0 auto;
}
.fl {
float: left
}
.fr {
float: right
}
.al {
text-align: left
}
.ac {
text-align: center
}
.ar {
text-align: right
}
.hide {
display: none
}
.bl {
border-left: 1px solid #ccc;
}
.br {
border-right: 1px solid #ccc;
}
.bb {
border-bottom: 1px solid #ccc;
}
CSS 主要代码:
.jd {
width: 100%;
/* height: 1000px; */
/* 最大宽度 */
max-width: 640px;
/* 最小宽度 */
min-width: 320px;
margin: 0 auto;
background-color: #eee;
}
/* 搜索栏开始 */
.search {
width: 100%;
max-width: 640px;
height: 40px;
background-color: rgba(233, 35, 34, 0);
position: fixed;
z-index: 10;
}
.search-logo {
width: 56px;
height: 20px;
background-image: url("../images/jd-sprites.png");
background-size: 200px 200px;
background-position: left -109px;
position: absolute;
left: 10px;
top: 10px;
}
.search-login {
color: #fff;
font-size: 14px;
line-height: 40px;
position: absolute;
right: 10px;
top: 0;
}
.search-login:visited {
color: #fff;
}
.search-text {
width: 100%;
height: 100%;
padding-left: 76px;
padding-right: 50px;
}
.search-text>input {
width: 100%;
height: 30px;
margin-top: 5px;
border-radius: 15px;
color: #666;
padding-left: 30px;
font-size: 14px;
}
.search-text::before {
content: "";
width: 19px;
height: 20px;
background-image: url("../images/jd-sprites.png");
background-size: 200px 200px;
background-position: -60px -109px;
position: absolute;
left: 83px;
top: 10px;
}
/* 搜索栏结束 */
/* 轮播图开始 */
.slideshow {
width: 100%;
overflow: hidden;
position: relative;
}
.slideshow-img {
width: 800%;
}
.slideshow-img>li {
width: 12.5%;
float: left;
}
.slideshow-img>li img {
width: 100%;
}
.slideshow-dot {
width: 80px;
height: 10px;
position: absolute;
left: 50%;
bottom: 0;
transform: translateX(-50%);
}
.slideshow-dot>li {
width: 6px;
height: 6px;
float: left;
border-radius: 50%;
border: 1px solid #fff;
margin: 0 2px;
}
.slideshow-dot>li.select {
background-color: #fff;
}
/* 轮播图结束 */
/* 导航栏开始 */
.nav {
width: 100%;
box-shadow: 0 2px 2px #ccc;
}
.nav-ul {
width: 100%;
background-color: #fff;
}
.nav-ul>li {
width: 25%;
float: left;
text-align: center;
margin-top: 10px;
}
.nav-ul>li img {
width: 60px;
height: 60px;
}
.nav-ul>li p {
margin: 5px 0;
}
/* 导航栏结束 */
/* 主体内容开始 */
.content {
width: 100%;
}
.content-box {
width: 100%;
margin-top: 10px;
background-color: #fff;
box-shadow: 0 2px 2px #ccc;
}
.content-title {
border-bottom: 1px solid #ccc;
position: relative;
}
.content-box>.content-title {
height: 30px;
line-height: 30px;
}
.content-title>h3 {
margin-left: 20px;
position: relative;
font-weight: 700;
}
.content-title>h3::before {
content: "";
width: 3px;
height: 10px;
background-color: #e92322;
position: absolute;
left: -8px;
top: 10px;
}
.content-ul {
width: 100%;
}
.content-ul>li {
width: 50%;
}
.content-ul>li img {
width: 100%;
display: block;
}
.content-title-left-clock {
display: block;
width: 16px;
height: 20px;
background-image: url("../images/jd-sprites.png");
background-size: 200px 200px;
background-position: -85px -111px;
position: absolute;
left: 3px;
top: 4px;
}
.content-title-left-text {
padding: 0 10px 0 25px;
color: #e92322;
}
.content-title-left-time>span {
display: inline-block;
width: 12px;
height: 18px;
line-height: 18px;
background-color: #363634;
color: #e5d790;
text-align: center;
font-weight: bold;
}
.content-title-left-time>span:nth-of-type(3n) {
background-color: transparent;
width: 3px;
}
.content-title-right {
margin-right: 10px;
}
.content-box-sk {
padding: 10px;
}
.content-box-sk>.content-title {
border-bottom: 0;
margin-bottom: 10px;
}
.content-box-sk .content-ul {
padding: 10px 0;
display: flex;
justify-content: space-around;
}
.content-box-sk .content-ul>li {
/* width: 33.33%; */
text-align: center;
}
.content-box-sk .content-ul>li img {
width: 100%;
display: inline-block;
padding: 0 30px;
margin-bottom: 5px;
}
.content-ul-delete {
text-decoration: line-through;
color: #aaa;
}
/* 主体内容结束 */
javaScript 代码:
window.onload = function () {
bannerEffect();
timeCount();
slideshowEffect();
};
// 搜索栏上下滚动时改变透明度
function bannerEffect() {
var bannerObj = document.querySelector(".search");
var slideshowObj = document.querySelector(".slideshow")
// 获取搜索栏的高度
var bannerHeight = bannerObj.offsetHeight; //40
// 获取轮播图高度
var slideshowHeight = slideshowObj.offsetHeight; //311
window.addEventListener("scroll", function () {
// 页面向上滚动的距离(兼容代码)
var scrolllen = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
if (scrolllen < (slideshowHeight - bannerHeight)) {
var setopacity = scrolllen / (slideshowHeight - bannerHeight);
bannerObj.style.backgroundColor = "rgba(233, 35, 34, " + setopacity + ")";
}
}, false);
}
// 主体内容秒杀栏目的倒计时
function timeCount() {
var spanObjs = document.querySelector(".content-title-left-time").children;
var titleCount = 2 * 60 * 60; // 2小时倒计时
var timeId = setInterval(function () {
titleCount--;
var hour = Math.floor(titleCount / 3600);
var minute = Math.floor((titleCount % 3600) / 60);
var second = titleCount % 60;
if (titleCount >= 0) {
// 下面的true实际想表达的是不执行任何操作,但是必须要写个语句,所以用true代替。
spanObjs[0].innerHTML == Math.floor(hour / 10) ? true : spanObjs[0].innerHTML = Math.floor(hour / 10);
spanObjs[1].innerHTML == hour % 10 ? (true) : spanObjs[1].innerHTML = hour % 10;
spanObjs[3].innerHTML == Math.floor(minute / 10) ? true : spanObjs[3].innerHTML = Math.floor(minute / 10);
spanObjs[4].innerHTML == minute % 10 ? true : spanObjs[4].innerHTML = minute % 10;
spanObjs[6].innerHTML == Math.floor(second / 10) ? true : spanObjs[6].innerHTML = Math.floor(second / 10);
spanObjs[7].innerHTML == second % 10 ? true : spanObjs[7].innerHTML = second % 10;
} else {
titleCount = 0;
clearInterval(timeId);
return;
}
}, 1000);
}
// 轮播图
function slideshowEffect() {
// 1. 自动轮播图
// 思路:1.1、使用js在图片开头动态添加原本最后一张图片
// 1.2、使用js在图片结尾动态添加原本第一张图片
// 获取轮播图
var slideshowObj = document.querySelector(".slideshow");
// 获取ul
var ulObj = document.querySelector(".slideshow-img");
// 获取所有li
var liObjs = ulObj.children;
// 设置li的索引值
var index = 1;
// 要添加的第一个li和最后一个li
var first = liObjs[0].cloneNode(true);
var last = liObjs[liObjs.length - 1].cloneNode(true);
// 在li开头结尾添加克隆图片
ulObj.appendChild(first);
ulObj.insertBefore(last, ulObj.firstElementChild);
// 设置ul宽度
ulObj.style.width = liObjs.length + "00%";
// 设置每个li的宽度
for (var i = 0; i < liObjs.length; i++) {
liObjs[i].style.width = slideshowObj.offsetWidth + "px";
}
// 默认显示第一张图
ulObj.style.transform = "translateX(" + -(slideshowObj.offsetWidth) + "px)"
// 改变窗口大小的时候自动调节轮播图的宽度
window.addEventListener("resize", function () {
ulObj.style.width = liObjs.length + "00%";
for (var i = 0; i < liObjs.length; i++) {
liObjs[i].style.width = slideshowObj.offsetWidth + "px";
}
// 改变窗口的大小的时候,不能仅仅回到第一张,要回到第index张
ulObj.style.transform = "translateX(" + -(slideshowObj.offsetWidth * index) + "px)"
}, false);
// 1. 实现自动轮播效果
var timerId;
var timerStart = function () {
timerId = setInterval(function () {
index++;
ulObj.style.transition = "transform 0.5s ease-in-out";
ulObj.style.transform = "translateX(" + -(slideshowObj.offsetWidth * index) + "px)";
// 设置小白点
if(index >= liObjs.length - 1) {
setDot(1);
return;
}
setDot(index);
// 由于过渡效果,使得过渡的时候,就进行if、判断,无法显示最后一张图片。
// 所以进行延时过渡的时候,等所有过渡效果完成后再进行判断是否到达最后一张。
setTimeout(function () {
if (index >= liObjs.length - 1) {
index = 1;
// 从最后一张调到第一张的时候,取消过渡效果
ulObj.style.transition = "none";
ulObj.style.transform = "translateX(" + -(slideshowObj.offsetWidth * index) + "px)";
}
}, 500);
}, 1500);
};
timerStart();
// 2. 轮播图的手动滑动效果
// 2.1、记录手指的起始位置
// 2.2、记录手指滑动时与起始位置水平轴的偏移距离
// 2.3、设置当手指松开后,判断偏移距离的大小,决定回弹还是翻页。
var startX, diffX;
// 设置节流阀,避免手动滑动过快,在过渡过程中也有滑动,造成的最后图片会有空白的操作,也就是index越界了,没有执行相应的 webkitTransitionEnd 事件。
var isEnd = true;
ulObj.addEventListener("touchstart", function (e) {
// 手指点击轮播图时,停止自动轮播效果
clearInterval(timerId);
startX = e.targetTouches[0].clientX;
}, false);
// 最开始的时候不触发,原因ul的高度为0
ulObj.addEventListener("touchmove", function (e) {
if (isEnd) {
// 手指移动的距离
diffX = e.targetTouches[0].clientX - startX;
// 关闭过渡效果,否则手指滑动困难
ulObj.style.transition = "none";
ulObj.style.transform = "translateX(" + -(slideshowObj.offsetWidth * index - diffX) + "px)";
}
}, false);
ulObj.addEventListener("touchend", function (e) {
isEnd = false;
// 判断当前滑动的距离是否超过一定的距离,则翻页
if (Math.abs(diffX) > 100) {
if (diffX > 0) {
index--;
} else {
index++;
}
// 翻页
ulObj.style.transition = "transform 0.5s ease-in-out";
ulObj.style.transform = "translateX(-" + slideshowObj.offsetWidth * index + "px)";
} else if (Math.abs(diffX) > 0) { // 回弹
ulObj.style.transition = "transform 0.5s ease-in-out";
ulObj.style.transform = "translateX(-" + slideshowObj.offsetWidth * index + "px)";
}
// 每次离开手指清除startX, diffX的值
startX = 0;
diffX = 0;
if(index == liObjs.length-1) {
setDot(1);
return;
} else if(index == 0) {
setDot(liObjs.length-2);
return;
}
setDot(index);
// 手指离开重新开启定时器
timerStart();
}, false);
// 我们发现在第一张往右滑动,或者最后一张往左滑动时,会造成空白
/*webkitTransitionEnd:可以监听当前元素的过渡效果执行完毕,当一个元素的过渡效果执行完毕的时候,会触发这个事件*/
ulObj.addEventListener("webkitTransitionEnd", function () {
// 如果到了第一张(index=0),让index=count-2
// 如果到了最后一张(index=count-1),让index=1;
if (index == 0) {
index = liObjs.length - 2;
// 从第一张到最后一张的时候,取消过渡效果
ulObj.style.transition = "none";
ulObj.style.transform = "translateX(" + -(slideshowObj.offsetWidth * index) + "px)";
} else if (index >= liObjs.length - 1) {
index = 1;
// 从最后一张调到第一张的时候,取消过渡效果
ulObj.style.transition = "none";
ulObj.style.transform = "translateX(" + -(slideshowObj.offsetWidth * index) + "px)";
}
// 设置过渡效果完成后,才可以继续滑动
setTimeout(function () {
isEnd = true;
}, 100);
}, false);
// 3. 设置轮播图小白点
function setDot(index) {
var dotliObjs = document.querySelector(".slideshow-dot").children;
// 建议不使用className,因为class属性可能有多个,使用dotliObjs[i].className = "";可能将其他的类样式一起清除。
for (var i = 0; i < dotliObjs.length; i++) {
// 清除所有select样式
dotliObjs[i].classList.remove("select");
}
// 设置当前索引的样式为select
dotliObjs[index-1].classList.add("select");
}
}
这里面的难点和重点就是轮播图部分:
1、思路:
要实现轮播图,必须在首尾添加图片,如果直接在 html 代码直接添加图片的话,由于图片的数量是不固定的,那么每次图片的数量发生改变的话,不仅需要设置 html 的源码,而且还要设置对应的 css 代码,所以,为了从后台获取的图片数量不固定的情况下,也能够实现轮播效果,我们可以使用 js 来动态的添加图片。
轮播图要首尾连接,关键是前后加图。
- 使用 js 动态的在最后的位置,添加原始第一张图片;在开始的位置,添加原始最后一张图片。
- 重新设置图片盒子的宽度和图片的宽度,并且在放大缩小视口大小的时候,自动改变宽度。
- 开启定时器,自动轮播
- 添加移动端滑动事件,手动轮播。
- 添加过渡效果结束事件,解决手动滑动到第一张和最后一张时,出现空白的问题。
- 设置小白点,在自动轮播和手动轮播两个地方添加。
2、在手动轮播的时候,一定记得将自动轮播时的过渡效果清除。还要关闭定时器,在手指离开的时候再次设置是定时器。关于手动轮播的相关触摸事件知识点在下面介绍。
四、移动触屏事件
1、事件类型
touchstart: 手指触摸屏幕时触发
touchmove: 手指在屏幕上移动时触发
touchend: 手指离开屏幕时触发
touchcancel: 手指要移动的事件中,touchmove事件被打断的时候触发(比如突然来了个弹框等)
细节:
touch 事件的触发,必须保证元素有大于0的宽高值,否则无法触发。(比如 ul 下 li 有宽高,ul 会被撑开,有了宽高,但是当 li 浮动起来后,ul 的宽还在, 高为0,此时无法对 ul 触发 touch 事件。)
2、触摸的 event 对象
event 事件对象是手指触摸屏幕时触发的事件对象,在这个对象中,我们主要关注三个对象数组。
touches:指屏幕上所有的触摸的手指列表
targetTouched:当前目标上所有的触摸的手指列表
changedTouches:指当前屏幕上变换的手指对象。在 touchstart 时为新接触屏幕的手指,在 touchend 时为新离开屏幕的手指。
PS:没有对比出 touches 同 targetTouches 的差异,推荐使用 targetTouches。
示例:手指拖动小球box
3、targetTouches 对象
targetTouches 对象中有几个坐标值值得我们关注:
screenX/screenY:手指的触摸点相对屏幕左上角的距离。
clientX/clientY:手指的触摸点相对视口(移动端屏幕左上角)的距离。
pageX/pageY:手指的触摸点相对当前页面的左上角的距离(当前页面可能有滚动条,所以距离包含滚动的距离)。
而,一般当我们在移动端设置了 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> 之后,clientX/clientY 的距离和 pageX/pageY 的距离是相同的了。所以一般使用 clientX/clientY。
最后
还有2件事拜托大家
一:求赞 求收藏 求分享 求留言,让更多的人看到这篇内容
二:欢迎添加我的个人微信
备注“加群”,我会拉你进技术交流群,群里大牛学霸具在,哪怕您做个潜水鱼也会学到很多东西