移动前端重构实战系列
移动前端重构实战系列:5-7章
(本文系来自腾讯imweb团队 结一大大 关于移动端重构经验以及思想的实战系列,推荐点击左下角的阅读原文。)
”本系列教程为实战教程,是自己移动端重构经验及思想的一次总结,也是对sheral UI的一次全方位剖析,首发在imweb和w3cplus两大站点及“前端Talk”微信公众号,其余所有标注或没有标注来源的均为转载。“
——imweb 结一
5.Form
form
大概要实现的效果如下图(具体demo可见sheral form):
粗略一看,跟line list差不多,好像可以直接套用,但是深究起来还是有那么些不同的,大概有以下几点用户体验差别:
-
输入框可点击范围
-
右边的箭头可点击范围
line list设计95%都是整行点击,所以不管你点哪,都是触发整行的点击事件,右边的箭头就是个指引而已,所以伪元素生成是没有问题的;而form就不一样了,右边箭头是真的要挂载事件的,所以除了直接使用元素外,点击范围一定要设计合理,总不能箭头多大就多大,那操作起来就不方便了,同理input框我们也需要设计成整行的高度,方便点击输入。以电话为例,既可以手动在input框输入,也可以点击右边的箭头去通讯录选择,所以单纯的复制line list过来是行不通的,下面从html及scss代码简单分析下。
从html结构上,大体分为三列,分别为label,表单元素及右边附加部分
.form-item
label.item-label
.item-field
input:text.f-text <!-- 表单元素 -->
p.field-value.placehold <!-- 选择值或默认值或选择说明 -->
i.icon-v-right <!-- 右边部分 -->
而scss主要是flex布局,设置中间输入元素部分为flex:1;
.form-item{ display: flex; // flex布局,子元素垂直居中
align-items: center;
position: relative;
line-height: $barHeight;
overflow: hidden;
&:not(:first-of-type)::before { // 1px 分割线
content: "";
@include retina-one-px-border;
} .item-field{ // 剩余宽度
flex: 1;
width: 1%;
} .icon-v-right{ // 右侧箭头
display: block;
width: 30px;
height: $barHeight;
color: #ccc;
}
}
错误处理
主要提供了四个icon,分别为alert、info、question、ok,demo可在sheral icon查看,样式定义在sandal/ext/_icons.scss
中,代码如下:
.icon-alert{ color: $red;
&::after{ content: "!";
}
}.icon-info{ color: $blue;
&::after{ content: "i";
}
}.icon-question{ color: $blue;
&::after{ content: "?";
}
}.icon-ok{ color: $green;
&::after{ content: "";
position: absolute;
top: 50%;
left: 50%;
width: 5px;
height: 2px;
border-bottom: 1px solid currentColor;
border-left: 1px solid currentColor;
transform: translate(-50%, -50%) rotate(-52deg) scale(1.5);
margin-top: -1px;
}
}
其中alert、info、question三个icon里面的图标分别为!
、 i
、 ?
,通过伪元素after设置content,而ok的则通过伪元素after绘制,为了达到和前面三个一样的粗细,使用了transform的scale
search
demo见sheral search
主要说下第二个搜索框。它其实是个假的搜索输入框,点击跳转到真的搜索,所以居中的icon和文字其实并不是input的内容。
range
demo见 sheral range
直接使用了input:range
,所以样式方面主要是重置range的shadow dom样式,样式比较多,可直接参阅_range.scss或自行搜索相关文章。
对于某些低端浏览器兼容问题,包裹了一层.range-wrap
,通过包裹的伪元素生成range的trace
6.icon与图片
icon
对于icon的问题多数都集中在颜色和大小上,所以sheral采用了svg icon和css绘制的icon,关于svg icon网上已经有很多文章了,所以这里主要介绍绘制icon的一些技巧,如下以搜索图标为例:
// icon search.icon-search { position: relative;
&::before { content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border: 2px solid currentColor;
height: 12px;
width: 12px;
box-sizing: border-box;
border-radius: 50%;
margin-left: -2px;
margin-top: -2px;
}
&::after { content: "";
background: currentColor;
height: 6px;
width: 2px;
position: absolute;
top: 50%;
left: 50%;
margin-left: 4px;
margin-top: 4px;
transform: translate(-50%, -50%) rotate(-45deg);
}
}
-
icon-search本身没有设置大小,只充当了一个relative的容器
-
绘制的功能交给伪元素before和after
-
伪元素采用绝对定位居中
-
颜色使用currentColor
这样做可以带来两个好处,一是可以方便设置icon-search的大小(扩大点击范围同时,还保持水平垂直居中),二是可以方便修改颜色(设置icon-search的color即可更改颜色)
其他的一些绘制icon具体可见sandal/ext/_icon.scss
文件,demo可见sheral icon
图片
关于图片这里主要讨论三点:
-
普通图片
-
图片的宽高比
-
背景图片大小
1、对于第一点,在sandal的_reset.scss
中就已经重置好了
img{ vertical-align: middle;
max-width: 100%;}
2、对于图片的宽高比,我们在基础知识中已经说了下,这里再具体说明下如何使用,以card list为例。
如果你多刷新几次应该就可以看到卡片1与2的图片区别了,1的图片区域有了高度,而2没有,所以1图片的加载不会影响下面内容的变化,而2加载图片会把下面内容向下排挤。这是因为卡片1的图片我们包裹了一层,设置了一个宽高比,而卡片2没有。
下面详细说下它们之间的html和css区别
<!-- 卡片1 -->.item-img-wrap > img.item-img<!-- 卡片2-->img.item-img// 卡片1 .item-img-wrap{ @include object-wrap(100%, '.item-img');}// 卡片2.item-img{ width: 100%;}
其中mixin object-wrap在sandal中定义如下(具体解释可参阅css中如何做到容器按比例缩放):
// object wrap// $child 参数请使用单引号,因为用于子元素选择器@mixin object-wrap($percent: 100%, $child: 'img') {
position: relative;
padding-top: $percent;
height: 0;
#{unquote($child)} { position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
}
3、至于最后的背景图片,说起来又是个悲伤的故事,虽然css3的background-size
已经非常强大了,但是安卓强大的阵线中总有某些机子总是拖了一大截后腿的。
首先安卓4.3-不支持background-size
的缩写,这倒没什么,再另写一行就是了,关键是有些安卓4.3-还不支持百分比单位。于是只好把目光转向cover
或contain
了(更多介绍请参考background-size),这又涉及到容器的宽高了。
如果容器已经有了宽高(当然这里的宽高是指可以随着机型变化的),比如全屏,我们就直接用cover了;而如果容器没有宽高,那就又回到了第二个问题,我们可以使用图片或者把设置背景图的这个容器设置成我们图片的宽高比,那样再使用cover或contain都可以。
以微信的朋友圈头部的背景图片为例(这里只是进行分析,具体的实现技术我也不知道):
-
在更换相册封面的时候,对选择的图片进行了1:1的裁剪
-
按照第二种情况,设置背景图片的容器的高度等于宽度(图片是1:1),然后设置background-size为cover即可
-
或者按照第二种情况,使用img元素,外面再嵌套一层wrap设置高度
PS:默认看到的大概是图片下面的四分之三(我并没有去量尺寸,根据经验猜测而已,如有错误请见谅),上面的四分之一当我们向下拉取刷新的时候就可以看到整个图片了
7.环形UI
圆形进度条
具体demo效果可见:sheral progress
这里主要分析下圆环的实现,蓝色的进度条圆环由左右两边构成,这里以右半边的为例,html结构为.circle-right > .right-inner
(为了视觉效果,把蓝色放在里面了,而非覆盖在灰色上),如下图:
半圆环由两层结构构成,.cicle-right
的大小为50px100px,超过隐藏,而.right-inner
的实际大小为100px100px,因为父容器宽度为50px且超过隐藏所以只会显示右边一半,如果只是这样的话我们旋转.right-inner
的话右边一直会有蓝色的半环。所以我们对.right-inner
再做个裁剪处理clip: rect(0, 50px, 100px, 0);
,这样导致整个.right-inner
圆环只有左半边可见,然后加上我们父元素是在右半边且多余隐藏,所以默认看不到我们的蓝色圆环,如要看到蓝色圆环部分只有将.right-inner
左边部分旋转到右边父元素的范围内,核心代码如下图:
.circle-right{
width: 50px;
height: 100px;
position: absolute;
top: -4px;
right: -4px;
overflow: hidden;}.right-inner{ width: 100px;
height: 100px;
position: absolute;
right: 0;
top: 0;
border-radius: 50%;
border: 4px solid #007aff;
box-sizing: border-box;
clip: rect(0, 50px, 100px, 0); // 设置左半边可见
transition: transform 0.5s linear;}
同理即可实现左半边,不过如果某些安卓机比较卡的话,在大于50%的时候,左边半圆在衔接的时候会有一个卡顿不连贯,所以可以考虑增加再增加一个底部的半圆环,让其在右边半圆环运动到一半的时候开始运动,然后设置一个比较短的完成时间,这样就可以衔接起来。
当然其实使用svg更方便,这里推荐一个库progress bar.js
PS:关于svg path的弧形绘制可参考MDN svg 路径的最后弧形部分
弧形tool
要实现的效果如下图,具体demo可见sheral tool
这里主要涉及到两点:
-
旋转角度计算
-
图标再旋转回来处理
-
动画处理,主要对opacity和transform进行动画
如果按总的90deg角计算,index表示item的索引(从1开始),n表示item总数,则每个item的旋转角度计算公式为:
每个item的旋转角度 = ( index -1) / (n - 1) * 90deg
而图标就要相应的旋转对应的负角度回来,于是每个icon的旋转角度计算公式为:
每个图标的旋转角度 = -( index -1) / (n - 1) * 90deg
默认样式:
.tool-item{ width: $quickToolSize;
height: $quickToolSize;
position: absolute;
background: $quickToolBg;
border-radius: 50%;
opacity: 0;
transition: opacity .3s linear, transform .3s $timingFunction;
@include center-flex(both);}
active样式
.tool-item{ opacity: 1;
@for $i from 1 through $quickToolNum{
&:nth-of-type(#{$i}) { // item旋转 加 偏移
transform: rotate(($i - 1) * 90deg / ($quickToolNum - 1)) translateX(-80px);
// transition-delay: ($i - 1) * 0.03s;
.item-icon{ // icon旋转
transform: rotate(-($i - 1) * 90deg / ($quickToolNum - 1));
}
}
}
}
抽奖圆盘
转盘背景图如下图,我们要把奖品填写到相应的区域。
大体思想跟上面的差不多,大概如下:
1、先绝对定位在圆中间,然后先计算每个item的旋转角度,再设置偏移值
.item{ position: absolute;
left: 50%;
top: 50%;}@for $i from 1 through 8 {
.item:nth-child(#{$i}){ transform: translate(-50%, -50%) rotate(($i - 1)*45deg + 22.5deg) translate(90px, 0);
}
}
2、调整奖品文字的旋转
.item-inner{ transform: rotate(90deg);}
早读课提醒
言归正传我们在微信群中推出了《早读课》,每日分享一篇我们认真精选的文章(不限于前端开发类),其目的是帮助开发者来学习有价值的东西。想加微信群的朋友,直接添加我的微信号:icepy_1988,审核之后会邀请你入群。想加QQ群的朋友,可以直接添加:418898836,答对问题即可入群。
关注我们
扫二维码 或搜索 fed-talk ,关注我们的微信公众号,也欢迎你将它分享给自己的朋友。