接下来要做的就是建立好项目的目录结构。一个项目的目录层级尽量扁平,不宜太深。根目录下面把样式库的核心CSS代码放在src目录下,里面包含主文件、公共样式和其他组件的样式文件。为了演示效果,在根目录下建立一个demo目录,来存放各个组件的演示代码。在后面还会在根目录下追加存放目标代码的dist目录和实战页面的page目录,这两个目录等用到的时候再建,现在可以按着下面的目录结构建立对应的文件夹和文件。
reset.css文件用来存放CSS-Reset的内容。CSS-Reset的作用是覆盖浏览器的默认样式,从而排除浏览器默认样式的影响。
/*
* @Author: Li
* @Date: 2020-04-26 14:14:14
* @Last Modified by: Li
* @Last Modified time: 2020-04-26 14:14:14
*/
/* 去掉所有元素的内外边距 */
html, body, div, span,
h1, h2, h3, h4, h5, h6, p, pre,
a, img, ul, li, form, label, input,
table, tbody, tfoot, thead, tr, th, td,
audio, video {
margin: 0;
padding: 0;
}
/* 统一全局字体 */
body {
font-family: -apple-system-font,BlinkMacSystemFont,"Helvetica Neue","PingFang SC","Hiragino Sans GB","Microsoft YaHei UI","Microsoft YaHei",Arial,sans-serif
}
/* 列表元素去掉默认的列表样式 */
ol, ul {
list-style: none;
}
/* Table元素的边框折叠 */
table {
border-collapse: collapse;
border-spacing: 0;
}
/* 去掉默认的下划线 */
a{
text-decoration: none;
}
/* 去掉input自带的边缘效果和背景颜色 */
input{
outline: none;
background: none;
}
在这份Reset文件里,每一条样式的作用都用注释进行了标注。其中要注意的是全局的字体,这里给body用font-family属性加了默认的字体。在运行的时候,浏览器会按着font-family所指定的字体从左到右依次在系统里查找,直到找到操作系统里存在的字体,然后把它作为网页使用的字体。如果找到最后一个都没有找到,就会使用浏览器的默认字体。
第二个要建立的是common.css,这个文件用来存放项目里的公共的样式,像控制全局布局的,或者用的比较多的公共样式,都可以放在这个文件中。
之前在上一章中介绍过经常使用的四种适配方式,包括百分比适配、多套样式分段适配、rem方式适配和弹性布局这几种方式,介绍了这四种适配方式的特点以及它们的优缺点。下来就一起看看怎么做一个移动端项目的适配。
适配中最先考虑的是要区分页面宽度,对不同的宽度做适配,在比较小的屏幕上把元素设置的小一些可以多显示一些内容;而在大一些的屏幕上把元素设置的也大一些,可以让内容更易读。在做宽度适配的时候有两种方式,一种是把宽度划分为几个区间,在每个区间里对需要适配的元素指定一个大小;另一种方式是根据屏幕宽度按比例计算元素的大小。这两种方式都没有什么致命的缺点,在实际项目中也都有在用的。但是相比之下,分区间适配在测试的时候会相对简单些,每个宽度区间取一个代表值来测试就可以了,而按比例适配的话不可能测试到每个尺寸,但也要把市面上常用的设备都测试一下。我们为了简便起见,准备采用分区间适配的方式。在做这个区间的分隔时,就可以使用CSS3中媒体查询的方式了。
@ Tips:
媒体查询是CSS3中的一个功能,它可以在不同类型的设备和不同的设备尺寸的情况下使用不同的样式。媒体查询的语法如下:
@media 设备类型 and 设备尺寸 { 样式部分 }
这条语句中的三个可设置的变量如下:
设备类型是指用的什么设备,这个值最常采用的是sceen(屏幕设备,幕设如手机电脑等都算屏备)和print(打印类型的设备,打印机等),而其他的像braille(盲文)、handheld(手持设备)和projection(项目演示)之类的取值基本很难用到。另外这个字段还可以使用all来代替所有设备类型,不写这个字段的话默认就是指定所有设备类型。
设备尺寸是指屏幕的宽度,这个字段的取值就是适配的关键了。它使用“max-width”表示小于的关系,使用“min-width表示大于的关系”,通过用这两个值做配合,就可以给宽度做不同的分区了。
样式部分和普通的CSS样式没有什么区别,如果某个设备复合这条媒体查询的规则,那么这条规则下的样式就会生效,如果样式优先级够高就会覆盖其他的样式从而达到适配的目的。
确定好整体的适配方式后,就可以对每个宽度区间里的元素做适配了。我们在指定的时候可以用px,那么每个区间的所有元素都需要适配一遍。当然也可以使用之前介绍过的rem单位来确定所有尺寸,这样只要控制不同区间下html元素的“font-size”就可以达到适配的效果,也就是rem方式适配。
这里我们要在刚才媒体查询的基础上,指定html元素的“font-size”属性。在基准区间内,我们使用20px作为“font-size”的值,在小屏幕上使用18px,在大屏幕上使用22px。
最后还有一类问题,就是希望几个元素平分屏幕宽度的情况。这种情况下,无论px还是rem都无法准确给元素设定宽度。所以就会用到另外两种适配方式,一个是使用百分比,另一种方式就是使用弹性布局。在平分屏幕宽度的需求中,弹性布局可以覆盖百分比的功能,所以在后面的开发中Flex弹性布局会用的多一些,有些比较简单的情况也会使用百分比来适配。
最终的common.css的内容会如下:
/*
* @Author: Li
* @Date: 2020-04-26 14:14:14
* @Last Modified by: Li
* @Last Modified time: 2020-04-26 17:28:14
*/
/* 屏幕宽度在340px至410px时,基准尺寸使用20px */
html{
font-size: 20px;
height:100%;
}
/* 屏幕宽度小于340px时,基准尺寸使用18px */
@media (max-width: 340px){
html{
font-size: 18px;
height:100%;
}
}
/* 屏幕宽度大于410px时,基准尺寸使用22px */
@media (min-width: 410px){
html{
font-size: 22px;
height:100%;
}
}
/* body默认样式 */
body{
max-width: 640px;
height:100%;
margin: 0 auto;
background: #f8f8f8;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
}
最后一个要做的文件就是这个框架的主文件,以后使用框架的时候都会通过这个文件来引用所有的样式,所以这个文件的功能就是集成其他的样式。
/*
* @Author: Li
* @Date: 2020-04-26 14:14:14
* @Last Modified by: Li
* @Last Modified time: 2020-04-26 17:28:14
*/
/* css reset */
@import './reset.css';
/* 公共样式 */
@import './common.css';
/* 字体图标 */
@import './icon.css';
/* 标题栏样式 */
@import './header.css';
/* 内容区样式 */
@import './content.css';
/* 导航条样式 */
@import './navbar.css';
/* 表单样式 */
@import './input.css';
/* 按钮的样式 */
@import './button.css';
/* 搜索栏的样式 */
@import './search.css';
/* 列表的样式 */
@import './list.css';
/* 网格组件的样式 */
@import './grid.css';
/* 菜单组件的样式 */
@import './menu.css';
/* 模态框组件的样式 */
@import './modal.css';
/* 加载提示组件 */
@import './loading.css';
/* Toast弹出提示组件 */
@import './toast.css';
/* actionSheet可选菜单 */
@import './action-sheet.css';
/* article文本组件 */
@import './article.css';
到此,这个项目的基础文件就建立完成了,后面整个框架的开发都会在这几个文件的基础上进行扩展。
在使用图标前,先了解一下图标的分类。在Web应用里,用的最多的就是图片图标和字体图标这两种。
一、图片图标
在早期的Web开发中,使用图片来制作图标是一个共识。当时使用透明的png图片把多个小图标合成在一个大的雪碧图里,几乎是所有应用都在使用的方法。
图片图标有很多优点:
图片图标的缺点也比较明显的:
二、字体图标
后来,字体图标就慢慢进入了开发者的视线。实际上早在IE6的时代,很多浏览器就有了自定义字体的能力。但是由于每个浏览器支持的字体格式不同,也没有像样的字体图标库出现,所以当时字体图标并没有被广泛应用。后来随着一些字体图标库的出现,字体图标就开始被广泛的使用了。
字体图标的优点恰好能弥补图片图标的不足:
当然字体图标也有不足:
图片图标和字体图标因为各自的优缺点,都有自己的适用场景,一般具有共性的且颜色比较单一的图标我们用字体图标,对于定制化要求比较高的就可以使用传统的图片图标。
这里使用的是一个免费的字体图标库Font Awesome 。这个字体图标库提供了网页开发中可以用到的大部分图标,本项目中使用的版本是v4.7.0。
一、字体图标的引入方式
Font Awesome提供了多种引入方式,可以在HTML中使用link标签引入,也可以在CSS文件中使用@import引入,还可以通过npm工具以npm插件的方式引入。
1、使用link标签引入
这种方式是最简单的,只需要使用link引入Font Awesome的地址即可。这个地址可以使用一些线上的CDN提供的文件地址;也可以把文件存到项目本地,然后使用本项目下的地址。用这种方式使用的时候,比较推荐使用线上的CDN文件,这样不占用自己服务器的资源,速度还比较快。唯一要注意的就是要找一个稳定的CDN服务,免得因为别人的服务挂了影响到自己的业务。以BootCDN提供的CDN服务为例,只需要在HTML的head标签里加入下面的代码,就可以引入Font Awesome文件了:
<link rel="stylesheet" href="//cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css">
2、在CSS文件中引用
在CSS文件中引入的方式和第一种方式很类似,也是可以使用CDN地址,也可以使用项目本地地址。这种引入方式的好处是当把Font Awesome作为依赖时,可以把Font Awesome封闭在框架的内部。但是这种方式也有些问题,就是@import会影响页面的加载速度,并且通过@import引入网络路径的话不能使用工具进行文件合并。但为了整个框架的闭合,会采取这种方式引入Font Awesome。引入的时候直接在CSS文件中按下面方式引用即可。
@import '//cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css';
3、以npm插件形式引入
Font Awesome同样提供了npm插件,如果是npm项目,可以直接使用npm安装font-awesome这个插件来使用。这个项目是纯文件形式的,所以无法使用npm插件。在做框架类的项目时,以npm方式安装字体图标文件是最好的,它既可以满足项目封闭的要求,也不会像使用@import方式一样会影响页面的加载速度。用npm方式使用的时候要先安装font-awesome插件:
npm install font-awesome@4.7.0 --save
二、字体图标的使用
刚才说了文件的引入方式,在文件成功引入以后,就可以在html中使用字体图标了。
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
<link rel="stylesheet" href="../src/my-ui.css">
<title>字体图标title>
<style>
/* 图标默认样式 */
.fa{
width: 1.5rem;
height: 1.5rem;
line-height: 1.5rem;
font-size: 1.2rem;
}
/* 红色图标 */
.fa-code{
color: red;
}
/* 圆形实心图标 */
.fa-info {
background: #666;
color: #fff;
border-radius: 50%;
}
/* 大图标 */
.fa-check {
display: block;
margin: 0 auto;
width: 6rem;
height: 6rem;
line-height: 6rem;
font-size: 3.5rem;
border-radius: 50%;
background: #09BB07;
color: #fff;
}
style>
head>
<body>
<i class="fa fa-address-book">i>
<h2>更改图标样式:h2>
<div>
<i class="fa fa-code">i>
<i class="fa fa-info">i>
<i class="fa fa-check">i>
div>
<h2>图标旋转:h2>
<div>
<i class="fa fa-spinner fa-spin">i>
<i class="fa fa-circle-o-notch fa-spin">i>
<i class="fa fa-cube fa-spin">i>
div>
body>
html>
@ Tips:
这是一个标准的移动端HTML文件,这里面要注意:
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
这条语句是移动端最常用的一个meta标签,这个标签是用来配置viewport属性的,viewport就是显示器的可视区域。在移动端浏览器中,为了让PC端的网页在手机上能正常显示,不至于把内容缩的太小,移动端浏览器就会让承载页面的区域大于屏幕的尺寸,这样可以让手机屏幕只显示网页的一部分内容,再通过拖拽和缩放来查看其他区域的内容。这种默认的行为虽然照顾了PC端的网页,但如果一个网页本来就是为移动端设计的,那么它的显示就是有问题的。所以浏览器提供了可以手动指定viewport值的方法,让承载页面的区域和屏幕等宽,并且使用user-scalable指定页面不可以缩放,这样原本为移动端设计的网页就可以正常显示了。
在移动端布局中:
这三部分区域只有内容区是一定会有的,头部标题栏和下面的导航栏在有些情况下并不需要,比如在一些嵌入 App 的页面就不需要标题栏,有些二级三级页面就不需要导航栏。因此在考虑内容区的布局的时候,要把对另外两个区域适配的适配考虑进去,保证内容区的内容既不会被遮挡,也不会留下大片的空白区。
在移动端布局中还有另外一个问题,就是各个区域的层级关系。前面章节在讲 z-index 的时候说过,把内容区放在最底层,标题栏和导航条放在第二层,页面蒙版放在最上层。
设计上面所说的这样一个布局,就要同时要达到如下这些要求:
在做布局的时候,因为标题栏和导航栏都要固定在屏幕的固定位置,且不跟随内容区进行滚动。为了实现这个效果,最先能想到的就是使用固定定位(position: fixed)。
固定定位是将元素固定在屏幕可视区(viewport)的固定位置,位置不会随着其他内容的滚动而变化。之前使用固定定位会使元素脱离文档流,并且会晚于正常文档流里的元素渲染的特点。除此之前,固定定位的元素还有一个比较重要的特性,就是它的宽和高都是相对于可视区的,和它的父级元素祖先元素都没关系。
这里面 /demo/layout.html 文件是一开始就建立好的,用来测试页面布局的样式;src 目录下圈出来的三个文件是新建出来的,分别用来存放标题栏、内容区和导航条的样式;还有一些其他的样式我们放在 common.css 里。在移动端的布局中,最容易想到的就是让内容区是正常的流式布局,然后把上下两个盒子使用固定定位来固定在页面的头部或尾部。下来我们就按着这种思路来进行布局。
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
<link rel="stylesheet" href="../src/my-ui.css">
<title>页面布局title>
head>
<body>
<div class="tt-header">
标题栏
div>
<div class="tt-navbar">
导航栏
div>
<div class="tt-content">
内容区
div>
<div class="tt-mask">div>
body>
html>
这个文件中我们在 body 里放了四部分内容,分别是标题栏、导航栏、内容区和页面蒙版。这个HTML 文件里导航栏在内容区的上面了。这是因为后面要通过判断是不是有标题栏和导航栏来控制内容区的样式,所以把内容区放在后面就可以使用兄弟选择器来操作内容区的样式了,省去了 JS 的工作。
接下来还要设置一下 body 的基础样式。
/* body默认样式 */
body{
max-width: 640px;
margin: 0 auto;
background: #f8f8f8;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
}
关于body的这几个样式,它们的作用是:
/src/header.css :
/* 头部导航条 */
.tt-header{
position: fixed;
box-sizing: border-box;
width: 100%;
max-width: 640px;
height: 2.3rem;
top: 0;
z-index: 200;
border-bottom: 1px solid #ddd;
}
/src/content.css :
/* 内容区 */
.tt-content{
box-sizing: border-box;
position: relative;
overflow-y: auto;
padding-top: 2.3rem;
padding-bottom: 2.5rem;
}
当标题栏和导航条不显示时,内容区能自动适配。如果按着刚才的样式,在没有标题栏或导航条的时候,响应的位置就是一片由 padding 撑出来的空白。为了自适应,我们可以把刚才的代码做个调整,改成如下的样子:
/* 内容区 */
.tt-content{
box-sizing: border-box;
position: relative;
overflow-y: auto;
}
/* 根据header和navbar自动适应内容区高度 */
.tt-header ~ .tt-content{
padding-top: 2.3rem;
}
.tt-navbar ~ .tt-content{
padding-bottom: 2.5rem;
}
这样在默认的情况下内容区是没有上下内边距的,当页面有了标题栏的时候,再用兄弟选择器给 tt-content 元素设置上“padding-top: 2.3rem;”。同理,在有导航栏的时候,也用兄弟选择器给内容区设置上“padding-bottom: 2.5rem;”。这样就达到了内容区自适应的需求。
弹性布局(Flex 布局)是 CSS3 中提出的一种布局方式,和传统的布局方式不同的是,弹性布局不再指定一个元素具体的尺寸,而是描述这个元素该如何去填充空间。这样做会把布局变得简单,我们只需要提出布局的要求,剩下计算的部分就交给浏览器完成了。
Flex 布局中有两个比较重要的东西,一个是弹性容器,另一个是弹性盒子。弹性容器和我们之前用的盒子没什么区别,我们通过 “display: flex;” 来把一个盒子指定成弹性容器,这样对这个容器本身没什么影响,但是会影响到这个容器里面元素的排布。当容器被指定为弹性容器以后,它里面的盒子就变成了弹性盒子。弹性盒子会有如下特点:
1、display: flex;
这个属性是用来指定弹性容器的,给容器加上 “display: flex” 属性后,这个容器就变成弹性容器了。
2、flex-grow
flex-grow 属性用来指定弹性盒子的拉伸方式。当一行内的弹性盒子不能充满整行时,就用这个属性来指定怎么去分配空闲的空间。flex-grow 的默认取值是 0,就是不去占用空闲区域。当 flex-grow 为非 0 数字的时候,这个弹性盒子就要进行拉伸了。具体拉伸多少,要看这一行里面有多少个需要拉伸的对象,这些需要拉伸的盒子按着 flex-grow 值的比例分配空闲区域的空间。
3、flex-shrink
我们在使用弹性布局的时候,通常是使用它可以拉伸的特性,但是弹性盒子也可以是收缩的。flex-shrink 属性就是用来指定弹性盒子的收缩方式的,当所有盒子的宽度加起来超过了容器的总宽度,每个盒子就要缩减一定的尺寸来保证所有盒子都能在容器中。flex-shrink 的默认值是 1,也就是默认情况下所有盒子平分超出部分的尺寸。
4、flex-basis
flex-basis 属性是用来指定弹性盒子基准宽度的。在弹性布局里,弹性盒子最终的宽度通常不是 width 属性指定的值,所以这里用 width 就显得不太准确。作为替代,弹性布局里给出了 flex-basis,它的作用和 width 属性很类似,但是把这个宽度叫做基准值,就显得严谨的多了。flex-basis 属性的优先级要高于 width,无论使用什么样的选择器,当两个属性同时作用在同一个弹性盒子上的时候,都是以 flex-basis 为准。
5、flex
flex 属性是对前面讲的 flex-grow、flex-shrink、flex-basis 三个属性的缩写。如果把三个属性都按顺序指定出来是很容易使用的,但通常我们会进一步缩写,把三个属性值合成一个来使用:
6、flex-direction
最后要说的是 flex-direction,前面的都是按着默认的从左到右一行排列。但是弹性布局中是支持弹性盒子排布方向的。这个属性是作用在弹性容器上的,取值可以是 “row”、“row-reverse”、“column” 和 “column-reverse”。默认的取值就是 row,也就是之前用的由左到右排列的方式,把 “flex-direction” 修改成 “ row-reverse” 时,就是从右至左排列。
我们要做的标题栏能满足如下要求即可:
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
<link rel="stylesheet" href="../src/my-ui.css">
<title>页面布局组件title>
head>
<body>
<div class="tt-header">
<div class="left"><i class="fa fa-chevron-left">i> 返回div>
<div class="title">我是标题div>
<div class="right"><i class="fa fa-ellipsis-h">i>div>
div>
<div class="tt-navbar">
导航栏
div>
<div class="tt-content">
内容区
div>
body>
html>
要完成之前提出的需求,我们要做的是:
/* 头部导航条 */
.tt-header{
+ display: flex;
position: fixed;
box-sizing: border-box;
width: 100%;
max-width: 640px;
height: 2.3rem;
+ line-height: 2.3rem;
+ text-align: center;
top: 0;
z-index: 200;
border-bottom: 1px solid #ddd;
+ background: #f8f8f8;
}
/* 左侧功能区 */
.tt-header > .left{
flex-basis: 3rem;
text-align: center;
flex-shrink: 0;
}
/* 中间标题部分 */
.tt-header > .title{
flex-grow: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
/* 右侧功能区 */
.tt-header > .right{
flex-basis: 3rem;
flex-shrink: 0;
}
导航栏里就是几个导航区域平分导航栏,每个导航区域里包括一个图标和一个导航名称。在做这个导航栏的时候,满足以下条件就可以了:
<div class="tt-navbar">
<a class="navbar-item">
<i class="fa fa-home icon">i>
<span class="name">首页span>
a>
<a class="navbar-item active">
<i class="fa fa-list icon">i>
<span class="name">分类span>
a>
<a class="navbar-item">
<i class="fa fa-search icon">i>
<span class="name">发现span>
a>
<a class="navbar-item">
<i class="fa fa-user-o icon">i>
<span class="name">我的span>
a>
div>
导航栏里的项目一般都是链接,所以我们用 a 标签来当做导航项目的容器。里面我们分为 “首页”、“分类”、“发现” 和 “我的” 四个项目,把 “分类” 这一栏用 active 类设置成了选中项。
/* 底部导航栏 */
.tt-navbar{
+ display: flex;
position: fixed;
box-sizing: border-box;
bottom: 0;
width: 100%;
max-width: 640px;
height: 2.5rem;
border-top: 1px solid #ddd;
z-index: 200;
+ background: #f8f8f8;
+ text-align: center;
}
/* 导航项目 */
.tt-navbar > .navbar-item{
flex: 1;
color: #808080;
}
/* 被选中的样式 */
.tt-navbar > .navbar-item.active{
color: #09BB07;
}
/* 导航图标 */
.tt-navbar > .navbar-item > .icon{
padding: .3rem 0 .1rem;
font-size: 1.1rem;
}
/* 导航名称 */
.tt-navbar > .navbar-item > .name{
display: block;
font-size: .5rem;
}
这段代码中,首先给容器 tt-navbar 设置成了弹性容器,并修改了对齐方式、背景色;然后在.navbar-item 上设置了 “flex: 1;” 和字体颜色,使得各个导航区域平分导航条(这里没有在.navbar-item 父元素上设置字体颜色,是为了可以覆盖链接自带的样式);再然后通过交集选择器.navbar-item.active 给选中的导航项加上了绿色的字体颜色,最后通过.icon 和.name 两个选择器调整了图标和字体的样式。
在移动端,经常用到面板 Panel 这种内容容器,这种容器通常用来存放一块独立的内容。Panel 容器一般都是由一个标题和一个内容区组成,在有些时候也可以没有标题。
对于这个容器样式,我们要注意到如下的细节:
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
<link rel="stylesheet" href="../src/my-ui.css">
<title>表单元素title>
head>
<body>
<div class="tt-content">
<h1 class="tt-panel-title">Panel标题h1>
<div class="tt-panel-body">
Panel内容区
div>
div>
body>
html>
为了简便这个页面里我们只保留内容区,去掉了头部和尾部。在内容区里直接使用 h1 标签作为Panel 的标题,使用 div 作为 Panel 的内容部分的容器。这里没有在外面套一层容器,是想减少一层嵌套,如果遇到什么需求必须加的再加一层即可。下来根据上面的要求,就可以写 Panel 部分的样式了。由于 Panel 是一个通用性比较大的容器,并不是表单才会用到的,所以我们把它的代码放在 /src/content.css 里,在 /src/content.css 文件的末尾追加如下代码:
/* 内容分区 -- Panel 面板 */
/* Panel 标题 */
.tt-content .tt-panel-title{
height: 1.8rem;
line-height: 1.8rem;
padding-left: 1rem;
color: #aaa;
background: #fff;
border-top: 1px solid #eee;
font-size: 14px;
font-weight: normal;
}
/* Panel 内容区 */
.tt-content .tt-panel-body{
position: relative;
margin-bottom: .6rem;
padding: .6rem 1rem;
background: #fff;
overflow: hidden;
border-top: 1px solid #eee;
border-bottom: 1px solid #eee;
}
/* 可手动设置内容区是否有内边距 */
.tt-content .tt-panel-body.no-padding{
padding: 0;
}
这里有几点要注意:
在移动端的表单中,输入框的样式比较固定,一般就是每个输入项占用一行,每行内由标签和输入框组成。
当我们要实现一个这样的样式时,就需要注意下面这些细节的处理:
<h1 class="tt-panel-title">基本输入框:h1>
<div class="tt-panel-body no-padding">
<div class="tt-form-item">
<label class="tt-form-label">用户名label>
<div class="tt-form-body">
<input class="tt-input" type="text" placeholder="请输入姓名" />
div>
div>
<div class="tt-form-item">
<label class="tt-form-label">密码label>
<div class="tt-form-body">
<input class="tt-input" type="password" placeholder="请输入密码" />
div>
div>
<div class="tt-form-item">
<label class="tt-form-label">日期label>
<div class="tt-form-body">
<input class="tt-input" type="date" placeholder="请选择日期" />
div>
div>
div>
这段 HTML 里,使用的带有 no-padding类的 Panel 容器。我们在 Panel 里建立了三个基本相同的 tt-form-item 元素,每一个元素就会占用一行。在每一行中,都会分为标签和输入区两部分。这三个 tt-form-item 元素里装了三种不同类型的输入框类型,分别是文本类型、密码类型和日期类型。这些样式需要写在新建的 /src/input.css 里,然后同样在 /src/my-ui.css 里把 /src/input.css 引入进去,下来我们就在 /src/input.css 里写它们的样式。
首先是表单项容器 .tt-form-item,根据前面提的要求我们逐个来实现:
在这里还有一个小的地方要注意,input 这个元素比较特殊,它会去继承父元素的字体样式,但会被浏览器给的默认样式覆盖,这些默认样式的优先级要高于继承来的样式。所以我们在设置input元素的字体样式时,需要直接给input设置样式,而不要依靠样式的继承。
在表单输入的时候,我们还需要一些特殊的功能,比如在输入有错误的时候可以进行提示,有些输入框可以通过一个按钮一键清空这些,我们这里就来写下这两种功能的输入框。在实现这些功能的样式的时候,其实就是在输入框的最后加上对应的小图标,其余的逻辑要交给 JS 来完成。
我们可以在基础输入框的HTML上进行改动,只需要在tt-form-body加上一个小叉号的字体图标,然后把字体图标变成上图所示的那种样式。
<h1 class="tt-panel-title">带清空功能的输入框:h1>
<div class="tt-panel-body no-padding">
<div class="tt-form-item">
<label class="tt-form-label">手机号label>
<div class="tt-form-body">
<input class="tt-input" type="text" placeholder="请输入手机号" />
<i class="fa fa-close tt-input-reset">i>
div>
div>
div>
在定位这个 fa-close 图标的时候,要把它放在输入框的右侧,并且在竖直方向上居中。在做这个需求的时候,就可以使用到绝对定位。我们可以按着如下的样式来设置这个图标:
/* 表单中的清空按钮 */
.tt-form-item > .tt-form-body > .tt-input-reset{
position: absolute;
width: .8rem;
height: .8rem;
line-height: .8rem;
top: 50%;
margin-top: -.4rem;
right: 1rem;
font-size: .6rem;
background: #aaa;
color: #fff;
border-radius: 50%;
}
@ Tips:
有关绝对定位的几点需要注意:
1、绝对定位的用法和固定定位很相似,只不过固定定位的参照物是屏幕可视区,而绝对定位的参照物是最近的有定位的祖先元素。
2、最近的有定位的祖先元素,这里说的定位可以是相对定位、绝对定位或者固定定位。在HTML中相对定位不会破坏文档流,所以通常会把固定定位和相对定位配合着来使用,但我们要知道固定定位的参考系也可以是绝对定位或固定定位的元素。
3、这里使用了一种新的竖直居中的方法,依靠“top:50%;”找到竖直方向的初始点,然后再依靠使用负值的margin把元素调整到竖直居中的位置。这种方式和我们在讲固定定位时讲到的水平竖直居中的方法,都是可以达到要求的。
在移动端中我们很少使用原生的 radio 或者 checkbox 样式,是因为这两种输入方式的点击区域比较小,不太利于移动端的操作。作为替代,通常会把单选和多选转变成下面这种形式:
根据上图,单选和多选里,都是每一个选项占用一行,每行中通过图标来标记是否为选中状态。
下来我们先来实现单选功能,单选中的图标我们用不带外框的勾选标志,根据上面的结构,我们可以这样定义 HTML 结构:
<h1 class="tt-panel-title">单选:h1>
<div class="tt-panel-body no-padding">
<div class="tt-form-item">
<div class="tt-radio">
<i class="fa fa-check tt-radio-icon">i>
<span class="tt-radio-desc">单选选项1span>
<input class="tt-radio-input" type="radio">
div>
div>
<div class="tt-form-item">
<div class="tt-radio checked">
<i class="fa fa-check tt-radio-icon">i>
<span class="tt-radio-desc">单选选项2span>
<input class="tt-radio-input" type="radio">
div>
div>
<div class="tt-form-item">
<div class="tt-radio">
<i class="fa fa-check tt-radio-icon">i>
<span class="tt-radio-desc">单选选项3span>
<input class="tt-radio-input" type="radio">
div>
div>
div>
在上面的结构中,外层的容器和之前输入框用的都是一样的,只是 .tt-form-item 里面的内容变了一下:
<div class="tt-form-item">
<div class="tt-radio">
<i class="fa fa-check tt-radio-icon">i>
<span class="tt-radio-desc">单选选项1span>
<input class="tt-radio-input" type="radio">
div>
div>
在这个结构中,给每个单选项放了一个 tt-radio 的容器,可以直接在这个容器上添加 class 来改变选中样式。容器里面有三个元素,分别是区分勾选状态的图标,选项的名称和一个 radio 类型的 input。在实际显示的时候,只需要显示前两个元素。而最后一个 input 我们会用它来记录单选的选择情况,用来给 JS 使用,所以会把它隐藏起来。我们在做单选样式的时候,只需要给 tt-radio 和它的子元素加上样式即可,可以在 /src/input.css 的末尾追加上:
/* 自定义单选 */
.tt-form-item > .tt-radio{
flex: 1;
font-size: .8rem;
line-height: 2rem;
}
/* 未选中状态的图标 */
.tt-form-item > .tt-radio > .tt-radio-icon{
margin-right: .5rem;
color: #09BB07;
visibility: hidden;
}
/* 选中状态的图标 */
.tt-form-item > .tt-radio.checked > .tt-radio-icon{
visibility: visible;
}
/* 隐藏的radio类型的input */
.tt-form-item > .tt-radio > .tt-radio-input{
position: absolute;
left: -999rem;
}
这段代码中,要注意的地方有:
接下来我们再实现一下多选,多选和单选很相似,我们只是变化了一下容器的名称和选项的图标。这里我们也直接看代码了。
<h1 class="tt-panel-title">多选:h1>
<div class="tt-panel-body no-padding">
<div class="tt-form-item">
<div class="tt-check">
<i class="fa fa-check tt-check-icon">i>
<span class="tt-check-desc">多选选项1span>
<input class="tt-check-input" type="checkbox">
div>
div>
<div class="tt-form-item">
<div class="tt-check checked">
<i class="fa fa-check tt-check-icon">i>
<span class="tt-check-desc">多选选项2span>
<input class="tt-check-input" type="checkbox">
div>
div>
<div class="tt-form-item">
<div class="tt-check checked">
<i class="fa fa-check tt-check-icon">i>
<span class="tt-check-desc">多选选项3span>
<input class="tt-check-input" type="checkbox">
div>
div>
div>
/* 自定义多选 */
.tt-form-item > .tt-check{
flex: 1;
font-size: .8rem;
line-height: 2rem;
}
/* 未选中时的图标 */
.tt-form-item > .tt-check > .tt-check-icon{
margin-right: .5rem;
border: 1px solid #ccc;
border-radius: 50%;
color: rgba(0,0,0,0);
font-size: .6rem;
width: .8rem;
height: .8rem;
line-height: .8rem;
}
/* 选中时的图标 */
.tt-form-item > .tt-check.checked > .tt-check-icon{
background: #09BB07;
color: #fff;
border-color: rgba(0,0,0,0)
}
/* 隐藏的check类型的input */
.tt-form-item > .tt-check > .tt-check-input{
position: absolute;
left: -999rem;
}
在多选里,我们用字体图标的颜色来控制它的勾选状态,在未选中的时候给字体图标透明的字体,在选中的时候再把字体颜色设置回来。其他的就都和单选是类似的了。
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
<link rel="stylesheet" href="../src/my-ui.css">
<title>按钮的设计与开发title>
head>
<body>
<div class="tt-content">
<h2 class="tt-panel-title">标准按钮h2>
<div class="tt-panel-body">
<a class="tt-btn">默认按钮a>
div>
div>
body>
html>
这段代码里就在 tt-panel-body 里加了一个 a 标签,给他起名叫 tt-btn,这就是一个最基本的按钮。我这里用了 a 标签,是可以直接用作超链接,也可以使用 span 或者 button 等标签代替。
对于标准按钮我们要有如下要求:
/* 按钮默认样式 */
.tt-btn{
display: block;
position: relative;
width: 10rem;
margin: 0 auto;
padding: .5rem 0;
box-sizing: border-box;
border: 1px solid #ccc;
font-size: .7rem;
text-align: center;
color: #000000;
border-radius: 5px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
user-select:none;
}
在这些样式中最后一条的作用要单独提一下,“user-select:none;” 表示用户不能对按钮的内容进行选中,防止因用户按住按钮太久而导致出现文本选择的选项。
刚才完成的默认按钮可以用在大部分场景里,但是这个按钮并没有特定的含义。从单一的功能上看这个按钮怎么用都行,但是从整个应用来说,不同类型的操作都用同一种按钮肯定是容易引起歧义的。所以我们要来做几种不同用途的按钮,用来区分不同的应用场景。
一个页面都是有特定的功能,也是有特定的目的。比如商品的详情页是展示商品细节的,但这个页面的目的是引导用户下单;订单确认页展示的是订单内容,但这个页面的目的是引导用户付款。这些页面里,都有一个非常强的目的性。为了引导用户进行下一步操作,这些页面中就会把引导用的按钮设计的更显眼,更方便用户点击。
在页面中,还有一类操作是比较敏感的,不希望用户点击或者需要谨慎点击的。这些操作不需要很强的引导性,反而需要对用户进行警示,也就是我们这里说的带警示功能的按钮。在设计上,通常使用红色作为警示色.
在页面中,还有一类比较特殊的按钮,就是不可用按钮。这类按钮通常是有约束条件的,在某种情况下才可用。而在不满足条件的时候,就是不可用状态。不可用按钮通常用对比度较弱的配色来表示,目的是让用户忽略这个按钮的存在。
@ Tips:
在一些设计中,有些是非常不希望用户点击但又不得不加的按钮,也会使用这种不可用按钮。比如取消订单、删除应用和某些确认操作里的取消按钮等,通常使用这种手段(也可以说是技巧)来阻碍用户进行对产品不利的操作。
除了场景的区分外,在不同位置使用的按钮样式也是有区别的。比如在一些需要强引导的页面中,会使用比标准更明显一些的按钮,有时为了让用户随时能进行操作还会把按钮固定在页面的底部,不参与页面的滚动。而在一些小的区域里,比如单行的区域,则会使用一些小号的行内按钮。
大按钮通常用于强引导,这里我们把它设计成宽度占满全屏.
大按钮和标准按钮有如下区别:
我们用“tt-btn-large”来标记大按钮的样式,这个样式和刚才定制按钮样式的类是从不同维度来定义按钮的,所以可以和用来区分按钮类型的类同时使用,这样按钮定制起来就更灵活了。下来是 HTML 的内容:
<h2 class="tt-panel-title">大按钮h2>
<div class="tt-panel-body no-padding">
<br/>
<a class="tt-btn tt-btn-large">大按钮a>
<br/>
<a class="tt-btn tt-btn-primary tt-btn-large">大按钮a>
<br/>
<a class="tt-btn tt-btn-warning tt-btn-large">大按钮a>
<br/>
<a class="tt-btn tt-btn-disabled tt-btn-large">大按钮a>
<br/>
div>
这里为了能撑满整个页面,我们给 Panel 容器加上了“no-padding”的类。这里在各种类型的按钮上都加上了一个“tt-btn-large”的类,我们通过指定这个类的样式来修改样式的大小。根据刚才的改动情况,很容易就能实现出大按钮的样式了:
/* 大按钮 */
.tt-btn.tt-btn-large{
width: 100%;
font-size: .9rem;
border-left: none;
border-right: none;
border-radius: 0;
}
@ Tips:
在做全宽的按钮时使用了“width: 100%;”,但这个样式对容器是有要求的,要求它的参考容器也是全宽的才可以。在 CSS3 中还有一个单位是 vw,这个单位也是一个相对单位,它的参考系是屏幕的宽度,像这种需要占满全屏的需求就可以直接使用“width: 100vw;”,这样就可以不用考虑外层容器的情况了。和 vw 对应的还有个 vh 单位,它是相对于屏幕高度的。但是!!!这里要注意,这两个单位在安卓 4.3 及以前的机器上是不支持的,如果用的话一定要确定自己产品中低版本安卓的占比。根据谷歌在 2018 年 8 月发布的统计数据来看,安卓 4.3 及之前版本还占安卓市场将近 4% 的份额。这里也推荐一个查询各种样式或者单位兼容性情况的网站:caniuse.com
下来还有一个小按钮的开发,小按钮通常是用作比较小的区域,通常以行内元素出现。所以在制作小按钮的时候,除了尺寸的变化外,还要把小按钮改成行内元素,这里我们可以使用行内块。我们用“tt-btn-small”来标记小按钮的样式,下面是 HMTL:
<h2 class="tt-panel-title">小按钮h2>
<div class="tt-panel-body">
<a class="tt-btn tt-btn-small">小按钮a>
<a class="tt-btn tt-btn-primary tt-btn-small">小按钮a>
<a class="tt-btn tt-btn-warning tt-btn-small">小按钮a>
<a class="tt-btn tt-btn-disabled tt-btn-small">小按钮a>
div>
/* 小按钮 */
.tt-btn.tt-btn-small{
display: inline-block;
width: 3rem;
padding: .2rem 0;
font-size: .7rem;
}
后续文章: 第三章.