”本系列教程为实战教程,是自己移动端重构经验及思想的一次总结,也是对sheral UI的一次全方位剖析,首发在imweb和w3cplus两大站点及“前端Talk”微信公众号,其余所有标注或没有标注来源的均为转载。“
0.sandal & sheral
简单来说,sandal是基于sass的一个移动端css的基础库,提供了一些基础的重置,常用的mixin,如flex布局,等分,水平垂直居中,常用图标等,基于它你可以非常方便快速地扩展出你需要的UI组件,其整体结构设计如下图:
_function.scss集成了所有的基础功能,并且不带任何样式,而_core.scss则在function的基础上加入了重置样式,ext文件夹则是三个扩展文件,可根据个人需要自由导入,具体介绍及使用请参考sandal 文档
sheral是基于sandal扩展的UI组件库,目前包括了btn,dialog,header,card,form,toast,line,media,progress等常用的25+组件。你可以直接调用,也可以根据自己的需求定制你的组件。
sandal与sheral的关系,就如jquery与其插件的关系。所以退一万步说如果sheral的UI真的不合你意,你也可以基于sandal提供的基础功能,快速构建一套你自己的UI库。这也是我把这两个分开开发的原因。
放肆还是克制
如果要适应各种场景,就必然会增加代码量,而各种情况又不一定能全部用上,那冗余的代码必然是个累赘,要是换个人开发那更是不敢动了;而如果太简单,必然又无法发挥一个UI库的作用,所以这必然是一个纠结的问题。
为了遵循克制这原则,在组件的头部,我们经常会看见一些带有switch标识的开关组件,有默认会true的,也有为false的,你可以根据你的需要选择开或者关来决定是否生成该样式。
其他说明
如无特别申明,所有的@mixin均定义在sandal的_mixin.scss中。
1.基础知识
既然是新的开始,先简单说下这个系列要用到的一些技术吧。同时也是对移动端重构一些技术的一个简单回顾。
关于viewport详细请参考移动前端开发之viewport的深入理解
css3选择器
CSS3 选择器——属性选择器
css选择器支持一览表
伪元素(::before, ::after)
A Whole Bunch of Amazing Stuff Pseudo Elements Can Do
伪元素的content使用
据说百分之八十的人入门移动端重构的第一个问题就会问:是不是所有的当要用百分比单位啊。这可以从侧面可以反应出百分比有多重要,下面是关于
新单位——rem,vw,vh...
CSS3的REM设置字体大小
vw, vh等新单位介绍(安卓4.4+支持)
flex
一个完整的Flexbox指南
A Complete Guide to Flexbox
用一首来说就是”眼前的黑不是黑,你说的1px是什么1px“,下面就是各种奇淫技巧实现:
Retina屏的移动设备如何实现真正1px的线?
// retina border// 0.5px实现 ios9@mixin retina-one-px() {
@media only screen and (-webkit-min-device-pixel-ratio: 2), screen and (-webkit-min-device-pixel-ratio: 3) {
}
}
除了曾经的1px不再是1px,曾经的fixed也不再是我们熟悉的fixed了,再搞下去都要得fixed恐惧症了。
Web移动端Fixed布局的解决方案
我们现在一般android采用fixed布局;ios采用absolute,然后中间滚动使用
-webkit-overflow-scrolling: touch;。如果还不行就具体问题具体分析。跟pc的不一样,移动端的图片很多都不是固定的宽高的(icon图标与头像等一些小图还是固定大小的),所以就面临一个问题:不能设置一个具体的高度,于是就会出现加载过程其他内容随着图片的加载慢慢向下移动。
给图片提供一个容器,设置高度为0,根据宽度按照图片的比例使用paddin-top得到一个高度值,然后图片绝对定位设置宽高为100%即可,如图片尺寸为200*100(则高度为宽度的二分之一):
}
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
css中如何做到容器按比例缩放
居中,居中,还是居中,重要的话说三次!!!
Centering in CSS: A Complete Guide
等分
目前等分大概分为三种:
间距为固定值如10px,剩余宽度item等分
这次我将会在这个实战系列中把这三种情况一一剖析。
这年头不会一两招css3动画,都不好意思说自己会css了。
Advanced CSS3 2D and 3D Transform Techniques
css3 animation 101
css3动画疑难杂症一览
这个line list的名字是我自己起的(大概的意思是单行列表),要实现的东西为sheral的line list,对应的scss组件为
_line-list.scss,下图为line-list的一个缩影:这个UI应该是每个移动端网页都必备的,而且使用场景也是非常的丰富,所以这里我们采用一步步循序渐进的方式去重构。
retina 1px
整行点击
如何方便扩展
把1px挂在除第一个元素之外的伪元素before上,而第一个最上面和最后一个最下面的1px将会在父元素上实现,那样中间line-item之间的1px就很容易扩展实现缩进。
右箭头跳转模式
item之间的1px缩进,最开始和最末位的不缩进
.line-list--indent { @extend %border-tb; // 添加最上和最下的1px,形成封闭
.line-item::before { left: 10px; // 缩进10px
}.line-list--after-v { // 右箭头通过after生成
.line-item { padding-right: 30px;
@extend %item-v-right;
}
PS:这里缩进用的伪元素before的1px left定位来实现的,看到过有些方法是设置item的border-bottom,然后设置item的
margin-left: 10px,这种实现方法是错误的,因为点击的不是整行了(缺了margin left的10px),当然也可以内嵌一个inner元素设置inner元素的margin left,或空元素定位等// border top & bottom%border-tb { position: relative;
@include retina-one-px-border(top);
z-index: 1; // 第一个元素点击的时候防止active背景色遮盖了1px
&::after { content: "";
@include retina-one-px-border(bottom);
}// item arrow, 右侧箭头跳转指向%item-v-right {
@include v-arrow;
position: absolute;
top: 50%;
transform: rotate(45deg) translate(0, -50%);
box-sizing: border-box;
}
选择模式
// 单选.line-list--select { .line-item { padding-right: 30px;
&::after { // 伪元素生成对钩
display: block;
height: 8px;
border-left: 2px solid currentColor;
transform: rotate(-52deg) translate(0, -50%);
position: absolute;
top: 50%;
margin-top: -4px;
}
}// 多选.line-list--multi-select { .active{ color: $primary;
.icon-checkbox{ color: $primary;
}
复杂模式
这里我们将采用flex,一行大概分为三栏:图标icon(固定宽度),中间内容(剩余宽度),右边操作或提示(switch,提示文字或数字,右箭头)。如果你要兼容的手机不支持flex,那也没关系,这个结构也足够你使用绝对定位或float布局了,完全不需要再更改结构。
align-items: center;
.item-icon, .item-img, .icon-switch, .remind-num, .item-append{ margin-right: 10px;
flex: 1;
width: 1%;
} .icon-v-right { width: 30px;
height: 30px;
margin-left: -10px;
} .remind-num { position: static;
line-height: 1.5;
}
3.各种等分
单行,不考虑间距等分
.nav-list{ @include equal-flex(nav-item);}
// flex等分@mixin equal-flex($children: li) {
$childrenEle: li div p a span strong;
#{$children} { flex: 1;
width: 1%;
} @else {
.#{$children} { // 自动加.成class
width: 1%;
}
参数部分可以是常用的
li div p a span strong几个元素,也可以是class,会自动加.。// table 等分@mixin equal-table($children: li) {
table-layout: fixed;
$childrenEle: li div p a span strong;
#{$children} { display: table-cell;
} @else {
.#{$children} { display: table-cell;
}
间距相等,剩余item平分
分为单行及多行情况,单行直接flex就好,而多行的flex老版本兼容不是很好,所以不建议使用,直接用原始的float。
.equal--gap{ @include line-equal-gap($children: line-equal-item);}
// line equal@mixin line-equal-gap($gap: 10px, $lr: true, $children: li) {
@if $lr { // 左右边缘是否有gap
padding-right: $gap;
#{$children} { flex: 1;
&:not(:first-of-type){ margin-left: $gap;
}
.#{$children} { flex: 1;
&:not(:first-of-type){ margin-left: $gap;
}
}
关于多行的可以参考sheral的card实现,这里以卡片2为例,关键代码如下:
$cardFlexSwitch: false !default; // 默认使用float$cardGap: 10px !default; // 默认间距为10px$carLineNum: 2 !default; // 目前只支持2 或 3 等分.card-list { @if $cardFlexSwitch {
flex-wrap: wrap;
} @else {
} .card-item { position: relative;
@if not $cardFlexSwitch {
} .item-img { width: 100%;
} .item-tt { line-height: 30px;
}
}.card-list--gap{ padding-left: $cardGap / 2;
.card-item{ margin-bottom: $cardGap;
padding-right: $cardGap / 2;
}
PS:这里考虑到flex与float的无缝切换,所以flex思路同样设置宽度的n等分,而不是单行的那种margin方法。
单行的demo为line equal的第二个。这里使用的另一个mixin: line-equal-item,其实现思路是通过flex
justify-content: space-between;进行变化使用。@mixin line-equal-item($lr: true, $children: li) {
justify-content: space-between;
@if $lr {
&::after { content: "";
}
多行的话,跟上面的card实现差不多,具体的间隙计算公式可以参考item宽度固定,剩余间距等分实现方案探讨
4.进入离开动画
在sandal的
_animation.scss中我们定义了fade-in/out, shrink-in/out, up-in/out, down-in/out, left-in/out, right-in/out六组基础动画,下面我们以fade-in/out为例说明如何使用:@include animation-fade-in;@include animation-fade-out;
.fade-in, .fade-out { -webkit-animation-duration: 0.3s; animation-duration: 0.3s; -webkit-animation-fill-mode: both; animation-fill-mode: both;
}@-webkit-keyframes fadeIn {
0% { opacity: 0;
100% { opacity: 1;
}}@keyframes fadeIn {
0% { opacity: 0;
100% { opacity: 1;
}}.fade-out { -webkit-animation-name: fadeOut; animation-name: fadeOut;
0% { opacity: 1;
100% { opacity: 0;
0% { opacity: 1;
100% { opacity: 0;
当然为了扩展,mixin还定义了两个参数:
animation-fade-in($className: fade, $from: 0),animation-fade-out($className: fade, $to: 0),第一个表示要用的class名字(会自动补上in/out),第二个表示opacity值(from为起始,to为结束)es6 封装动画进入离开类
constructor({ele, className, inCallback, outCallback}) { this.ele = ele.nodeType === 1 ? ele : document.querySelector(ele); this.inClass = className + '-in'; // 加上in表示进入class
this.inCallback = inCallback; // 进入动画结束后回调函数
this.animationend = this.whichEndEvent(); // 使用animationend事件
} // 进入
this.ele.addEventListener(this.animationend, this.endBind);
leave() { this.ele.classList.add(this.outClass); // animation动画结束之后,移除该class
} // 动画结束事件处理函数
eleClassList = ele.classList,
isOut = eleClassList.contains(this.outClass); // 离开
eleClassList.remove(this.inClass); this.inCallback && this.inCallback();
eleClassList.remove(this.outClass); this.outCallback && this.outCallback();
} // 判断end事件,可独立为一个基础功能
el = document.createElement('div'); var animations = { "animation" : "animationend", "WebkitAnimation": "webkitAnimationEnd"
} for(k in animations) { if(el.style[k] !== undefined) { return animations[k];
}
}
PS:注意这里我们采用的animation动画,而不是transition动画,因为transition动画从none到block的时候,直接添加动画的class是不会有动画效果的(除非使用回调函数或promise),而animation动画从none到block的时候添加动画class是可以的。这里不想设计得太复杂,所以直接使用animation动画
function leaveEnd() { console.log('hello the world');
PS:本系列教程未完待续,正在码字中...
在后台回复福利,可以获取下载《全球移动技术大会2016》PPT集合。
关注我们
扫二维码 或搜索 fed-talk ,关注我们的微信公众号,也欢迎你将它分享给自己的朋友。
html结构
结构方面,标签可以是
ul.line-list>.line-item或者div.line-list>a.line-item,前者用于单页应用,后者用于链接跳转。.line-item { @extend %bar-line;}.line-list { background: #fff;
+ .line-list { margin-top: 10px;
}
由于这种line item的样式使用场景较多,所以我们封装了一个
%bar-line,定义在sandal的_mixin.scss文件中(下面如无特殊说明,mixin和%均在该文件定义),如下:padding: 5px 10px;
display: block;
@if $activeStateSwitch{ //是否开启:active样式
&:hover { background-color: darken($colorF, 3%);
}
content: "";
@include retina-one-px-border;
}
下面解读下上面的scss代码:

